feat(payment): canceled status, manageable origin allowlist, CORS subdomains
Unify and harden the payment flow (same API for the main site and all consumer sites; per-client difference is only frontend_address). - Payment gains STATUS_CANCELED. Gateways distinguish user-cancel from failure (Mellat ResCode=17, SEP CanceledByUser, mock cancel=1) via a new PaymentVerifyResult::canceled flag; callback sets canceled vs failed and skips the circuit-breaker on cancel. - Expiry job now cancels the pending payment when a booking lapses (AppointmentExpiryService + PaymentRepository::findPendingByAppointment). - frontend_address allowlist is read from the payment_allowed_frontend_hosts site setting (manageable via PATCH /api/v1/admin/settings), falling back to the ALLOWED_FRONTEND_HOSTS env var — so a new consumer site needs no code change. - .env: broaden CORS_ALLOW_ORIGIN to city subdomains (*.localhost / *.clinic-pro.ddev.site) and add yazd-nobat.localhost to ALLOWED_FRONTEND_HOSTS. - Update docs/api/payment.md and docs/api/admin.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,10 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
$refId = $callbackData['RefId'] ?? '';
|
||||
$resCode = $callbackData['ResCode'] ?? '';
|
||||
|
||||
if ($resCode === '17') {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'پرداخت توسط کاربر لغو شد', canceled: true);
|
||||
}
|
||||
|
||||
if ($resCode !== '0') {
|
||||
return new PaymentVerifyResult(false, errorMessage: "Payment failed: $resCode");
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ class MockGateway implements PaymentGatewayInterface
|
||||
return new PaymentVerifyResult(false, errorMessage: 'mock callback مجاز نیست');
|
||||
}
|
||||
|
||||
if (($callbackData['cancel'] ?? '0') === '1' || $resCode === '17') {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'پرداخت توسط کاربر لغو شد', canceled: true);
|
||||
}
|
||||
|
||||
$refId = $callbackData['RefId'] ?? $callbackData['order_id'] ?? 'MOCK-REF';
|
||||
return new PaymentVerifyResult(true, referenceId: $refId);
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ final class PaymentVerifyResult
|
||||
public readonly string $referenceId = '',
|
||||
public readonly string $errorMessage = '',
|
||||
public readonly int $amountRials = 0,
|
||||
public readonly bool $canceled = false,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ class SepGateway implements PaymentGatewayInterface
|
||||
public function verify(array $callbackData): PaymentVerifyResult
|
||||
{
|
||||
$state = $callbackData['State'] ?? '';
|
||||
if (strtolower($state) === 'canceledbyuser') {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'پرداخت توسط کاربر لغو شد', canceled: true);
|
||||
}
|
||||
if (strtolower($state) !== 'ok') {
|
||||
return new PaymentVerifyResult(false, errorMessage: "Payment state: $state");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user