Browse Source

Creating Payment Checkout scenario

pull/69/head
enisn 5 years ago
parent
commit
cc9be5dc7b
  1. 2
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs
  2. 17
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs
  3. 12
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs
  4. 14
      modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs
  5. 3
      modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml
  6. 30
      modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd
  7. 18
      modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs
  8. 22
      modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs
  9. 10
      modules/payment/src/Payment.Domain/PaymentDomainModule.cs
  10. 7
      modules/payment/src/Payment.Domain/PaymentOptions.cs
  11. 18
      modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs
  12. 6
      modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml
  13. 64
      modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs
  14. 8
      modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml
  15. 6
      modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs
  16. 20
      modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs
  17. 1
      modules/payment/src/Payment.Web/Payment.Web.csproj
  18. 2
      modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs
  19. 5
      modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs
  20. 4
      modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs
  21. 8
      src/EventHub.Web/appsettings.json

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

@ -7,7 +7,7 @@ namespace Payment.PaymentRequests
public interface IPaymentRequestAppService : IApplicationService public interface IPaymentRequestAppService : IApplicationService
{ {
Task<PaymentRequestDto> GetAsync(Guid id); Task<PaymentRequestDto> GetAsync(Guid id);
Task<PaymentRequestDto> CreateAsync(PaymentRequestCreationDto input); Task<PaymentRequestDto> CreateAsync(PaymentRequestCreationDto input);
} }
} }

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

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending;
@ -6,12 +7,20 @@ namespace Payment.PaymentRequests
public class PaymentRequestCreationDto : ExtensibleObject public class PaymentRequestCreationDto : ExtensibleObject
{ {
public string CustomerId { get; set; } public string CustomerId { get; set; }
public string ProductId { get; set; } public string ProductId { get; set; }
[Required] [Required]
public string ProductName { get; set; } public string ProductName { get; set; }
public decimal Amount { get; set; } public decimal Price { get; set; }
/// <summary>
/// 3 Letter Language Code in ISO 4217 Standards.
/// For example: USD, EUR.
/// </summary>
[StringLength(3)]
[CanBeNull]
public string Currency { get; set; }
} }
} }

12
modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs

@ -6,13 +6,15 @@ namespace Payment.PaymentRequests
public class PaymentRequestDto : CreationAuditedEntityDto<Guid> public class PaymentRequestDto : CreationAuditedEntityDto<Guid>
{ {
public string CustomerId { get; set; } public string CustomerId { get; set; }
public string ProductId { get; set; } public string ProductId { get; set; }
public string ProductName { get; set; } public string ProductName { get; set; }
public decimal Amount { get; set; } public decimal Price { get; set; }
public string Currency { get; set; }
public PaymentRequestState State { get; set; } public PaymentRequestState State { get; set; }
} }
} }

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

@ -1,4 +1,5 @@
using System; using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Payment.PaymentRequests namespace Payment.PaymentRequests
@ -6,16 +7,20 @@ namespace Payment.PaymentRequests
public class PaymentRequestAppService : PaymentAppService, IPaymentRequestAppService public class PaymentRequestAppService : PaymentAppService, IPaymentRequestAppService
{ {
private readonly IPaymentRequestRepository _paymentRequestRepository; private readonly IPaymentRequestRepository _paymentRequestRepository;
private readonly PaymentOptions _paymentOptions;
public PaymentRequestAppService(IPaymentRequestRepository paymentRequestRepository) public PaymentRequestAppService(
IPaymentRequestRepository paymentRequestRepository,
IOptions<PaymentOptions> paymentOptions)
{ {
_paymentRequestRepository = paymentRequestRepository; _paymentRequestRepository = paymentRequestRepository;
_paymentOptions = paymentOptions.Value;
} }
public async Task<PaymentRequestDto> GetAsync(Guid id) public async Task<PaymentRequestDto> GetAsync(Guid id)
{ {
var paymentRequest = await _paymentRequestRepository.GetAsync(id); var paymentRequest = await _paymentRequestRepository.GetAsync(id);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest); return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
} }
@ -26,7 +31,8 @@ namespace Payment.PaymentRequests
input.CustomerId, input.CustomerId,
input.ProductId, input.ProductId,
input.ProductName, input.ProductName,
input.Amount input.Price,
input.Currency ?? _paymentOptions.DefaultCurrency
); );
await _paymentRequestRepository.InsertAsync(paymentRequest); await _paymentRequestRepository.InsertAsync(paymentRequest);

