178 lines
7.8 KiB
Markdown
178 lines
7.8 KiB
Markdown
# نمایش درگاه تست در رابط کاربری هنگام فعال بودن حالت آزمایشی
|
||
|
||
## زمینه
|
||
|
||
سیستم پرداخت یک حالت آزمایشی (`payment_test_mode`) دارد که در `SiteConfig` ذخیره میشود. وقتی این حالت فعال است، backend در `resolveGateway()` (هر دو `PaymentController` و `SmsWalletController`) بجای درگاه واقعی، درگاه `MockGateway` را بهکار میبرد و پول واقعی کسر نمیشود.
|
||
|
||
**مشکل:** Frontend هیچ اطلاعی از حالت تست ندارد. کاربر در `SmsWalletPage` و `SubscriptionPage` انتخاب درگاه میبیند (ملت / سپ)، حتی وقتی backend همه را به Mock هدایت میکند. این گیجکننده است.
|
||
|
||
**هدف:** وقتی `payment_test_mode = '1'` است:
|
||
- انتخابکننده درگاه واقعی پنهان شود
|
||
- بجای آن یک نوار زرد/نارنجی نمایش داده شود: «درگاه آزمایشی فعال است — پول واقعی کسر نخواهد شد»
|
||
|
||
---
|
||
|
||
## مشکل / هدف
|
||
|
||
دو صفحه frontend نیاز به تغییر دارند:
|
||
1. `SmsWalletPage.tsx` — modal شارژ کیف پول پیامک
|
||
2. `SubscriptionPage.tsx` — modal خرید اشتراک
|
||
|
||
همچنین یک endpoint عمومی (برای همه کاربران احراز هویت شده) باید ساخته شود چون `GET /api/v1/admin/settings` فقط برای `ROLE_ADMIN` است.
|
||
|
||
---
|
||
|
||
## فایلهای مرتبط
|
||
|
||
| فایل | نقش |
|
||
|------|-----|
|
||
| `src/Config/Controller/SiteConfigController.php` | endpoint تنظیمات — فعلاً فقط ROLE_ADMIN |
|
||
| `src/Config/Repository/SiteConfigRepository.php` | خواندن `payment_test_mode` |
|
||
| `src/Payment/Controller/PaymentController.php` | `resolveGateway()` — backend قبلاً درست است |
|
||
| `assets/admin/pages/SmsWalletPage.tsx` | modal شارژ — نمایشدهنده انتخابکننده درگاه |
|
||
| `assets/admin/pages/SubscriptionPage.tsx` | modal اشتراک — نمایشدهنده انتخابکننده درگاه |
|
||
|
||
---
|
||
|
||
## وضعیت فعلی
|
||
|
||
### Backend — endpoint تنظیمات فقط ادمین است
|
||
```php
|
||
// src/Config/Controller/SiteConfigController.php
|
||
#[IsGranted('ROLE_ADMIN')]
|
||
class SiteConfigController extends BaseController
|
||
{
|
||
#[Route('/api/v1/admin/settings', methods: ['GET'])]
|
||
public function get(): JsonResponse
|
||
{
|
||
return $this->success($this->configRepo->getAll());
|
||
}
|
||
}
|
||
```
|
||
|
||
### Frontend — SmsWalletPage modal (خط ۳۴۸–۳۷۳)
|
||
```tsx
|
||
// همیشه دو گزینه mellat و sep نمایش میدهد
|
||
{(['mellat', 'sep'] as const).map((gw) => (
|
||
<div key={gw} onClick={() => setGateway(gw)} ...>
|
||
<div>{gw === 'mellat' ? 'ملت' : 'سپ'}</div>
|
||
...
|
||
</div>
|
||
))}
|
||
```
|
||
|
||
### Frontend — SubscriptionPage modal (خط ۳۰۷–۳۲۷)
|
||
```tsx
|
||
// همیشه دو گزینه mellat و sep نمایش میدهد
|
||
{(['mellat', 'sep'] as const).map((gw) => (
|
||
<button key={gw} onClick={() => setSelectedGateway(gw)} ...>
|
||
<CreditCardIcon ... />
|
||
{GATEWAY_LABELS[gw]}
|
||
</button>
|
||
))}
|
||
```
|
||
|
||
---
|
||
|
||
## وظایف
|
||
|
||
### ۱. Backend — endpoint عمومی `GET /api/v1/payment/config`
|
||
|
||
در `src/Payment/Controller/PaymentController.php` یک متد جدید اضافه کن:
|
||
|
||
```php
|
||
#[Route('/api/v1/payment/config', methods: ['GET'])]
|
||
public function config(): JsonResponse
|
||
{
|
||
return $this->success([
|
||
'test_mode' => $this->configRepo->get('payment_test_mode') === '1',
|
||
]);
|
||
}
|
||
```
|
||
|
||
- **Permission:** `IS_AUTHENTICATED_FULLY` (کلاس `PaymentController` قبلاً این را دارد)
|
||
- **مهم:** هرگز credential های درگاه (terminal_id، password، ...) را expose نکن
|
||
- پاسخ فقط یک boolean کافی است
|
||
- مستندات را در `docs/api/payment.md` اضافه کن
|
||
|
||
### ۲. Frontend — hook برای خواندن حالت تست
|
||
|
||
در `assets/admin/pages/SmsWalletPage.tsx` و `assets/admin/pages/SubscriptionPage.tsx`، یک `useQuery` برای خواندن config اضافه کن:
|
||
|
||
```tsx
|
||
const { data: paymentConfigData } = useQuery<ApiResponse<{ test_mode: boolean }>>({
|
||
queryKey: ['payment-config'],
|
||
queryFn: () => api.get('/api/v1/payment/config'),
|
||
staleTime: 5 * 60 * 1000, // 5 دقیقه cache
|
||
});
|
||
|
||
const isTestMode = paymentConfigData?.data?.test_mode ?? false;
|
||
```
|
||
|
||
### ۳. Frontend — SmsWalletPage: پنهان کردن انتخابکننده درگاه در حالت تست
|
||
|
||
در modal شارژ (`SmsWalletPage.tsx`)، قسمت «انتخاب درگاه پرداخت» را با شرط جایگزین کن:
|
||
|
||
```tsx
|
||
{isTestMode ? (
|
||
<div style={{
|
||
background: '#fef9c3',
|
||
border: '1px solid #fbbf24',
|
||
borderRadius: 10,
|
||
padding: '12px 16px',
|
||
display: 'flex', alignItems: 'center', gap: 10,
|
||
}}>
|
||
<span style={{ fontSize: 18 }}>⚠️</span>
|
||
<div>
|
||
<div style={{ fontWeight: 700, fontSize: 13.5, color: '#92400e' }}>
|
||
درگاه آزمایشی فعال است
|
||
</div>
|
||
<div style={{ fontSize: 12, color: '#b45309', marginTop: 2 }}>
|
||
پول واقعی کسر نخواهد شد — این تراکنش آزمایشی است
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
// کد فعلی انتخاب درگاه (mellat / sep)
|
||
<div>...</div>
|
||
)}
|
||
```
|
||
|
||
همچنین در mutation، وقتی `isTestMode` است، مقدار `gateway` را به `'mock'` تنظیم کن (یا هر رشتهای — backend به هر حال Mock را استفاده میکند، ولی باید یک مقدار ارسال کنیم).
|
||
|
||
### ۴. Frontend — SubscriptionPage: پنهان کردن انتخابکننده درگاه در حالت تست
|
||
|
||
همان pattern را در `SubscriptionPage.tsx` در قسمت «انتخاب درگاه پرداخت» داخل modal اعمال کن.
|
||
|
||
همچنین در دکمه پرداخت زیر modal، متن را در حالت تست تغییر بده:
|
||
```tsx
|
||
{isTestMode
|
||
? `پرداخت آزمایشی ${formatRial(purchaseTarget.period.price_rials)}`
|
||
: `پرداخت ${formatRial(purchaseTarget.period.price_rials)}`
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## نکات مهم
|
||
|
||
- **Backend قبلاً درست است:** `resolveGateway()` در هر دو `PaymentController` و `SmsWalletController` حالت تست را بررسی میکند. فقط UI نیاز به تغییر دارد.
|
||
- **Security:** endpoint جدید فقط یک boolean برمیگرداند — هیچ اطلاعات حساسی expose نمیشود.
|
||
- **قرارداد API:** پاسخ باید `{ success: true, data: { test_mode: boolean } }` باشد. در frontend: `data?.data?.test_mode`.
|
||
- **در حالت تست:** state انتخاب درگاه (`gateway` / `selectedGateway`) بیتأثیر است چون backend آن را نادیده میگیرد — ولی باید یک مقدار ارسال شود (از مقدار default `'mellat'` استفاده کن).
|
||
- **Stale time:** این تنظیم خیلی نادر تغییر میکند، 5 دقیقه staleTime کافی است.
|
||
- **Routing ترتیب:** مطمئن شو route `/api/v1/payment/config` قبل از `/api/v1/payment/{uuid}` تعریف شده تا با UUID conflict نکند. در Symfony اگر methodها مختلف است مشکلی نیست ولی اگر هر دو GET هستند، route خاصتر باید اول باشد.
|
||
|
||
---
|
||
|
||
## ترتیب اجرا
|
||
|
||
1. Backend: اضافه کردن `config()` به `PaymentController`
|
||
2. تست route: `ddev exec php bin/console debug:router | grep payment`
|
||
3. Cache: `ddev exec php bin/console cache:clear`
|
||
4. Frontend: `SmsWalletPage.tsx` — اضافه کردن query و شرط حالت تست
|
||
5. Frontend: `SubscriptionPage.tsx` — همان تغییر
|
||
6. Build: `ddev exec yarn dev`
|
||
7. TypeScript check: `ddev exec npx tsc --noEmit --project tsconfig.json 2>&1 | head -20`
|
||
8. مستندات: `docs/api/payment.md`
|