Browse Source

Merge pull request #17 from abpframework/enisn/payment-service

Payment Service with PayPal
pull/31/head
Galip Tolga Erdem 5 years ago
committed by GitHub
parent
commit
77dbf96f3b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs
  2. 18
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs
  3. 23
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestDto.cs
  4. 22
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestProduct.cs
  5. 18
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestProductCreationDto.cs
  6. 16
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs
  7. 10
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartResultDto.cs
  8. 47
      services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalConsts.cs
  9. 22
      services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalOptions.cs
  10. 156
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs
  11. 16
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs
  12. 15
      services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestCompletedEto.cs
  13. 7
      services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestConsts.cs
  14. 16
      services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestFailedEto.cs
  15. 9
      services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestState.cs
  16. 9
      services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/IPaymentRequestRepository.cs
  17. 73
      services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequest.cs
  18. 37
      services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequestProduct.cs
  19. 6
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/IPaymentServiceDbContext.cs
  20. 15
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/PaymentServiceDbContext.cs
  21. 27
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/PaymentServiceDbContextModelCreatingExtensions.cs
  22. 7
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/PaymentServiceEntityFrameworkCoreModule.cs
  23. 30
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211125131248_Initial.Designer.cs
  24. 19
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211125131248_Initial.cs
  25. 123
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211129051351_Initial.Designer.cs
  26. 70
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211129051351_Initial.cs
  27. 93
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/PaymentServiceDbContextModelSnapshot.cs
  28. 22
      services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/PaymentRequests/EfCorePaymentRequestRepository.cs
  29. 50
      services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs
  30. 30
      services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs

16
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs

@ -0,0 +1,16 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public interface IPaymentRequestAppService : IApplicationService
{
Task<PaymentRequestDto> CreateAsync(PaymentRequestCreationDto input);
Task<PaymentRequestStartResultDto> StartAsync(PaymentRequestStartDto input);
Task<PaymentRequestDto> CompleteAsync(string token);
Task<bool> HandleWebhookAsync(string payload);
}
}

18
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
[Serializable]
public class PaymentRequestCreationDto
{
[Required]
[MaxLength(PaymentRequestConsts.MaxCurrencyLength)]
public string Currency { get; set; }
public string BuyerId { get; set; }
public List<PaymentRequestProductCreationDto> Products { get; set; }
}
}

23
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestDto.cs

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
[Serializable]
public class PaymentRequestDto : CreationAuditedEntityDto<Guid>
{
[Required]
[MaxLength(PaymentRequestConsts.MaxCurrencyLength)]
public string Currency { get; set; }
public string BuyerId { get; set; }
public bool IsDeleted { get; set; }
public PaymentRequestState State { get; set; }
public List<PaymentRequestProductDto> Products { get; set; }
}
}

22
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestProduct.cs

@ -0,0 +1,22 @@
using System;
using Volo.Abp.Application.Dtos;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
[Serializable]
public class PaymentRequestProductDto : EntityDto<Guid>
{
public Guid PaymentRequestId { get; private set; }
public string ReferenceId { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice { get; set; }
}
}

18
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestProductCreationDto.cs

@ -0,0 +1,18 @@
using System;
namespace EShopOnAbp.PaymentService
{
[Serializable]
public class PaymentRequestProductCreationDto
{
public string ReferenceId { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice { get; set; }
}
}

16
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs

@ -0,0 +1,16 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
[Serializable]
public class PaymentRequestStartDto
{
public Guid PaymentRequestId { get; set; }
[Required]
public string ReturnUrl { get; set; }
public string CancelUrl { get; set; }
}
}

10
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartResultDto.cs

@ -0,0 +1,10 @@
using System;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
[Serializable]
public class PaymentRequestStartResultDto
{
public string CheckoutLink { get; set; }
}
}

47
services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalConsts.cs

@ -0,0 +1,47 @@
namespace EShopOnAbp.PaymentService.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";
}
}
}

22
services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalOptions.cs

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace EShopOnAbp.PaymentService.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();
}
}

156
services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs

@ -0,0 +1,156 @@
using EShopOnAbp.PaymentService.PayPal;
using Newtonsoft.Json.Linq;
using PayPalCheckoutSdk.Core;
using PayPalCheckoutSdk.Orders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public class PaymentRequestAppService : PaymentServiceAppService, IPaymentRequestAppService
{
protected IPaymentRequestRepository PaymentRequestRepository { get; }
protected PayPalHttpClient PayPalHttpClient { get; }
public PaymentRequestAppService(
IPaymentRequestRepository paymentRequestRepository,
PayPalHttpClient payPalHttpClient)
{
PaymentRequestRepository = paymentRequestRepository;
PayPalHttpClient = payPalHttpClient;
}
public async Task<PaymentRequestDto> CreateAsync(PaymentRequestCreationDto input)
{
var paymentRequest = new PaymentRequest(GuidGenerator.Create(), input.Currency, input.BuyerId);
foreach (var paymentRequestProduct in input.Products
.Select(s => new PaymentRequestProduct(
paymentRequest.Id,
s.Name,
s.UnitPrice,
s.Quantity,
s.TotalPrice,
s.ReferenceId)))
{
paymentRequest.Products.Add(paymentRequestProduct);
}
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
public async Task<PaymentRequestStartResultDto> StartAsync(PaymentRequestStartDto input)
{
var paymentRequest = await PaymentRequestRepository.GetAsync(input.PaymentRequestId);
var totalCheckoutPrice = paymentRequest.Products.Sum(s => s.TotalPrice);
var order = new OrderRequest
{
CheckoutPaymentIntent = "CAPTURE",
ApplicationContext = new ApplicationContext
{
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 = totalCheckoutPrice.ToString(".00")
}
},
CurrencyCode = paymentRequest.Currency,
Value = totalCheckoutPrice.ToString(".00"),
},
Items = paymentRequest.Products.Select(p => new Item
{
Quantity = p.Quantity.ToString(),
Name = p.Name,
UnitAmount = new Money
{
CurrencyCode = paymentRequest.Currency,
Value = p.UnitPrice.ToString(".00")
}
}).ToList(),
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 PaymentRequestStartResultDto
{
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 paymentRequest = await UpdatePaymentRequestStateAsync(order);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
public async Task<bool> HandleWebhookAsync(string payload)
{
var jObject = JObject.Parse(payload);
var order = jObject["resource"].ToObject<Order>();
var request = new OrdersGetRequest(order.Id);
// Ensure order object comes from PayPal
var response = await PayPalHttpClient.Execute(request);
order = response.Result<Order>();
await UpdatePaymentRequestStateAsync(order);
// PayPal doesn't accept Http 204 (NoContent) result and tries to execute webhook again.
// So with following value, API returns Http 200 (OK) result.
return true;
}
private async Task<PaymentRequest> UpdatePaymentRequestStateAsync(Order order)
{
var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId);
var paymentRequest = await PaymentRequestRepository.GetAsync(paymentRequestId);
if (order.Status == PayPalConsts.OrderStatus.Completed || order.Status == PayPalConsts.OrderStatus.Approved)
{
paymentRequest.SetAsCompleted();
}
else
{
paymentRequest.SetAsFailed(order.Status);
}
paymentRequest.ExtraProperties[PayPalConsts.OrderIdPropertyName] = order.Id;
paymentRequest.ExtraProperties[nameof(order.Status)] = order.Status;
await PaymentRequestRepository.UpdateAsync(paymentRequest);
return paymentRequest;
}
}
}

16
services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs

@ -2,6 +2,10 @@
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
using Volo.Abp.Application;
using Microsoft.Extensions.Options;
using EShopOnAbp.PaymentService.PayPal;
using PayPalCheckoutSdk.Core;
using System;
namespace EShopOnAbp.PaymentService
{
@ -20,6 +24,18 @@ namespace EShopOnAbp.PaymentService
{
options.AddMaps<PaymentServiceApplicationModule>(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));
});
}
}
}

15
services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestCompletedEto.cs

@ -0,0 +1,15 @@
using System;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus;
namespace EShopOnAbp.PaymentService
{
[Serializable]
[EventName("Payment.Completed")]
public class PaymentRequestCompletedEto: EtoBase, IHasExtraProperties
{
public Guid PaymentRequestId { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; }
}
}

7
services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestConsts.cs

@ -0,0 +1,7 @@
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public static class PaymentRequestConsts
{
public const int MaxCurrencyLength = 3;
}
}

16
services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestFailedEto.cs

@ -0,0 +1,16 @@
using System;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus;
namespace EShopOnAbp.PaymentService
{
[Serializable]
[EventName("Payment.Completed")]
public class PaymentRequestFailedEto : EtoBase, IHasExtraProperties
{
public Guid PaymentRequestId { get; set; }
public string FailReason { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; }
}
}

9
services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentRequests/PaymentRequestState.cs

@ -0,0 +1,9 @@
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public enum PaymentRequestState
{
Waiting = 0,
Completed,
Failed
}
}

9
services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/IPaymentRequestRepository.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public interface IPaymentRequestRepository : IBasicRepository<PaymentRequest, Guid>
{
}
}

