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
{
Task<PaymentRequestDto> GetAsync(Guid id);
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 Volo.Abp.ObjectExtending;
@ -6,12 +7,20 @@ namespace Payment.PaymentRequests
public class PaymentRequestCreationDto : ExtensibleObject
{
public string CustomerId { get; set; }
public string ProductId { get; set; }
[Required]
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 string CustomerId { get; set; }
public string ProductId { 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; }
}
}

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;
namespace Payment.PaymentRequests
@ -6,16 +7,20 @@ namespace Payment.PaymentRequests
public class PaymentRequestAppService : PaymentAppService, IPaymentRequestAppService
{
private readonly IPaymentRequestRepository _paymentRequestRepository;
private readonly PaymentOptions _paymentOptions;
public PaymentRequestAppService(IPaymentRequestRepository paymentRequestRepository)
public PaymentRequestAppService(
IPaymentRequestRepository paymentRequestRepository,
IOptions<PaymentOptions> paymentOptions)
{
_paymentRequestRepository = paymentRequestRepository;
_paymentOptions = paymentOptions.Value;
}
public async Task<PaymentRequestDto> GetAsync(Guid id)
{
var paymentRequest = await _paymentRequestRepository.GetAsync(id);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
@ -26,7 +31,8 @@ namespace Payment.PaymentRequests
input.CustomerId,
input.ProductId,
input.ProductName,
input.Amount
input.Price,
input.Currency ?? _paymentOptions.DefaultCurrency
);
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;
namespace Payment
@ -9,6 +11,12 @@ namespace Payment
)]
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]
public string CustomerId { get; private set; }
[CanBeNull]
public string ProductId { get; private set; }
[NotNull]
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; }
@ -24,7 +26,7 @@ namespace Payment.PaymentRequests
private PaymentRequest()
{
}
public PaymentRequest(
@ -32,13 +34,15 @@ namespace Payment.PaymentRequests
[CanBeNull] string customerId,
[CanBeNull] string productId,
[NotNull] string productName,
decimal amount)
decimal price,
[NotNull] string currency)
: base(id)
{
CustomerId = customerId;
ProductId = productId;
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
@using Microsoft.Extensions.Localization
@using Payment.Localization
@using Payment.Web.Pages.Payment
@inherits Payment.Web.Pages.PaymentPageBase
@model Payment.Web.Pages.Payment.CheckoutPageModel
@inject IStringLocalizer<PaymentResource> L
@section scripts {
<abp-script-bundle name="@typeof(CheckoutPageModel).FullName">
@ -20,7 +18,7 @@
<abp-card-body>
<ul>
<li>Product name: @Model.PaymentRequest.ProductName</li>
<li>Amount: @Model.PaymentRequest.Amount</li>
<li>Amount: @Model.PaymentRequest.Price</li>
</ul>
</abp-card-body>
</abp-card>

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

@ -1,7 +1,14 @@
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;
namespace Payment.Web.Pages.Payment
{
@ -13,10 +20,17 @@ namespace Payment.Web.Pages.Payment
public PaymentRequestDto PaymentRequest { get; set; }
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;
this._paymentUrlBuilder = paymentUrlBuilder;
this._payPalHttpClient = payPalHttpClient;
}
public async Task OnGetAsync()
@ -28,10 +42,56 @@ namespace Payment.Web.Pages.Payment
{
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>
<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>

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

@ -5,5 +5,7 @@ namespace Payment.Web.PaymentRequest
public interface IPaymentUrlBuilder
{
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);
}
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
{
ProductName = "My product 1",
Amount = 99.99m
Price = 99.99m
}
);
result.Id.ShouldNotBe(Guid.Empty);
result.Amount.ShouldBe(99.99m);
result.Price.ShouldBe(99.99m);
result.ProductName.ShouldBe("My product 1");
}
}

8
src/EventHub.Web/appsettings.json

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

Loading…
Cancel
Save