3
modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

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

@ -0,0 +1,18 @@
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";
}
}
}

22
modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace Payment.PayPal
{
public class PayPalOptions
{
public string ClientId { get; set; }
public string Secret { get; set; }
public string Locale { get; set; }
/// <summary>
/// "Sandbox" or "Live". Default value is "Sandbox"
/// </summary>
public string Environment { get; set; } = PayPalConsts.Environment.Sandbox;
public bool Recommended { get; set; }
public List<string> ExtraInfos { get; set; } = new();
}
}

10
modules/payment/src/Payment.Domain/PaymentDomainModule.cs

@ -1,4 +1,6 @@
using Volo.Abp.Domain; using Microsoft.Extensions.DependencyInjection;
using Payment.PayPal;
using Volo.Abp.Domain;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Payment namespace Payment
@ -9,6 +11,12 @@ namespace Payment
)] )]
public class PaymentDomainModule : AbpModule public class PaymentDomainModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<PayPalOptions>(configuration.GetSection("Payment:PayPal"));
Configure<PaymentOptions>(configuration.GetSection("Payment"));
}
} }
} }

7
modules/payment/src/Payment.Domain/PaymentOptions.cs

@ -0,0 +1,7 @@
namespace Payment
{
public class PaymentOptions
{
public string DefaultCurrency { get; set; }
}
}

18
modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs

@ -9,14 +9,16 @@ namespace Payment.PaymentRequests
{ {
[CanBeNull] [CanBeNull]
public string CustomerId { get; private set; } public string CustomerId { get; private set; }
[CanBeNull] [CanBeNull]
public string ProductId { get; private set; } public string ProductId { get; private set; }
[NotNull] [NotNull]
public string ProductName { get; private set; } public string ProductName { get; private set; }
public decimal Amount { get; private set; } public decimal Price { get; private set; }
public string Currency { get; private set; }
public PaymentRequestState State { get; set; } public PaymentRequestState State { get; set; }
@ -24,7 +26,7 @@ namespace Payment.PaymentRequests
private PaymentRequest() private PaymentRequest()
{ {
} }
public PaymentRequest( public PaymentRequest(
@ -32,13 +34,15 @@ namespace Payment.PaymentRequests
[CanBeNull] string customerId, [CanBeNull] string customerId,
[CanBeNull] string productId, [CanBeNull] string productId,
[NotNull] string productName, [NotNull] string productName,
decimal amount) decimal price,
[NotNull] string currency)
: base(id) : base(id)
{ {
CustomerId = customerId; CustomerId = customerId;
ProductId = productId; ProductId = productId;
ProductName = Check.NotNullOrWhiteSpace(productName, nameof(productName)); ProductName = Check.NotNullOrWhiteSpace(productName, nameof(productName));
Amount = amount; Price = price;
Currency = currency;
} }
} }
} }

6
modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml

@ -1,9 +1,7 @@
@page @page
@using Microsoft.Extensions.Localization
@using Payment.Localization
@using Payment.Web.Pages.Payment @using Payment.Web.Pages.Payment
@inherits Payment.Web.Pages.PaymentPageBase
@model Payment.Web.Pages.Payment.CheckoutPageModel @model Payment.Web.Pages.Payment.CheckoutPageModel
@inject IStringLocalizer<PaymentResource> L
@section scripts { @section scripts {
<abp-script-bundle name="@typeof(CheckoutPageModel).FullName"> <abp-script-bundle name="@typeof(CheckoutPageModel).FullName">
@ -20,7 +18,7 @@
<abp-card-body> <abp-card-body>
<ul> <ul>
<li>Product name: @Model.PaymentRequest.ProductName</li> <li>Product name: @Model.PaymentRequest.ProductName</li>
<li>Amount: @Model.PaymentRequest.Amount</li> <li>Amount: @Model.PaymentRequest.Price</li>
</ul> </ul>
</abp-card-body> </abp-card-body>
</abp-card> </abp-card>

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

@ -1,7 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NUglify.Helpers;
using Payment.PaymentRequests; using Payment.PaymentRequests;
using Payment.Web.PaymentRequest;
using PayPalCheckoutSdk.Core;
using PayPalCheckoutSdk.Orders;
namespace Payment.Web.Pages.Payment namespace Payment.Web.Pages.Payment
{ {
@ -13,10 +20,17 @@ namespace Payment.Web.Pages.Payment
public PaymentRequestDto PaymentRequest { get; set; } public PaymentRequestDto PaymentRequest { get; set; }
private readonly IPaymentRequestAppService _paymentRequestAppService; private readonly IPaymentRequestAppService _paymentRequestAppService;
private readonly IPaymentUrlBuilder _paymentUrlBuilder;
private readonly PayPalHttpClient _payPalHttpClient;
public CheckoutPageModel(IPaymentRequestAppService paymentRequestAppService) public CheckoutPageModel(
IPaymentRequestAppService paymentRequestAppService,
IPaymentUrlBuilder paymentUrlBuilder,
PayPalHttpClient payPalHttpClient)
{ {
_paymentRequestAppService = paymentRequestAppService; _paymentRequestAppService = paymentRequestAppService;
this._paymentUrlBuilder = paymentUrlBuilder;
this._payPalHttpClient = payPalHttpClient;
} }
public async Task OnGetAsync() public async Task OnGetAsync()
@ -28,10 +42,56 @@ namespace Payment.Web.Pages.Payment
{ {
PaymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId); PaymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId);
// TODO: Redirect from here. var order = new OrderRequest
{
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>();
Response.Redirect(result.Links.First(x => x.Rel == "approve").Href);
} }
} }
} }

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

