fix: pass amount to ZarinPal VerifyPaymentAsync — fix verify failure (amount=0 bug)
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m38s

- 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 (تأیید تراکنش ناموفق).
This commit is contained in:
masoodafar-web
2026-02-27 21:17:50 +03:30
parent aee7b59fc1
commit 721661af0f
7 changed files with 59 additions and 22 deletions
@@ -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 با مبلغ را پیاده‌سازی کند");
}
/// <summary>
@@ -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)
@@ -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;
@@ -193,6 +193,16 @@ public class DayaPaymentService : IPaymentGatewayService
}
}
public Task<PaymentVerificationResult> VerifyPaymentAsync(
string refId,
string verificationToken,
decimal amountInToman,
CancellationToken cancellationToken = default)
{
// دایا: درگاه دایا مبلغ را در verify نیاز ندارد — از overload بدون مبلغ استفاده می‌شود
return VerifyPaymentAsync(refId, verificationToken, cancellationToken);
}
public async Task<PayoutResult> ProcessPayoutAsync(
PayoutRequest request,
CancellationToken cancellationToken = default)
@@ -74,6 +74,16 @@ public class MockPaymentGatewayService : IPaymentGatewayService
};
}
public Task<PaymentVerificationResult> VerifyPaymentAsync(
string refId,
string verificationToken,
decimal amountInToman,
CancellationToken cancellationToken = default)
{
// Mock: مبلغ تفاوتی نمی‌کند، از همان overload بدون مبلغ استفاده می‌شود
return VerifyPaymentAsync(refId, verificationToken, cancellationToken);
}
public async Task<PayoutResult> ProcessPayoutAsync(
PayoutRequest request,
CancellationToken cancellationToken = default)
@@ -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;
@@ -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;