73
services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequest.cs

@ -0,0 +1,73 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public class PaymentRequest : CreationAuditedAggregateRoot<Guid>, ISoftDelete
{
[NotNull]
public string Currency { get; protected set; }
[CanBeNull]
public string BuyerId { get; protected set; }
public PaymentRequestState State { get; protected set; }
[CanBeNull]
public string FailReason { get; protected set; }
public bool IsDeleted { get; set; }
public ICollection<PaymentRequestProduct> Products { get; } = new List<PaymentRequestProduct>();
private PaymentRequest()
{
}
public PaymentRequest(Guid id, [NotNull] string currency, [CanBeNull] string buyerId = null)
{
Id = id;
Currency = Check.NotNullOrWhiteSpace(currency, nameof(currency), maxLength: PaymentRequestConsts.MaxCurrencyLength);
BuyerId = buyerId;
}
public virtual void SetAsCompleted()
{
if (State == PaymentRequestState.Completed)
{
return;
}
State = PaymentRequestState.Completed;
FailReason = null;
AddDistributedEvent(new PaymentRequestCompletedEto
{
PaymentRequestId = Id,
ExtraProperties = ExtraProperties
});
}
public virtual void SetAsFailed(string failReason)
{
if (State != PaymentRequestState.Failed)
{
return;
}
State = PaymentRequestState.Failed;
FailReason = failReason;
AddDistributedEvent(new PaymentRequestFailedEto
{
PaymentRequestId = Id,
FailReason = failReason,
ExtraProperties = ExtraProperties
});
}
}
}

37
services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequestProduct.cs

@ -0,0 +1,37 @@
using JetBrains.Annotations;
using System;
using Volo.Abp.Domain.Entities;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public class PaymentRequestProduct : Entity<Guid>
{
public Guid PaymentRequestId { get; private set; }
public string ReferenceId { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice { get; set; }
public PaymentRequestProduct(
Guid paymentRequestId,
[NotNull] string name,
decimal unitPrice,
int quantity,
decimal totalPrice,
[CanBeNull] string referenceId = null)
{
PaymentRequestId = paymentRequestId;
Name = name;
UnitPrice = unitPrice;
Quantity = quantity;
TotalPrice = totalPrice;
ReferenceId = referenceId;
}
}
}

6
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/IPaymentServiceDbContext.cs

@ -1,4 +1,6 @@
using Volo.Abp.Data;
using EShopOnAbp.PaymentService.PaymentRequests;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace EShopOnAbp.PaymentService.EntityFrameworkCore
@ -9,5 +11,7 @@ namespace EShopOnAbp.PaymentService.EntityFrameworkCore
/* Add DbSet for each Aggregate Root here. Example:
* DbSet<Question> Questions { get; }
*/
DbSet<PaymentRequest> PaymentRequests { get; set; }
}
}

15
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/PaymentServiceDbContext.cs

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using EShopOnAbp.PaymentService.PaymentRequests;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
@ -6,7 +7,8 @@ namespace EShopOnAbp.PaymentService.EntityFrameworkCore
{
[ConnectionStringName(PaymentServiceDbProperties.ConnectionStringName)]
public class PaymentServiceDbContext :
AbpDbContext<PaymentServiceDbContext>
AbpDbContext<PaymentServiceDbContext>,
IPaymentServiceDbContext
{
/* Add DbSet properties for your Aggregate Roots / Entities here. */
@ -27,6 +29,8 @@ namespace EShopOnAbp.PaymentService.EntityFrameworkCore
}
public DbSet<PaymentRequest> PaymentRequests { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
@ -36,13 +40,6 @@ namespace EShopOnAbp.PaymentService.EntityFrameworkCore
builder.ConfigurePaymentService();
/* Configure your own tables/entities inside here */
//builder.Entity<YourEntity>(b =>
//{
// b.ToTable(PaymentServiceConsts.DbTablePrefix + "YourEntities", PaymentServiceConsts.DbSchema);
// b.ConfigureByConvention(); //auto configure for the base class props
// //...
//});
}
}
}

27
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/PaymentServiceDbContextModelCreatingExtensions.cs

