feat(payment): implement multi-gateway support and enhance payment processing
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m44s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m44s
- Added support for multiple payment gateways, including ZarinPal and TwentyCard, through a new IPaymentGatewayResolver. - Updated PaymentRequest DTO to include payer name and invoice number for better transaction tracking. - Enhanced PackageService to dynamically determine payment amounts based on the selected gateway. - Updated appsettings to include configuration for TwentyCard payment options. - Bumped Protobuf project version to reflect the addition of new features and changes.
This commit is contained in:
@@ -32,7 +32,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IPaymentGatewayResolver _paymentGatewayResolver;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
ISender sender,
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IPaymentGatewayResolver paymentGatewayResolver,
|
||||
IConfiguration configuration,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
@@ -49,7 +49,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
_paymentGateway = paymentGateway;
|
||||
_paymentGatewayResolver = paymentGatewayResolver;
|
||||
_configuration = configuration;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
@@ -171,19 +171,38 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
|
||||
if (package == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "پکیج مورد نظر یافت نشد"));
|
||||
|
||||
// Create transaction
|
||||
|
||||
var gatewayProvider = NormalizeGatewayProvider(request.GatewayProvider);
|
||||
var isTwentyCard = gatewayProvider == "20card";
|
||||
|
||||
long gatewayAmount = package.Price;
|
||||
if (isTwentyCard)
|
||||
{
|
||||
var packageKey = $"TwentyCard:Packages:{package.Id}";
|
||||
gatewayAmount = _configuration.GetValue<long?>($"{packageKey}:GatewayAmountToman") ?? 0;
|
||||
var walletCredit = _configuration.GetValue<long?>($"{packageKey}:WalletCreditToman") ?? 0;
|
||||
if (gatewayAmount <= 0 || walletCredit <= 0)
|
||||
{
|
||||
throw new RpcException(new Status(
|
||||
StatusCode.FailedPrecondition,
|
||||
"پرداخت ۲۰کارت برای این پکیج پیکربندی نشده است"));
|
||||
}
|
||||
}
|
||||
|
||||
// Create transaction — 20card: Amount = gateway charge; zarinpal: Package.Price
|
||||
var transaction = new CMSMicroservice.Domain.Entities.Transaction
|
||||
{
|
||||
Amount = package.Price,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
Amount = gatewayAmount,
|
||||
Description = isTwentyCard
|
||||
? $"خرید پکیج {package.Title} (۲۰کارت)"
|
||||
: $"خرید پکیج {package.Title}",
|
||||
PaymentStatus = Domain.Enums.PaymentStatus.Pending,
|
||||
Type = Domain.Enums.TransactionType.Buy
|
||||
};
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Create purchase record
|
||||
// Create purchase record — UPP.Amount always Package.Price (catalog)
|
||||
var purchaseMethod = request.PurchaseMethod == PurchaseMethodEnum.PurchaseMethodGateway
|
||||
? Domain.Enums.PackagePurchaseMethod.DirectPurchase
|
||||
: Domain.Enums.PackagePurchaseMethod.DayaLoan;
|
||||
@@ -204,18 +223,23 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new { u.Mobile })
|
||||
.Select(u => new { u.Mobile, u.FirstName, u.LastName })
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}";
|
||||
|
||||
var paymentGateway = _paymentGatewayResolver.Get(gatewayProvider);
|
||||
var payerName = $"{user?.FirstName} {user?.LastName}".Trim();
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
var paymentResult = await paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
Amount = package.Price,
|
||||
Amount = gatewayAmount,
|
||||
UserId = userId,
|
||||
Mobile = user?.Mobile ?? string.Empty,
|
||||
Name = payerName,
|
||||
Invoice = purchase.Id.ToString(),
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
CallbackUrl = callbackUrl
|
||||
}, cancellationToken);
|
||||
@@ -235,12 +259,14 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
// Save RefId
|
||||
transaction.RefId = paymentResult.RefId;
|
||||
|
||||
// ثبت PaymentTransaction
|
||||
// ثبت PaymentTransaction — Amount = gateway amount
|
||||
var paymentTx = new PaymentTransaction
|
||||
{
|
||||
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
|
||||
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
|
||||
Amount = package.Price,
|
||||
GatewayProvider = gatewayProvider,
|
||||
MerchantId = isTwentyCard
|
||||
? (_configuration["TwentyCard:TerminalId"] ?? "")
|
||||
: (_configuration["ZarinPal:MerchantId"] ?? ""),
|
||||
Amount = gatewayAmount,
|
||||
CallbackUrl = callbackUrl,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
Mobile = user?.Mobile,
|
||||
@@ -264,6 +290,14 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
Authority = paymentResult.RefId ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static string NormalizeGatewayProvider(string? provider)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(provider))
|
||||
return "zarinpal";
|
||||
var key = provider.Trim().ToLowerInvariant();
|
||||
return key is "20card" or "twentycard" ? "20card" : "zarinpal";
|
||||
}
|
||||
|
||||
public override async Task<CustomerVerifyPackagePurchaseResponse> CustomerVerifyPackagePurchase(
|
||||
CustomerVerifyPackagePurchaseRequest request,
|
||||
|
||||
Reference in New Issue
Block a user