From 721661af0f930508e5045220407f4a913c6c07cf Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 21:17:50 +0330 Subject: [PATCH] =?UTF-8?q?fix:=20pass=20amount=20to=20ZarinPal=20VerifyPa?= =?UTF-8?q?ymentAsync=20=E2=80=94=20fix=20verify=20failure=20(amount=3D0?= =?UTF-8?q?=20bug)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PackageService.CustomerVerifyPackagePurchase: look up PaymentTransaction.Amount and pass to 3-arg overload - TransactionsService.CustomerPaymentVerification: same fix - VerifyDiscountWalletChargeCommandHandler: look up amount from PaymentTransaction - VerifyPackagePurchaseCommandHandler: fix copy-paste bug (Authority as verificationToken) + add amount - IPaymentGatewayService: throw NotImplementedException in default 3-arg impl to prevent silent amount=0 - MockPaymentGatewayService & DayaPaymentService: add 3-arg overload for compatibility Root cause: ZarinPal requires the exact amount in verify request. The 2-arg overload was sending amount=0 which caused Code=-1 (تأیید تراکنش ناموفق). --- .../Interfaces/IPaymentGatewayService.cs | 6 ++++-- .../VerifyPackagePurchaseCommandHandler.cs | 12 ++++++++++-- ...erifyDiscountWalletChargeCommandHandler.cs | 19 +++++++++++-------- .../Services/Payment/DayaPaymentService.cs | 10 ++++++++++ .../Payment/MockPaymentGatewayService.cs | 10 ++++++++++ .../Services/PackageService.cs | 12 +++++++----- .../Services/TransactionsService.cs | 12 +++++++----- 7 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs index 31fa543..bb9a668 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs @@ -41,8 +41,10 @@ public interface IPaymentGatewayService decimal amountInToman, CancellationToken cancellationToken = default) { - // پیش‌فرض: درگاه‌هایی که Amount نمی‌خواهند، از overload بدون amount استفاده کنند - return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + // ⚠️ هشدار: این پیاده‌سازی پیش‌فرض مبلغ را نادیده می‌گیرد. + // درگاه‌هایی مثل زرین‌پال باید حتماً این متد را override کنند. + throw new NotImplementedException( + "درگاه پرداخت باید متد VerifyPaymentAsync با مبلغ را پیاده‌سازی کند"); } /// diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs index 867b694..0221abf 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs @@ -58,10 +58,18 @@ public class VerifyPackagePurchaseCommandHandler return true; } - // 3. Verify با درگاه بانکی + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken); + + var amountInToman = (decimal)(paymentTx?.Amount ?? order.Amount); + + // 3. Verify با درگاه بانکی (مبلغ به تومان — تبدیل به ریال در ZarinPalService) var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, - request.Authority // verificationToken - در بعضی درگاه‌ها همان Authority است + "OK", // verificationToken — Status از درگاه + amountInToman, + cancellationToken ); if (!verifyResult.IsSuccess) diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs index 400b8dd..a5694d5 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs @@ -49,16 +49,19 @@ public class VerifyDiscountWalletChargeCommandHandler throw new NotFoundException(nameof(User), request.UserId); } - // 2. Verify با درگاه - // Authority = کد مرجع تراکنش، Status = "OK" (تأیید کاربر از درگاه) - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, - "OK" // وقتی این handler فراخوانی میشه یعنی کاربر از درگاه برگشته — Status باید OK باشه - ); - - // آپدیت PaymentTransaction با نتیجه verify + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken); + + var amountInToman = (decimal)(paymentTx?.Amount ?? 0); + + // 2. Verify با درگاه (مبلغ به تومان — تبدیل به ریال در ZarinPalService) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, + "OK", // وقتی این handler فراخوانی میشه یعنی کاربر از درگاه برگشته — Status باید OK باشه + amountInToman, + cancellationToken + ); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess; diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs index d31972b..01effe4 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs @@ -193,6 +193,16 @@ public class DayaPaymentService : IPaymentGatewayService } } + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + // دایا: درگاه دایا مبلغ را در verify نیاز ندارد — از overload بدون مبلغ استفاده می‌شود + return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + } + public async Task ProcessPayoutAsync( PayoutRequest request, CancellationToken cancellationToken = default) diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs index 46e22df..d4cf080 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs @@ -74,6 +74,16 @@ public class MockPaymentGatewayService : IPaymentGatewayService }; } + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + // Mock: مبلغ تفاوتی نمی‌کند، از همان overload بدون مبلغ استفاده می‌شود + return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + } + public async Task ProcessPayoutAsync( PayoutRequest request, CancellationToken cancellationToken = default) diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 3ad2be6..9b2ffae 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -274,13 +274,15 @@ public class PackageService : PackageContract.PackageContractBase }; } - // Verify with payment gateway - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, request.Status, context.CancellationToken); - - // آپدیت PaymentTransaction + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + + var amountInToman = paymentTx?.Amount ?? purchase.Amount; + + // Verify with payment gateway (مبلغ به تومان — سرویس زرین‌پال خودش ×۱۰ می‌کنه) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess; diff --git a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs index f1043cf..dbc4966 100644 --- a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs +++ b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs @@ -235,13 +235,15 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase }; } - // Verify with gateway - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, request.Status, context.CancellationToken); - - // آپدیت PaymentTransaction + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + + var amountInToman = paymentTx?.Amount ?? transaction.Amount; + + // Verify with gateway (مبلغ به تومان — سرویس زرین‌پال خودش ×۱۰ می‌کنه) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess;