fix(payment): skip Shaparak IP allowlist for browser-redirect gateways (mellat/sep)
Mellat/SEP return the result via a user-browser redirect (POST/GET), so the received IP is the user's, not Shaparak's. The IP allowlist therefore rejected every real callback — including user cancel — with 'forbidden'. Security is provided by the tamper check (RefId/SaleOrderId) and server-side verify. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -28,7 +28,7 @@
|
|||||||
[درگاه بانک / شاپرک] پرداخت کاربر
|
[درگاه بانک / شاپرک] پرداخت کاربر
|
||||||
│ (۳) بازگشت به callbackUrl بکاند
|
│ (۳) بازگشت به callbackUrl بکاند
|
||||||
▼
|
▼
|
||||||
[PaymentController::callback] (عمومی، محدود به IP شاپرک مگر تست)
|
[PaymentController::callback] (عمومی؛ ملت/سپ ریدایرکت مرورگر → بدون IP-check، امنیت با tamper+verify)
|
||||||
│ gateway->verify()؛ بررسی مبلغ؛ جلوگیری از replay (reference_id یکتا)؛ ست وضعیت
|
│ gateway->verify()؛ بررسی مبلغ؛ جلوگیری از replay (reference_id یکتا)؛ ست وضعیت
|
||||||
│ post-action: confirm نوبت / فعالسازی اشتراک / شارژ کیفپول + کمیسیون + پیامک
|
│ post-action: confirm نوبت / فعالسازی اشتراک / شارژ کیفپول + کمیسیون + پیامک
|
||||||
│ (۴) RedirectResponse → frontend_address?payment_uuid=..&status=.. (همان دامنهٔ مبدأ)
|
│ (۴) RedirectResponse → frontend_address?payment_uuid=..&status=.. (همان دامنهٔ مبدأ)
|
||||||
@@ -271,7 +271,7 @@ Payment gateway callback. Called by the bank after user completes (or cancels) p
|
|||||||
|
|
||||||
**نکته IPG ملت:** طبق راهنمای درگاه ملت، `callBackUrl` باید روی **دامنهٔ ثبتشدهٔ پذیرنده** باشد و **IP مجاز نیست** (در غیر این صورت کد پاسخ `62` — «مسیر back call در دامنهٔ ثبتشده نیست»). بنابراین `APP_BASE_URL` در پروداکشن باید دقیقاً `https://clinic-pro.ir` (دامنهٔ ثبتشده نزد ملت/شاپرک) باشد.
|
**نکته IPG ملت:** طبق راهنمای درگاه ملت، `callBackUrl` باید روی **دامنهٔ ثبتشدهٔ پذیرنده** باشد و **IP مجاز نیست** (در غیر این صورت کد پاسخ `62` — «مسیر back call در دامنهٔ ثبتشده نیست»). بنابراین `APP_BASE_URL` در پروداکشن باید دقیقاً `https://clinic-pro.ir` (دامنهٔ ثبتشده نزد ملت/شاپرک) باشد.
|
||||||
|
|
||||||
**Permission:** `PUBLIC` — called by the gateway, not the user (محدود به IPهای شبکهٔ شاپرک `isAllowedCallbackIp`؛ در `test_mode` بدون محدودیت IP)
|
**Permission:** `PUBLIC`. **نکتهٔ مهم:** درگاههای **ملت و سپ** نتیجه را با **ریدایرکتِ مرورگرِ کاربر** (POST/GET) برمیگردانند، نه server-to-server؛ پس IP دریافتی، IPِ کاربر است و **allowlist شاپرک اعمال نمیشود** (برای `gateway ∈ {mellat, sep}` و نیز `test_mode`). در غیر این صورت هر callback واقعی — از جمله «لغو» توسط کاربر — با «دسترسی غیرمجاز» رد میشد. امنیت از طریق **چک ضد-دستکاری** (`RefId==gateway_token`، `SaleOrderId==payment.id`) و **verify سمت بانک** در `PaymentManager` تأمین میشود. `isAllowedCallbackIp` فقط برای درگاههای آیندهٔ server-to-server معنی دارد.
|
||||||
|
|
||||||
### Path Parameters
|
### Path Parameters
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
|
|||||||
@@ -285,9 +285,12 @@ class PaymentController extends BaseController
|
|||||||
public function callback(string $gateway, Request $request): \Symfony\Component\HttpFoundation\Response
|
public function callback(string $gateway, Request $request): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
$clientIp = $request->getClientIp() ?? '';
|
$clientIp = $request->getClientIp() ?? '';
|
||||||
// در حالت تست یا sandbox ملت، callback از IPی خارج از رنج شاپرک میآید؛ IP-check رد میشود.
|
// درگاههای ملت و سپ نتیجه را با ریدایرکتِ مرورگرِ کاربر (POST/GET) برمیگردانند،
|
||||||
$bypassIp = $this->gateways->isTestMode()
|
// نه server-to-server؛ پس IP دریافتی، IPِ کاربر است و allowlist شاپرک اعمال نمیشود
|
||||||
|| ($gateway === 'mellat' && $this->gateways->isMellatSandbox());
|
// (در غیر این صورت هر callback واقعی — از جمله «لغو» — رد میشد). امنیت از طریق چک
|
||||||
|
// ضد-دستکاری (RefId/SaleOrderId) و verify سمت بانک در PaymentManager تأمین میشود.
|
||||||
|
$browserRedirectGateways = ['mellat', 'sep'];
|
||||||
|
$bypassIp = $this->gateways->isTestMode() || in_array($gateway, $browserRedirectGateways, true);
|
||||||
if (!$bypassIp && !$this->isAllowedCallbackIp($clientIp)) {
|
if (!$bypassIp && !$this->isAllowedCallbackIp($clientIp)) {
|
||||||
return $this->renderPaymentResult('forbidden');
|
return $this->renderPaymentResult('forbidden');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user