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:
hamed
2026-07-02 20:33:35 +03:30
co-authored by Claude Opus 4.8
parent 27b2d66afa
commit 10be1728ff
2 changed files with 8 additions and 5 deletions
+2 -2
View File
@@ -28,7 +28,7 @@
[درگاه بانک / شاپرک] پرداخت کاربر
│ (۳) بازگشت به callbackUrl بک‌اند
[PaymentController::callback] (عمومی، محدود به IP شاپرک مگر تست)
[PaymentController::callback] (عمومی؛ ملت/سپ ریدایرکت مرورگر → بدون IP-check، امنیت با tamper+verify)
│ gateway->verify()؛ بررسی مبلغ؛ جلوگیری از replay (reference_id یکتا)؛ ست وضعیت
│ post-action: confirm نوبت / فعال‌سازی اشتراک / شارژ کیف‌پول + کمیسیون + پیامک
│ (۴) 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` (دامنهٔ ثبت‌شده نزد ملت/شاپرک) باشد.
**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
| Param | Type | Description |
+6 -3
View File
@@ -285,9 +285,12 @@ class PaymentController extends BaseController
public function callback(string $gateway, Request $request): \Symfony\Component\HttpFoundation\Response
{
$clientIp = $request->getClientIp() ?? '';
// در حالت تست یا sandbox ملت، callback از IPی خارج از رنج شاپرک می‌آید؛ IP-check رد می‌شود.
$bypassIp = $this->gateways->isTestMode()
|| ($gateway === 'mellat' && $this->gateways->isMellatSandbox());
// درگاه‌های ملت و سپ نتیجه را با ریدایرکتِ مرورگرِ کاربر (POST/GET) برمی‌گردانند،
// نه server-to-server؛ پس IP دریافتی، IPِ کاربر است و allowlist شاپرک اعمال نمی‌شود
// (در غیر این صورت هر callback واقعی — از جمله «لغو» — رد می‌شد). امنیت از طریق چک
// ضد-دستکاری (RefId/SaleOrderId) و verify سمت بانک در PaymentManager تأمین می‌شود.
$browserRedirectGateways = ['mellat', 'sep'];
$bypassIp = $this->gateways->isTestMode() || in_array($gateway, $browserRedirectGateways, true);
if (!$bypassIp && !$this->isAllowedCallbackIp($clientIp)) {
return $this->renderPaymentResult('forbidden');
}