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:
@@ -1,5 +1,7 @@
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
|
||||
@@ -26,6 +28,7 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<DiscountOrderService> _logger;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public DiscountOrderService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
@@ -33,7 +36,8 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
ICurrentUserService currentUserService,
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<DiscountOrderService> logger)
|
||||
ILogger<DiscountOrderService> logger,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
@@ -41,6 +45,7 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
|
||||
private long GetCurrentUserId()
|
||||
@@ -58,20 +63,32 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
UserAddressId = request.UserAddressId,
|
||||
DiscountBalanceToUse = request.DiscountBalanceToUse
|
||||
};
|
||||
var result = await _sender.Send(command);
|
||||
|
||||
var response = new PlaceOrderResponse
|
||||
|
||||
try
|
||||
{
|
||||
Success = result.Success,
|
||||
Message = result.Message ?? string.Empty,
|
||||
OrderId = result.OrderId ?? 0,
|
||||
GatewayAmount = result.GatewayAmountRequired,
|
||||
};
|
||||
var result = await _sender.Send(command);
|
||||
|
||||
if (!string.IsNullOrEmpty(result.PaymentUrl))
|
||||
response.PaymentUrl = result.PaymentUrl;
|
||||
var response = new PlaceOrderResponse
|
||||
{
|
||||
Success = result.Success,
|
||||
Message = result.Message ?? string.Empty,
|
||||
OrderId = result.OrderId ?? 0,
|
||||
GatewayAmount = result.GatewayAmountRequired,
|
||||
};
|
||||
|
||||
return response;
|
||||
if (!string.IsNullOrEmpty(result.PaymentUrl))
|
||||
response.PaymentUrl = result.PaymentUrl;
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
return new PlaceOrderResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<CompleteOrderPaymentResponse> CompleteOrderPayment(CompleteOrderPaymentRequest request, ServerCallContext context)
|
||||
@@ -193,6 +210,48 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
|
||||
public override async Task<CustomerVerifyDiscountOrderPaymentResponse> CustomerVerifyDiscountOrderPayment(
|
||||
CustomerVerifyDiscountOrderPaymentRequest request, ServerCallContext context)
|
||||
{
|
||||
var orderOwner = await _context.DiscountOrders
|
||||
.Where(o => o.Id == request.OrderId)
|
||||
.Select(o => (long?)o.UserId)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (orderOwner is null or 0)
|
||||
{
|
||||
return new CustomerVerifyDiscountOrderPaymentResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "سفارش یافت نشد",
|
||||
OrderId = request.OrderId
|
||||
};
|
||||
}
|
||||
|
||||
var verifyKey = !string.IsNullOrEmpty(request.Authority)
|
||||
? request.Authority
|
||||
: request.OrderId.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
return await _paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Verify(orderOwner.Value, verifyKey),
|
||||
PaymentLockStrategy.WaitForRelease,
|
||||
ct => CustomerVerifyDiscountOrderPaymentCore(request, ct),
|
||||
context.CancellationToken);
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
return new CustomerVerifyDiscountOrderPaymentResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = ex.Message,
|
||||
OrderId = request.OrderId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CustomerVerifyDiscountOrderPaymentResponse> CustomerVerifyDiscountOrderPaymentCore(
|
||||
CustomerVerifyDiscountOrderPaymentRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"CustomerVerifyDiscountOrderPayment called: OrderId={OrderId}, Authority={Authority}, Status={Status}",
|
||||
@@ -203,7 +262,7 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
// پیدا کردن سفارش و تراکنش
|
||||
var order = await _context.DiscountOrders
|
||||
.Include(o => o.OrderDetails)
|
||||
.FirstOrDefaultAsync(o => o.Id == request.OrderId, context.CancellationToken);
|
||||
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
@@ -218,7 +277,7 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
|
||||
var transaction = order.TransactionId.HasValue
|
||||
? await _context.Transactions.FirstOrDefaultAsync(
|
||||
t => t.Id == order.TransactionId.Value, context.CancellationToken)
|
||||
t => t.Id == order.TransactionId.Value, cancellationToken)
|
||||
: null;
|
||||
|
||||
// تأیید پرداخت از درگاه
|
||||
@@ -233,14 +292,14 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
request.Authority,
|
||||
request.Status,
|
||||
order.GatewayAmountPaid,
|
||||
context.CancellationToken);
|
||||
cancellationToken);
|
||||
|
||||
paymentSuccess = verifyResult.IsSuccess;
|
||||
refId = verifyResult.TrackingCode ?? verifyResult.RefId;
|
||||
|
||||
// آپدیت PaymentTransaction با نتیجه verify
|
||||
var paymentTx = await _context.PaymentTransactions
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken);
|
||||
if (paymentTx != null)
|
||||
{
|
||||
paymentTx.PaymentStatus = verifyResult.IsSuccess;
|
||||
@@ -249,7 +308,7 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
paymentTx.CardPan = verifyResult.CardPan;
|
||||
paymentTx.CardHash = verifyResult.CardHash;
|
||||
paymentTx.RefId = verifyResult.TrackingCode;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
@@ -268,7 +327,7 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
TransactionId = transaction?.Id ?? 0,
|
||||
PaymentSuccess = paymentSuccess,
|
||||
RefId = refId
|
||||
}, context.CancellationToken);
|
||||
}, cancellationToken);
|
||||
|
||||
return new CustomerVerifyDiscountOrderPaymentResponse
|
||||
{
|
||||
|
||||
@@ -10,6 +10,8 @@ using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Domain.Entities.Payment;
|
||||
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||
using Grpc.Core;
|
||||
@@ -32,6 +34,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public PackageService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
@@ -39,7 +42,8 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IConfiguration configuration)
|
||||
IConfiguration configuration,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
@@ -47,6 +51,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
_currentUserService = currentUserService;
|
||||
_paymentGateway = paymentGateway;
|
||||
_configuration = configuration;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
public override async Task<CreateNewPackageResponse> CreateNewPackage(CreateNewPackageRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -138,12 +143,31 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
public override async Task<CustomerPurchasePackageResponse> CustomerPurchasePackage(CustomerPurchasePackageRequest request, ServerCallContext context)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return await _paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Initiate(userId),
|
||||
PaymentLockStrategy.FailFast,
|
||||
ct => CustomerPurchasePackageCore(request, userId, ct),
|
||||
context.CancellationToken);
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CustomerPurchasePackageResponse> CustomerPurchasePackageCore(
|
||||
CustomerPurchasePackageRequest request,
|
||||
long userId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Lookup package
|
||||
var package = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(p => p.Id == request.PackageId && !p.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (package == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "پکیج مورد نظر یافت نشد"));
|
||||
@@ -157,7 +181,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
Type = Domain.Enums.TransactionType.Buy
|
||||
};
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Create purchase record
|
||||
var purchaseMethod = request.PurchaseMethod == PurchaseMethodEnum.PurchaseMethodGateway
|
||||
@@ -174,14 +198,14 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
_context.UserPackagePurchases.Add(purchase);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Initiate payment with gateway
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new { u.Mobile })
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||
@@ -194,12 +218,12 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
Mobile = user?.Mobile ?? string.Empty,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
CallbackUrl = callbackUrl
|
||||
}, context.CancellationToken);
|
||||
}, cancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CustomerPurchasePackageResponse
|
||||
{
|
||||
@@ -229,7 +253,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
OrderId = purchase.Id.ToString()
|
||||
};
|
||||
_context.PaymentTransactions.Add(paymentTx);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CustomerPurchasePackageResponse
|
||||
{
|
||||
@@ -245,6 +269,36 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
{
|
||||
var ct = context.CancellationToken;
|
||||
|
||||
var purchaseUserId = await _context.UserPackagePurchases
|
||||
.Where(p => p.Id == request.OrderId && !p.IsDeleted)
|
||||
.Select(p => (long?)p.UserId)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (purchaseUserId is null or 0)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد"));
|
||||
|
||||
var verifyKey = !string.IsNullOrEmpty(request.Authority)
|
||||
? request.Authority
|
||||
: request.OrderId.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
return await _paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Verify(purchaseUserId.Value, verifyKey),
|
||||
PaymentLockStrategy.WaitForRelease,
|
||||
lockCt => CustomerVerifyPackagePurchaseCore(request, lockCt),
|
||||
ct);
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CustomerVerifyPackagePurchaseResponse> CustomerVerifyPackagePurchaseCore(
|
||||
CustomerVerifyPackagePurchaseRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var purchase = await _context.UserPackagePurchases
|
||||
.Include(p => p.Package)
|
||||
.Where(p => p.Id == request.OrderId && !p.IsDeleted)
|
||||
|
||||
@@ -10,6 +10,8 @@ using CMSMicroservice.Application.TransactionsCQ.Commands.RefundTransaction;
|
||||
using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction;
|
||||
using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Domain.Entities.Payment;
|
||||
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||
using Grpc.Core;
|
||||
@@ -28,6 +30,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
public TransactionsService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
@@ -35,7 +38,8 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IConfiguration configuration)
|
||||
IConfiguration configuration,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
@@ -43,6 +47,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
_currentUserService = currentUserService;
|
||||
_paymentGateway = paymentGateway;
|
||||
_configuration = configuration;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
public override async Task<CreateNewTransactionsResponse> CreateNewTransactions(CreateNewTransactionsRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -142,13 +147,32 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
public override async Task<CustomerPaymentRequestResponse> CustomerPaymentRequest(CustomerPaymentRequestRequest request, ServerCallContext context)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return await _paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Initiate(userId),
|
||||
PaymentLockStrategy.FailFast,
|
||||
ct => CustomerPaymentRequestCore(request, userId, ct),
|
||||
context.CancellationToken);
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CustomerPaymentRequestResponse> CustomerPaymentRequestCore(
|
||||
CustomerPaymentRequestRequest request,
|
||||
long userId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Get user mobile for payment gateway
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new { u.Mobile, u.Email })
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
// Create transaction record in DB
|
||||
var transaction = new CMSMicroservice.Domain.Entities.Transaction
|
||||
@@ -160,7 +184,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
};
|
||||
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||
@@ -174,13 +198,13 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
Mobile = request.Mobile ?? user?.Mobile ?? string.Empty,
|
||||
Description = request.Description ?? "پرداخت آنلاین",
|
||||
CallbackUrl = callbackUrl
|
||||
}, context.CancellationToken);
|
||||
}, cancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
// Update transaction status to failed
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
throw new RpcException(new Status(StatusCode.Internal,
|
||||
paymentResult.ErrorMessage ?? "خطا در ارتباط با درگاه پرداخت"));
|
||||
@@ -206,7 +230,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
_context.PaymentTransactions.Add(paymentTx);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CustomerPaymentRequestResponse
|
||||
{
|
||||
@@ -215,11 +239,35 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
}
|
||||
|
||||
public override async Task<CustomerPaymentVerificationResponse> CustomerPaymentVerification(CustomerPaymentVerificationRequest request, ServerCallContext context)
|
||||
{
|
||||
var ct = context.CancellationToken;
|
||||
var userId = GetCurrentUserId();
|
||||
var verifyKey = !string.IsNullOrEmpty(request.Authority)
|
||||
? request.Authority
|
||||
: userId.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
return await _paymentLock.ExecuteAsync(
|
||||
PaymentLockScopes.Verify(userId, verifyKey),
|
||||
PaymentLockStrategy.WaitForRelease,
|
||||
lockCt => CustomerPaymentVerificationCore(request, lockCt),
|
||||
ct);
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CustomerPaymentVerificationResponse> CustomerPaymentVerificationCore(
|
||||
CustomerPaymentVerificationRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Find the transaction by authority/refId
|
||||
var transaction = await _context.Transactions
|
||||
.Where(t => t.RefId == request.Authority && !t.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (transaction == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "تراکنش یافت نشد"));
|
||||
@@ -228,7 +276,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
if (request.Status != "OK")
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CustomerPaymentVerificationResponse
|
||||
{
|
||||
@@ -241,13 +289,13 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
|
||||
// واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify
|
||||
var paymentTx = await _context.PaymentTransactions
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken);
|
||||
|
||||
var amountInToman = paymentTx?.Amount ?? transaction.Amount;
|
||||
|
||||
// Verify with gateway (مبلغ به تومان — سرویس زرینپال خودش ×۱۰ میکنه)
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken);
|
||||
request.Authority, request.Status, (decimal)amountInToman, cancellationToken);
|
||||
if (paymentTx != null)
|
||||
{
|
||||
paymentTx.PaymentStatus = verifyResult.IsSuccess;
|
||||
@@ -269,7 +317,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CustomerPaymentVerificationResponse
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using Grpc.Core;
|
||||
@@ -184,18 +185,29 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var result = await _sender.Send(new ChargeMagicWalletCommand
|
||||
try
|
||||
{
|
||||
UserId = userId,
|
||||
Amount = request.Amount
|
||||
}, context.CancellationToken);
|
||||
var result = await _sender.Send(new ChargeMagicWalletCommand
|
||||
{
|
||||
UserId = userId,
|
||||
Amount = request.Amount
|
||||
}, context.CancellationToken);
|
||||
|
||||
return new InitiateMagicChargeResponse
|
||||
return new InitiateMagicChargeResponse
|
||||
{
|
||||
IsSuccess = result.IsSuccess,
|
||||
GatewayUrl = result.GatewayUrl ?? "",
|
||||
ErrorMessage = result.ErrorMessage ?? ""
|
||||
};
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
IsSuccess = result.IsSuccess,
|
||||
GatewayUrl = result.GatewayUrl ?? "",
|
||||
ErrorMessage = result.ErrorMessage ?? ""
|
||||
};
|
||||
return new InitiateMagicChargeResponse
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============= Discount Wallet Methods =============
|
||||
@@ -205,18 +217,29 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var result = await _sender.Send(new ChargeDiscountWalletCommand
|
||||
try
|
||||
{
|
||||
UserId = userId,
|
||||
Amount = request.Amount
|
||||
}, context.CancellationToken);
|
||||
var result = await _sender.Send(new ChargeDiscountWalletCommand
|
||||
{
|
||||
UserId = userId,
|
||||
Amount = request.Amount
|
||||
}, context.CancellationToken);
|
||||
|
||||
return new InitiateDiscountChargeResponse
|
||||
return new InitiateDiscountChargeResponse
|
||||
{
|
||||
IsSuccess = result.IsSuccess,
|
||||
GatewayUrl = result.GatewayUrl ?? "",
|
||||
ErrorMessage = result.ErrorMessage ?? ""
|
||||
};
|
||||
}
|
||||
catch (PaymentInProgressException ex)
|
||||
{
|
||||
IsSuccess = result.IsSuccess,
|
||||
GatewayUrl = result.GatewayUrl ?? "",
|
||||
ErrorMessage = result.ErrorMessage ?? ""
|
||||
};
|
||||
return new InitiateDiscountChargeResponse
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============= Wallet Verify Methods =============
|
||||
|
||||
Reference in New Issue
Block a user