feat(payment): add per-user in-memory lock for gateway operations
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 9m58s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 9m58s
Introduce IUserPaymentLock to serialize payment initiate and verify flows per user, preventing concurrent duplicate gateway requests across services. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+15
-2
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
@@ -17,20 +18,32 @@ public class ChargeDiscountWalletCommandHandler
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ChargeDiscountWalletCommandHandler> _logger;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public ChargeDiscountWalletCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IConfiguration configuration,
|
||||
ILogger<ChargeDiscountWalletCommandHandler> logger)
|
||||
ILogger<ChargeDiscountWalletCommandHandler> logger,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
|
||||
public async Task<PaymentInitiateResult> Handle(
|
||||
public Task<PaymentInitiateResult> Handle(
|
||||
ChargeDiscountWalletCommand request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Initiate(request.UserId),
|
||||
PaymentLockStrategy.FailFast,
|
||||
ct => HandleCore(request, ct),
|
||||
cancellationToken);
|
||||
|
||||
private async Task<PaymentInitiateResult> HandleCore(
|
||||
ChargeDiscountWalletCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
+15
-2
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
@@ -18,20 +19,32 @@ public class ChargeMagicWalletCommandHandler
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ChargeMagicWalletCommandHandler> _logger;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public ChargeMagicWalletCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IConfiguration configuration,
|
||||
ILogger<ChargeMagicWalletCommandHandler> logger)
|
||||
ILogger<ChargeMagicWalletCommandHandler> logger,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
|
||||
public async Task<PaymentInitiateResult> Handle(
|
||||
public Task<PaymentInitiateResult> Handle(
|
||||
ChargeMagicWalletCommand request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Initiate(request.UserId),
|
||||
PaymentLockStrategy.FailFast,
|
||||
ct => HandleCore(request, ct),
|
||||
cancellationToken);
|
||||
|
||||
private async Task<PaymentInitiateResult> HandleCore(
|
||||
ChargeMagicWalletCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
+15
-2
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
@@ -15,18 +16,30 @@ public class VerifyDiscountWalletChargeCommandHandler
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<VerifyDiscountWalletChargeCommandHandler> _logger;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public VerifyDiscountWalletChargeCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<VerifyDiscountWalletChargeCommandHandler> logger)
|
||||
ILogger<VerifyDiscountWalletChargeCommandHandler> logger,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(
|
||||
public Task<bool> Handle(
|
||||
VerifyDiscountWalletChargeCommand request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Verify(request.UserId, request.Authority),
|
||||
PaymentLockStrategy.WaitForRelease,
|
||||
ct => HandleCore(request, ct),
|
||||
cancellationToken);
|
||||
|
||||
private async Task<bool> HandleCore(
|
||||
VerifyDiscountWalletChargeCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
+26
-1
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
@@ -16,20 +17,44 @@ public class VerifyMagicWalletChargeCommandHandler
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<VerifyMagicWalletChargeCommandHandler> _logger;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public VerifyMagicWalletChargeCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<VerifyMagicWalletChargeCommandHandler> logger)
|
||||
ILogger<VerifyMagicWalletChargeCommandHandler> logger,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(
|
||||
VerifyMagicWalletChargeCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var paymentTx = await _context.PaymentTransactions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken);
|
||||
|
||||
if (paymentTx == null)
|
||||
throw new NotFoundException("تراکنش پرداخت یافت نشد");
|
||||
|
||||
if (!paymentTx.UserId.HasValue)
|
||||
throw new BadRequestException("شناسه کاربر در تراکنش پرداخت یافت نشد");
|
||||
|
||||
return await _paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Verify(paymentTx.UserId.Value, request.Authority),
|
||||
PaymentLockStrategy.WaitForRelease,
|
||||
ct => HandleCore(request, ct),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<bool> HandleCore(
|
||||
VerifyMagicWalletChargeCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user