@ -1,5 +1,7 @@
using Microsoft.EntityFrameworkCore;
using EShopOnAbp.PaymentService.PaymentRequests;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace EShopOnAbp.PaymentService.EntityFrameworkCore
{
@ -10,25 +12,16 @@ namespace EShopOnAbp.PaymentService.EntityFrameworkCore
{
Check.NotNull(builder, nameof(builder));
/* Configure all entities here. Example:
builder.Entity<Question>(b =>
builder.Entity<PaymentRequest>(entity =>
{
//Configure table & schema name
b.ToTable(PaymentServiceDbProperties.DbTablePrefix + "Questions", PaymentServiceDbProperties.DbSchema);
b.ConfigureByConvention();
//Properties
b.Property(q => q.Title).IsRequired().HasMaxLength(QuestionConsts.MaxTitleLength);
entity.ConfigureByConvention();
//Relations
b.HasMany(question => question.Tags).WithOne().HasForeignKey(qt => qt.QuestionId);
//Indexes
b.HasIndex(q => q.CreationTime);
entity
.Property(p => p.Currency)
.IsRequired()
.HasMaxLength(PaymentRequestConsts.MaxCurrencyLength);
});
*/
}
}
}

7
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/EntityFrameworkCore/PaymentServiceEntityFrameworkCoreModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using EShopOnAbp.PaymentService.PaymentRequests;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.PostgreSql;
using Volo.Abp.Modularity;
@ -20,9 +21,7 @@ namespace EShopOnAbp.PaymentService.EntityFrameworkCore
{
context.Services.AddAbpDbContext<PaymentServiceDbContext>(options =>
{
/* Remove "includeAllEntities: true" to create
* default repositories only for aggregate roots */
options.AddDefaultRepositories(includeAllEntities: true);
options.AddRepository<PaymentRequest, EfCorePaymentRequestRepository>();
});
Configure<AbpDbContextOptions>(options =>

30
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211125131248_Initial.Designer.cs

@ -1,30 +0,0 @@
// <auto-generated />
using EShopOnAbp.PaymentService.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Volo.Abp.EntityFrameworkCore;
#nullable disable
namespace EShopOnAbp.PaymentService.Migrations
{
[DbContext(typeof(PaymentServiceDbContext))]
[Migration("20211125131248_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql)
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
#pragma warning restore 612, 618
}
}
}

19
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211125131248_Initial.cs

@ -1,19 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopOnAbp.PaymentService.Migrations
{
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

123
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211129051351_Initial.Designer.cs

@ -0,0 +1,123 @@
// <auto-generated />
using System;
using EShopOnAbp.PaymentService.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Volo.Abp.EntityFrameworkCore;
#nullable disable
namespace EShopOnAbp.PaymentService.Migrations
{
[DbContext(typeof(PaymentServiceDbContext))]
[Migration("20211129051351_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql)
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequest", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("BuyerId")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("character varying(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uuid")
.HasColumnName("CreatorId");
b.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b.Property<string>("ExtraProperties")
.HasColumnType("text")
.HasColumnName("ExtraProperties");
b.Property<string>("FailReason")
.HasColumnType("text");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<int>("State")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("PaymentRequests");
});
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestProduct", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<Guid>("PaymentRequestId")
.HasColumnType("uuid");
b.Property<int>("Quantity")
.HasColumnType("integer");
b.Property<string>("ReferenceId")
.HasColumnType("text");
b.Property<decimal>("TotalPrice")
.HasColumnType("numeric");
b.Property<decimal>("UnitPrice")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("PaymentRequestId");
b.ToTable("PaymentRequestProduct");
});
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestProduct", b =>
{
b.HasOne("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequest", null)
.WithMany("Products")
.HasForeignKey("PaymentRequestId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequest", b =>
{
b.Navigation("Products");
});
#pragma warning restore 612, 618
}
}
}

70
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/20211129051351_Initial.cs

@ -0,0 +1,70 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopOnAbp.PaymentService.Migrations
{
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PaymentRequests",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Currency = table.Column<string>(type: "character varying(3)", maxLength: 3, nullable: false),
BuyerId = table.Column<string>(type: "text", nullable: true),
State = table.Column<int>(type: "integer", nullable: false),
FailReason = table.Column<string>(type: "text", nullable: true),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
ExtraProperties = table.Column<string>(type: "text", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatorId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PaymentRequests", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PaymentRequestProduct",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
PaymentRequestId = table.Column<Guid>(type: "uuid", nullable: false),
ReferenceId = table.Column<string>(type: "text", nullable: true),
Name = table.Column<string>(type: "text", nullable: true),
UnitPrice = table.Column<decimal>(type: "numeric", nullable: false),
Quantity = table.Column<int>(type: "integer", nullable: false),
TotalPrice = table.Column<decimal>(type: "numeric", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PaymentRequestProduct", x => x.Id);
table.ForeignKey(
name: "FK_PaymentRequestProduct_PaymentRequests_PaymentRequestId",
column: x => x.PaymentRequestId,
principalTable: "PaymentRequests",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_PaymentRequestProduct_PaymentRequestId",
table: "PaymentRequestProduct",
column: "PaymentRequestId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PaymentRequestProduct");
migrationBuilder.DropTable(
name: "PaymentRequests");
}
}
}

93
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/Migrations/PaymentServiceDbContextModelSnapshot.cs

@ -1,4 +1,5 @@
// <auto-generated />
using System;
using EShopOnAbp.PaymentService.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -22,6 +23,98 @@ namespace EShopOnAbp.PaymentService.Migrations
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequest", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("BuyerId")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("character varying(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uuid")
.HasColumnName("CreatorId");
b.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b.Property<string>("ExtraProperties")
.HasColumnType("text")
.HasColumnName("ExtraProperties");
b.Property<string>("FailReason")
.HasColumnType("text");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<int>("State")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("PaymentRequests");
});
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestProduct", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<Guid>("PaymentRequestId")
.HasColumnType("uuid");
b.Property<int>("Quantity")
.HasColumnType("integer");
b.Property<string>("ReferenceId")
.HasColumnType("text");
b.Property<decimal>("TotalPrice")
.HasColumnType("numeric");
b.Property<decimal>("UnitPrice")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("PaymentRequestId");
b.ToTable("PaymentRequestProduct");
});
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestProduct", b =>
{
b.HasOne("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequest", null)
.WithMany("Products")
.HasForeignKey("PaymentRequestId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("EShopOnAbp.PaymentService.PaymentRequests.PaymentRequest", b =>
{
b.Navigation("Products");
});
#pragma warning restore 612, 618
}
}

