Browse Source

Move entire PayPal logic to Application layer

pull/69/head
enisn 5 years ago
parent
commit
de0d8b20c9
  1. 4
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs
  2. 17
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentDto.cs
  3. 7
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentResultDto.cs
  4. 3
      modules/payment/src/Payment.Application/Payment.Application.csproj
  5. 16
      modules/payment/src/Payment.Application/PaymentApplicationModule.cs
  6. 92
      modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs
  7. 6
      modules/payment/src/Payment.Domain.Shared/Localization/Payment/en.json
  8. 47
      modules/payment/src/Payment.Domain.Shared/PayPal/PayPalConsts.cs
  9. 18
      modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs
  10. 71
      modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs
  11. 31
      modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml
  12. 49
      modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs
  13. 1
      modules/payment/src/Payment.Web/Payment.Web.csproj
  14. 8
      modules/payment/src/Payment.Web/PaymentWebOptions.cs

4
modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs

@ -9,5 +9,9 @@ namespace Payment.PaymentRequests
Task<PaymentRequestDto> GetAsync(Guid id);
Task<PaymentRequestDto> CreateAsync(PaymentRequestCreationDto input);
Task<StartPaymentResultDto> StartPaymentAsync(StartPaymentDto input);
Task<PaymentRequestDto> CompleteAsync(string token);
}
}

17
modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentDto.cs

@ -0,0 +1,17 @@
using JetBrains.Annotations;
using System;
using System.ComponentModel.DataAnnotations;
namespace Payment.PaymentRequests
{
public class StartPaymentDto
{
public Guid PaymentRequestId { get; set; }
[NotNull]
[Required]
public string ReturnUrl { get; set; }
public string CancelUrl { get; set; }
}
}

7
modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentResultDto.cs

@ -0,0 +1,7 @@
namespace Payment.PaymentRequests
{
public class StartPaymentResultDto
{
public string CheckoutLink { get; set; }
}
}

3
modules/payment/src/Payment.Application/Payment.Application.csproj

@ -14,4 +14,7 @@
<ProjectReference Include="..\Payment.Domain\Payment.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="PayPalCheckoutSdk" Version="1.0.4" />
</ItemGroup>
</Project>

16
modules/payment/src/Payment.Application/PaymentApplicationModule.cs

@ -2,6 +2,10 @@
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
using Volo.Abp.Application;
using Microsoft.Extensions.Options;
using Payment.PayPal;
using System;
using PayPalCheckoutSdk.Core;
namespace Payment
{
@ -20,6 +24,18 @@ namespace Payment
{
options.AddMaps<PaymentApplicationModule>(validate: true);
});
context.Services.AddTransient(provider =>
{
var options = provider.GetService<IOptions<PayPalOptions>>().Value;
if (options.Environment.IsNullOrWhiteSpace() || options.Environment == PayPalConsts.Environment.Sandbox)
{
return new PayPalHttpClient(new SandboxEnvironment(options.ClientId, options.Secret));
}
return new PayPalHttpClient(new LiveEnvironment(options.ClientId, options.Secret));
});
}
}
}

92
modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs

@ -1,5 +1,10 @@
using Microsoft.Extensions.Options;
using Payment.PayPal;
using PayPalCheckoutSdk.Core;
using PayPalCheckoutSdk.Orders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Payment.PaymentRequests
@ -8,13 +13,16 @@ namespace Payment.PaymentRequests
{
private readonly IPaymentRequestRepository _paymentRequestRepository;
private readonly PaymentOptions _paymentOptions;
private readonly PayPalHttpClient _payPalHttpClient;
public PaymentRequestAppService(
IPaymentRequestRepository paymentRequestRepository,
IOptions<PaymentOptions> paymentOptions)
IOptions<PaymentOptions> paymentOptions,
PayPalHttpClient payPalHttpClient)
{
_paymentRequestRepository = paymentRequestRepository;
_paymentOptions = paymentOptions.Value;
_payPalHttpClient = payPalHttpClient;
}
public async Task<PaymentRequestDto> GetAsync(Guid id)
@ -39,5 +47,87 @@ namespace Payment.PaymentRequests
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
public async Task<StartPaymentResultDto> StartPaymentAsync(StartPaymentDto input)
{
var paymentRequest = await _paymentRequestRepository.GetAsync(input.PaymentRequestId);
var order = new OrderRequest
{
CheckoutPaymentIntent = "CAPTURE",
ApplicationContext = new ApplicationContext
{
//ReturnUrl = _paymentUrlBuilder.BuildReturnUrl(PaymentRequest.Id).AbsoluteUri,
//CancelUrl = _paymentUrlBuilder.BuildCheckoutUrl(PaymentRequest.Id).AbsoluteUri,
ReturnUrl = input.ReturnUrl,
CancelUrl = input.CancelUrl,
},
PurchaseUnits = new List<PurchaseUnitRequest>
{
new PurchaseUnitRequest
{
AmountWithBreakdown = new AmountWithBreakdown
{
AmountBreakdown = new AmountBreakdown
{
ItemTotal = new Money
{
CurrencyCode = paymentRequest.Currency,
Value = paymentRequest.Price.ToString(".00")
}
},
CurrencyCode = paymentRequest.Currency,
Value = paymentRequest.Price.ToString(".00"),
},
Items = new List<Item>
{
new Item
{
Quantity = 1.ToString(),
Name = paymentRequest.ProductName,
UnitAmount = new Money
{
CurrencyCode = paymentRequest.Currency,
Value = paymentRequest.Price.ToString(".00")
}
}
},
ReferenceId = paymentRequest.Id.ToString()
}
}
};
var request = new OrdersCreateRequest();
request.Prefer("return=representation");
request.RequestBody(order);
var result = (await _payPalHttpClient.Execute(request)).Result<Order>();
return new StartPaymentResultDto
{
CheckoutLink = result.Links.First(x => x.Rel == "approve").Href
};
}
public async Task<PaymentRequestDto> CompleteAsync(string token)
{
var request = new OrdersCaptureRequest(token);
request.RequestBody(new OrderActionRequest());
var order = (await _payPalHttpClient.Execute(request)).Result<Order>();
var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId);
var paymentRequest = await _paymentRequestRepository.GetAsync(paymentRequestId);
paymentRequest.State = order.Status == PayPalConsts.OrderStatus.Approved || order.Status == PayPalConsts.OrderStatus.Completed ?
PaymentRequestState.Completed : PaymentRequestState.Failed;
paymentRequest.ExtraProperties.Add(PayPalConsts.OrderIdPropertyName, order.Id);
await _paymentRequestRepository.UpdateAsync(paymentRequest);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
}
}

