fix(security): verify gateway-confirmed amount in payment callback (C1)
The callback marked an order success on any verify-ok result without comparing the gateway-settled amount to the amount charged. SEP returns AffectiveAmount; an underpayment or a replayed RefNum from a cheaper order would confirm the expensive order. Now reject (status=failed, no activation) when the gateway reports an amount that mismatches the stored amount_rials. Gateways that don't report a settled amount (Mellat binds it server-side) skip the check. MockGateway now echoes mock_amount so the guard is exercisable in tests. Regression: tests/Payment/PaymentCallbackAmountTest (underpayment rejected, matching amount succeeds). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -263,7 +263,7 @@ class PaymentController extends BaseController
|
||||
$result = $gw?->verify($callbackData) ?? null;
|
||||
|
||||
if ($result === null || !$result->success) {
|
||||
$canceled = $result?->canceled ?? false;
|
||||
$canceled = $result !== null && $result->canceled;
|
||||
$payment->setStatus($canceled ? Payment::STATUS_CANCELED : Payment::STATUS_FAILED);
|
||||
$this->paymentRepo->save($payment);
|
||||
if (!$canceled) {
|
||||
@@ -274,6 +274,18 @@ class PaymentController extends BaseController
|
||||
}
|
||||
|
||||
$this->circuitBreaker->recordSuccess($gateway);
|
||||
|
||||
// Gateway-confirmed amount must match the amount we charged. Gateways that
|
||||
// report the settled amount (SEP: AffectiveAmount) let us catch an
|
||||
// underpayment / RefNum-replay; gateways that don't report it bind the
|
||||
// amount server-side to the original request, so amountRials is 0 here.
|
||||
if ($result->amountRials > 0 && $result->amountRials !== $payment->getAmountRials()) {
|
||||
$payment->setStatus(Payment::STATUS_FAILED);
|
||||
$this->paymentRepo->save($payment);
|
||||
|
||||
return $this->redirectToFrontend($payment, false);
|
||||
}
|
||||
|
||||
$payment->setStatus(Payment::STATUS_SUCCESS);
|
||||
$payment->setReferenceId($result->referenceId);
|
||||
$this->paymentRepo->save($payment);
|
||||
|
||||
Reference in New Issue
Block a user