22
services/payment/src/EShopOnAbp.PaymentService.EntityFrameworkCore/PaymentRequests/EfCorePaymentRequestRepository.cs

@ -0,0 +1,22 @@
using EShopOnAbp.PaymentService.EntityFrameworkCore;
using System;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public class EfCorePaymentRequestRepository :
EfCoreRepository<
IPaymentServiceDbContext,
PaymentRequest,
Guid>,
IPaymentRequestRepository
{
public EfCorePaymentRequestRepository(IDbContextProvider<IPaymentServiceDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
}

50
services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs

@ -0,0 +1,50 @@
using EShopOnAbp.PaymentService.PaymentRequests;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
namespace EShopOnAbp.PaymentService.Controllers
{
[RemoteService(Name = PaymentServiceRemoteServiceConsts.RemoteServiceName)]
[Area("payment")]
[Route("api/payment/requests")]
public class PaymentRequestController : PaymentServiceController, IPaymentRequestAppService
{
protected IPaymentRequestAppService PaymentRequestAppService { get; }
public PaymentRequestController(IPaymentRequestAppService paymentRequestAppService)
{
PaymentRequestAppService = paymentRequestAppService;
}
[HttpPost("complete")]
public Task<PaymentRequestDto> CompleteAsync(string token)
{
return PaymentRequestAppService.CompleteAsync(token);
}
[HttpPost]
public Task<PaymentRequestDto> CreateAsync(PaymentRequestCreationDto input)
{
return PaymentRequestAppService.CreateAsync(input);
}
[HttpPost]
[Route("webhook")]
public async Task<bool> HandleWebhookAsync(string payload)
{
var bytes = await Request.Body.GetAllBytesAsync();
payload = Encoding.UTF8.GetString(bytes);
return await PaymentRequestAppService.HandleWebhookAsync(payload);
}
[HttpPost("start")]
public Task<PaymentRequestStartResultDto> StartAsync(PaymentRequestStartDto input)
{
return PaymentRequestAppService.StartAsync(input);
}
}
}

30
services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs

@ -0,0 +1,30 @@
using EShopOnAbp.PaymentService.EntityFrameworkCore;
using Shouldly;
using System;
using System.Threading.Tasks;
using Xunit;
namespace EShopOnAbp.PaymentService.PaymentRequests
{
public class PaymentRequestRepository_Tests : PaymentServiceEntityFrameworkCoreTestBase
{
private readonly IPaymentRequestRepository _paymentRequestRepository;
public PaymentRequestRepository_Tests()
{
_paymentRequestRepository = GetRequiredService<IPaymentRequestRepository>();
}
[Fact]
public async Task Should_Insert_Payment_Request()
{
var id = Guid.NewGuid();
var paymentRequest = new PaymentRequest(id, "USD");
await _paymentRequestRepository.InsertAsync(paymentRequest, autoSave: true);
var inserted = await _paymentRequestRepository.GetAsync(id);
inserted.Id.ShouldNotBe(Guid.Empty);
}
}
}
Loading…
Cancel
Save