@ -0,0 +1,8 @@
@page
@using Payment.Web.Pages.Payment
@inherits Payment.Web.Pages.PaymentPageBase
@model Payment.Web.Pages.Payment.CheckoutCallbackModel
@{
PageLayout.Content.Title = L["Redirecting"];
}
<p>@L["Redirecting"]</p>

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

@ -0,0 +1,6 @@
namespace Payment.Web.Pages.Payment
{
public class CheckoutCallbackModel : PaymentPageModel
{
}
}

20
modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.Extensions.Localization;
using Payment.Localization;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Layout;
namespace Payment.Web.Pages
{
public class PaymentPageBase : Microsoft.AspNetCore.Mvc.RazorPages.Page
{
[RazorInject] public IStringLocalizer<PaymentResource> L { get; set; }
[RazorInject] public IPageLayout PageLayout { get; set; }
public override Task ExecuteAsync()
{
return Task.CompletedTask; // Will be overriden by razor pages. (.cshtml)
}
}
}

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

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

2
modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs

@ -5,5 +5,7 @@ namespace Payment.Web.PaymentRequest
public interface IPaymentUrlBuilder public interface IPaymentUrlBuilder
{ {
Uri BuildCheckoutUrl(Guid paymentRequestId); Uri BuildCheckoutUrl(Guid paymentRequestId);
Uri BuildReturnUrl(Guid paymentRequestId);
} }
} }

5
modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs

@ -18,5 +18,10 @@ namespace Payment.Web.PaymentRequest
{ {
return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/Checkout?paymentRequestId=" + paymentRequestId); return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/Checkout?paymentRequestId=" + paymentRequestId);
} }
public Uri BuildReturnUrl(Guid paymentRequestId)
{
return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/CheckoutCallback?paymentRequestId=" + paymentRequestId);
}
} }
} }

4
modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs

@ -21,12 +21,12 @@ namespace Payment.PaymentRequests
new PaymentRequestCreationDto new PaymentRequestCreationDto
{ {
ProductName = "My product 1", ProductName = "My product 1",
Amount = 99.99m Price = 99.99m
} }
); );
result.Id.ShouldNotBe(Guid.Empty); result.Id.ShouldNotBe(Guid.Empty);
result.Amount.ShouldBe(99.99m); result.Price.ShouldBe(99.99m);
result.ProductName.ShouldBe("My product 1"); result.ProductName.ShouldBe("My product 1");
} }
} }

8
src/EventHub.Web/appsettings.json

@ -7,5 +7,13 @@
"RequireHttpsMetadata": "true", "RequireHttpsMetadata": "true",
"ClientId": "EventHub_Web", "ClientId": "EventHub_Web",
"ClientSecret": "1q2w3e*" "ClientSecret": "1q2w3e*"
},
"Payment": {
"DefaultCurrency": "USD",
"PayPal": {
"ClientId": "{ClientId}",
"Secret": "{Secret}",
"Environment": "Sandbox"
}
} }
} }

Loading…
Cancel
Save