6
modules/payment/src/Payment.Domain.Shared/Localization/Payment/en.json

@ -1,7 +1,9 @@
{
"culture": "en",
"texts": {
"MyAccount": "My account",
"SamplePageMessage": "A sample page for the Payment module"
"MyAccount": "My account",
"SamplePageMessage": "A sample page for the Payment module",
"YourPaymentCompletedSuccessfully": "Your payment has been completed successfully!",
"YourPaymentFailed": "Your payment has been failed unfortunetely!"
}
}

47
modules/payment/src/Payment.Domain.Shared/PayPal/PayPalConsts.cs

@ -0,0 +1,47 @@
namespace Payment.PayPal
{
public static class PayPalConsts
{
public const string OrderIdPropertyName = "OrderId";
public static class OrderStatus
{
/// <summary>
/// The order was created with the specified context.
/// </summary>
public const string Created = "CREATED";
/// <summary>
/// The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order.
/// </summary>
public const string Saved = "SAVED";
/// <summary>
/// The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on.
/// </summary>
public const string Approved = "APPROVED";
/// <summary>
/// All purchase units in the order are voided.
/// </summary>
public const string Voided = "VOIDED";
/// <summary>
/// The payment was authorized or the authorized payment was captured for the order.
/// </summary>
public const string Completed = "COMPLETED";
/// <summary>
/// The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order.
/// </summary>
public const string PlayerActionRequired = "PAYER_ACTION_REQUIRED";
}
public static class Environment
{
public const string Sandbox = "Sandbox";
public const string Live = "Live";
}
}
}

18
modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Payment.PayPal
{
public static class PayPalConsts
{
public static class Environment
{
public const string Sandbox = "Sandbox";
public const string Live = "Live";
}
}
}

71
modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs

@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NUglify.Helpers;
using Payment.PaymentRequests;
using Payment.Web.PaymentRequest;
using PayPalCheckoutSdk.Core;
using PayPalCheckoutSdk.Orders;
using System;
using System.Threading.Tasks;
namespace Payment.Web.Pages.Payment
{
@ -21,16 +15,13 @@ namespace Payment.Web.Pages.Payment
private readonly IPaymentRequestAppService _paymentRequestAppService;
private readonly IPaymentUrlBuilder _paymentUrlBuilder;
private readonly PayPalHttpClient _payPalHttpClient;
public CheckoutPageModel(
IPaymentRequestAppService paymentRequestAppService,
IPaymentUrlBuilder paymentUrlBuilder,
PayPalHttpClient payPalHttpClient)
IPaymentUrlBuilder paymentUrlBuilder)
{
_paymentRequestAppService = paymentRequestAppService;
this._paymentUrlBuilder = paymentUrlBuilder;
this._payPalHttpClient = payPalHttpClient;
}
public async Task OnGetAsync()
@ -40,58 +31,14 @@ namespace Payment.Web.Pages.Payment
public async Task OnPostAsync()
{
PaymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId);
var order = new OrderRequest
var result = await _paymentRequestAppService.StartPaymentAsync(new StartPaymentDto
{
CheckoutPaymentIntent = "CAPTURE",
ApplicationContext = new ApplicationContext
{
ReturnUrl = _paymentUrlBuilder.BuildReturnUrl(PaymentRequest.Id).AbsoluteUri,
CancelUrl = _paymentUrlBuilder.BuildCheckoutUrl(PaymentRequest.Id).AbsoluteUri,
},
PurchaseUnits = new List<PurchaseUnitRequest>
{
new PurchaseUnitRequest
{
AmountWithBreakdown = new AmountWithBreakdown
{
AmountBreakdown = new AmountBreakdown
{
ItemTotal = new Money
{
CurrencyCode = PaymentRequest.Currency,
Value = PaymentRequest.Price.ToString(".00")
}
},
CurrencyCode = PaymentRequest.Currency,
Value = PaymentRequest.Price.ToString(".00"),
},
Items = new List<Item>
{
new Item
{
Quantity = 1.ToString(),
Name = PaymentRequest.ProductName,
UnitAmount = new Money
{
CurrencyCode = PaymentRequest.Currency,
Value = PaymentRequest.Price.ToString(".00")
}
}
},
ReferenceId = PaymentRequest.Id.ToString()
}
}
};
var request = new OrdersCreateRequest();
request.Prefer("return=representation");
request.RequestBody(order);
var result = (await _payPalHttpClient.Execute(request)).Result<Order>();
PaymentRequestId = PaymentRequestId,
ReturnUrl = _paymentUrlBuilder.BuildReturnUrl(PaymentRequestId).AbsoluteUri,
CancelUrl = _paymentUrlBuilder.BuildCheckoutUrl(PaymentRequestId).AbsoluteUri
});
Response.Redirect(result.Links.First(x => x.Rel == "approve").Href);
Response.Redirect(result.CheckoutLink);
}
}
}

31
modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml

@ -1,8 +1,35 @@
@page
@using Payment.Web.Pages.Payment
@using Payment.PaymentRequests
@inherits Payment.Web.Pages.PaymentPageBase
@model Payment.Web.Pages.Payment.CheckoutCallbackModel
@{
PageLayout.Content.Title = L["Redirecting"];
PageLayout.Content.Title = L["PaymentResult"] + L["Enum:PaymentRequestState:" + Model.PaymentRequest.State];
}
<p>@L["Redirecting"]</p>
<abp-card>
<abp-card-header>
<h2>@L["PaymentResult"]</h2>
</abp-card-header>
<abp-card-body>
@if (Model.PaymentRequest.State == PaymentRequestState.Completed)
{
<abp-alert alert-type="Success" dismissible="false">
<h1><i class="bi-check-circle-fill"></i></h1>
@L["YourPaymentCompletedSuccessfully"]
</abp-alert>
}
else
{
<abp-alert alert-type="Danger" dismissible="false">
<h1><i class="bi-check-circle-fill"></i></h1>
@L["YourPaymentFailed"]
</abp-alert>
<abp-button text="@L["GoBack"]"/>
}
</abp-card-body>
</abp-card>

49
modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs

@ -1,6 +1,53 @@
namespace Payment.Web.Pages.Payment
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Payment.PaymentRequests;
using System;
using System.Threading.Tasks;
namespace Payment.Web.Pages.Payment
{
public class CheckoutCallbackModel : PaymentPageModel
{
[BindProperty(SupportsGet = true)]
public string Token { get; set; }
public PaymentRequestDto PaymentRequest { get; protected set; }
private readonly IPaymentRequestAppService _paymentRequestAppService;
private readonly PaymentWebOptions _paymentWebOptions;
public CheckoutCallbackModel(
IPaymentRequestAppService appService,
IOptions<PaymentWebOptions> paymentWebOptions)
{
_paymentRequestAppService = appService;
_paymentWebOptions = paymentWebOptions.Value;
}
public async Task<IActionResult> OnGetAsync()
{
if (Token.IsNullOrWhiteSpace())
{
return BadRequest();
}
PaymentRequest = await _paymentRequestAppService.CompleteAsync(Token);
if (PaymentRequest.State == PaymentRequestState.Completed
&& !_paymentWebOptions.PaymentSuccessfulCallbackUrl.IsNullOrWhiteSpace())
{
var callbackUrl = _paymentWebOptions.PaymentSuccessfulCallbackUrl + "?paymentRequestId=" + PaymentRequest.Id;
Response.Redirect(callbackUrl);
}
if (PaymentRequest.State == PaymentRequestState.Failed
&& !_paymentWebOptions.PaymentFailureCallbackUrl.IsNullOrWhiteSpace())
{
var callbackUrl = _paymentWebOptions.PaymentFailureCallbackUrl + "?paymentRequestId=" + PaymentRequest.Id;
Response.Redirect(callbackUrl);
}
return Page();
}
}
}

1
modules/payment/src/Payment.Web/Payment.Web.csproj

@ -12,7 +12,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PayPalCheckoutSdk" Version="1.0.4" />
<PackageReference Include="Volo.Abp.AutoMapper" Version="5.0.0-beta.1" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared" Version="5.0.0-beta.1" />
</ItemGroup>

8
modules/payment/src/Payment.Web/PaymentWebOptions.cs

@ -0,0 +1,8 @@
namespace Payment.Web
{
public class PaymentWebOptions
{
public string PaymentSuccessfulCallbackUrl { get; set; }
public string PaymentFailureCallbackUrl { get; set; }
}
}
Loading…
Cancel
Save