Feat add payment with gateway of montify
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
# شارژ آنلاین کیف پول (Wallet Online Payment)
|
||||
|
||||
مستند پیادهسازی داخلی پروژه برای شارژ کیف پول از طریق درگاه.
|
||||
برای پروتکل Montify (HMAC، initiate، webhook) به سند جداگانه مراجعه کنید:
|
||||
|
||||
→ [`EXTERNAL_PAYMENT_PARTNER.md`](./EXTERNAL_PAYMENT_PARTNER.md)
|
||||
|
||||
---
|
||||
|
||||
## ۱. هدف
|
||||
|
||||
- کاربر بتواند علاوه بر واریز دستی (کارت/شبا)، از **پرداخت آنلاین** کیف را شارژ کند.
|
||||
- تمام credentialها و انتخاب درگاه **فقط سمت سرور** بماند.
|
||||
- معماری طوری باشد که بعداً چند نوع درگاه (Montify پارتنر، درگاه مستقیم، …) اضافه شود.
|
||||
|
||||
---
|
||||
|
||||
## ۲. جریان کلی (از دید کاربر و سیستم)
|
||||
|
||||
```
|
||||
اپ کاربر (khaneghi_user)
|
||||
│ مبلغ را وارد میکند → مدال راهنمای شارژ
|
||||
│ GET /customer/wallet/payment/options → { online_enabled }
|
||||
│ POST /customer/wallet/payment/initiate → { payment_url }
|
||||
▼
|
||||
سرور ما
|
||||
│ Withdraw (sts=1) میسازد
|
||||
│ درگاه فعال را از Settings انتخاب میکند (مثلاً montify)
|
||||
│ با HMAC به Montify: POST /external-payment/initiate
|
||||
▼
|
||||
Montify → payment_url → درگاه بانکی
|
||||
│
|
||||
├─► Browser return → GET /customer/wallet/payment/return/{provider}
|
||||
└─► Webhook → POST /customer/wallet/payment/webhook/{provider}
|
||||
│
|
||||
└─► شارژ idempotent کیف (sts=2 + Transaction)
|
||||
```
|
||||
|
||||
**منبع حقیقت شارژ:** webhook (یا verify امن سمت سرور). فقط به query برگشت مرورگر اعتماد نکنید؛ امضا را verify کنید و مبلغ/`reference_id` را با `Withdraw` تطبیق دهید.
|
||||
|
||||
---
|
||||
|
||||
## ۳. امنیت — قوانین اجباری
|
||||
|
||||
| درست | غلط |
|
||||
|------|-----|
|
||||
| `client_id` / `client_secret` / `base_url` فقط در Settings سرور | گذاشتن secret در JS فرانت |
|
||||
| فیلتر `api/get_settings` برای کلیدهای محرمانه | برگرداندن `external_payment_*` به اپ کاربر |
|
||||
| اپ فقط `online_enabled` و بعد `payment_url` میبیند | خواندن provider یا credential از settings در کلاینت |
|
||||
| initiate فقط با JWT مشتری | ساخت لینک پرداخت در مرورگر |
|
||||
| verify امضای return و webhook | اعتماد کور به query بدون signature |
|
||||
| تطبیق `amount` با `Withdraw.cash` | قبول هر مبلغ برگشتی |
|
||||
|
||||
### ۳.۱. چه چیزی به اپ کاربر میرسد؟
|
||||
|
||||
- `GET /customer/wallet/payment/options` → فقط `{ "online_enabled": true|false }`
|
||||
- `POST /customer/wallet/payment/initiate` → فقط `{ "payment_url": "https://..." }`
|
||||
- **هیچ** `client_id`، `client_secret`، `base_url`، نام درگاه داخلی، یا `wallet_payment_provider` در Network تب اپ نباید دیده شود.
|
||||
|
||||
### ۳.۲. فیلتر `get_settings`
|
||||
|
||||
در `App\Models\Setting::isSecretOrServerOnlyKey()` و `SettingsController::authorizeSettingsPayload()`:
|
||||
|
||||
- درخواست عمومی (`api/get_settings` بدون auth ادمین) → کلیدهای محرمانه حذف میشوند.
|
||||
- پنل ادمین (`admin/get_settings` با `auth:user`) → همه کلیدها قابل مشاهده/ویرایش طبق `can_edit`.
|
||||
|
||||
کلیدهای مرتبط با پرداخت که به اپ عمومی نمیروند شامل:
|
||||
|
||||
- همه `external_payment_*`
|
||||
- `wallet_payment_provider`
|
||||
- همه `payping_*`
|
||||
- الگوهای `*_secret` / `*_token` / `*_password` و لیست ثابت توکنها
|
||||
|
||||
---
|
||||
|
||||
## ۴. Settings (جدول `settings`)
|
||||
|
||||
پیکربندی فقط از پنل ادمین / دیپلوی. گروه UI: **درگاه خارجی (Montify)**.
|
||||
|
||||
| کلید | نوع | توضیح |
|
||||
|------|-----|--------|
|
||||
| `wallet_payment_provider` | select | `none` = خاموش، `montify` = پارتنر Montify. برای درگاههای بعدی همین فیلد گسترش مییابد. |
|
||||
| `external_payment_enabled` | switch | سازگاری قدیمی؛ اگر `true` باشد و provider خالی باشد، مثل `montify` رفتار میکند. ترجیحاً از `wallet_payment_provider` استفاده کنید. |
|
||||
| `external_payment_base_url` | text | BASE_URL API Montify (بدون اسلش انتهایی) |
|
||||
| `external_payment_client_id` | text | `client_id` پارتنر |
|
||||
| `external_payment_client_secret` | text | `client_secret` — محرمانه |
|
||||
| `external_payment_gateway` | select | درگاه داخل Montify: `zarinpall` / `zibal` / `payping` / `sepal` |
|
||||
| `external_payment_return_url` | text | اختیاری؛ خالی = پیشفرض سرور |
|
||||
| `external_payment_webhook_url` | text | اختیاری؛ خالی = پیشفرض سرور |
|
||||
| `min_deposit` | price | حداقل مبلغ واریز (مشترک با واریز دستی) |
|
||||
|
||||
### مقادیر پیشفرض URL وقتی فیلد خالی است
|
||||
|
||||
- Return: `{APP}/customer/wallet/payment/return/montify`
|
||||
- Webhook: `{APP}/customer/wallet/payment/webhook/montify`
|
||||
|
||||
این آدرسها باید در whitelist پارتنر Montify ثبت شوند.
|
||||
|
||||
### Migrationها
|
||||
|
||||
- `2026_07_24_000001_add_external_payment_settings.php`
|
||||
- `2026_07_24_000002_add_wallet_payment_provider_setting.php`
|
||||
|
||||
```bash
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۵. APIهای سرور ما
|
||||
|
||||
پیشوند: `/customer`
|
||||
Auth مشتری: `auth:customer` + `customer.active` (بهجز return/webhook)
|
||||
|
||||
| متد | مسیر | Auth | نقش |
|
||||
|-----|------|------|-----|
|
||||
| GET | `/wallet/payment/options` | مشتری | `{ online_enabled }` |
|
||||
| POST | `/wallet/payment/initiate` | مشتری | Body: `{ value }` (ریال) → `{ payment_url }` |
|
||||
| GET | `/wallet/payment/return/{provider}` | عمومی | برگشت مرورگر؛ verify + گزارش |
|
||||
| POST | `/wallet/payment/webhook/{provider}` | عمومی (HMAC) | شارژ کیف |
|
||||
|
||||
### سازگاری قدیمی
|
||||
|
||||
| مسیر قدیمی | معادل |
|
||||
|------------|--------|
|
||||
| `POST /wallet/external-payment/initiate` | همان `initiate` |
|
||||
| `GET /wallet/external-payment/return` | `return/montify` |
|
||||
| `POST /wallet/external-payment/webhook` | `webhook/montify` |
|
||||
|
||||
### Initiate — جزئیات
|
||||
|
||||
1. بررسی `WalletPaymentManager::isOnlineDepositAvailable()`
|
||||
2. اعتبارسنجی مبلغ (≥ ۱۰۰۰ و ≥ `min_deposit`)
|
||||
3. ساخت `Withdraw` با `sts=1`، `note=واریز آنلاین`
|
||||
4. `reference_id = WD-{withdraw_id}`
|
||||
5. فراخوانی `gateway->initiate(...)`
|
||||
6. ذخیره `session_id` در `tracked_number` در صورت وجود
|
||||
7. پاسخ فقط `payment_url`
|
||||
|
||||
### Webhook / Return — شارژ
|
||||
|
||||
منطق مشترک در `App\Services\Payment\WalletDepositCreditor`:
|
||||
|
||||
- قفل ردیف `Withdraw` (idempotent)
|
||||
- اگر قبلاً `sts=2` باشد، دوباره شارژ نمیکند
|
||||
- `TransactionInterface` نوع واریز نقدی
|
||||
- همگامسازی حسابداری (Pargar) در صورت امکان
|
||||
- SMS ادمین با سوییچ `sms_notif_gateway_charge_admin`
|
||||
|
||||
---
|
||||
|
||||
## ۶. معماری کد (چنددرگاهی)
|
||||
|
||||
```
|
||||
app/Services/Payment/
|
||||
Contracts/
|
||||
WalletPaymentGatewayInterface.php # قرارداد درگاه
|
||||
Gateways/
|
||||
MontifyPartnerGateway.php # پیادهسازی فعلی Montify
|
||||
# PaypingDirectGateway.php # آینده
|
||||
Support/
|
||||
MontifyHmacSigner.php # HMAC + stableStringify
|
||||
WalletPaymentManager.php # انتخاب درگاه از Settings
|
||||
WalletDepositCreditor.php # شارژ idempotent کیف
|
||||
|
||||
app/Http/Controllers/pay/
|
||||
WalletPaymentController.php # options / initiate / return / webhook
|
||||
```
|
||||
|
||||
### قرارداد درگاه
|
||||
|
||||
هر درگاه باید اینها را پیاده کند:
|
||||
|
||||
- `key(): string` — مثلاً `montify`
|
||||
- `isAvailable(): bool` — فعال + پیکربندی کامل
|
||||
- `initiate(array $payload): array` — حداقل `payment_url`
|
||||
- `returnUrl()` / `webhookUrl()`
|
||||
- `handleReturn(Request): array`
|
||||
- `handleWebhook(Request): array`
|
||||
|
||||
### افزودن درگاه جدید (چکلیست)
|
||||
|
||||
1. کلاس جدید در `Gateways/` با `WalletPaymentGatewayInterface`
|
||||
2. ثبت در `WalletPaymentManager::drivers()`
|
||||
3. افزودن مقدار به optionsی `wallet_payment_provider` در migration/admin (`settingTypes.ts`)
|
||||
4. Settings مخصوص آن درگاه (و اضافه کردن به فیلتر secret در `Setting::isSecretOrServerOnlyKey`)
|
||||
5. مسیرهای `return/{provider}` و `webhook/{provider}` از قبل generic هستند
|
||||
6. تست: options → initiate → webhook → موجودی کیف
|
||||
|
||||
---
|
||||
|
||||
## ۷. فرانتاند (khaneghi_user)
|
||||
|
||||
| فایل | نقش |
|
||||
|------|-----|
|
||||
| `src/api/wallet.ts` | `getWalletPaymentOptions` / `initiateWalletPayment` |
|
||||
| `src/views/base/wallet/composables/useWalletPage.ts` | بارگذاری `online_enabled`، `payOnline()` |
|
||||
| `src/views/base/wallet/components/WalletDepositGuideModal.vue` | دکمه «پرداخت آنلاین از درگاه» |
|
||||
| `src/views/base/wallet/index.vue` | اتصال props/events مدال |
|
||||
|
||||
رفتار UI:
|
||||
|
||||
1. کاربر مبلغ را وارد میکند و واریز میزند → مدال راهنما (کارت/شبا + پشتیبانی)
|
||||
2. اگر `online_enabled` باشد دکمه درگاه نمایش داده میشود
|
||||
3. کلیک → `initiate` → `window.location = payment_url`
|
||||
|
||||
**نکته:** وضعیت دکمه از endpoint سرور میآید، نه از `settingsStore` و کلیدهای `external_payment_*`.
|
||||
|
||||
---
|
||||
|
||||
## ۸. پنل ادمین
|
||||
|
||||
- گروه تنظیمات: `admin/.../configuration/settingGroups.ts` → id `external_payment`
|
||||
- انواع/برچسب/قفل: `settingTypes.ts`
|
||||
- ویرایش credentialها فقط در ادمین (و کلیدهای `can_edit=0` فقط دیپلوی)
|
||||
|
||||
---
|
||||
|
||||
## ۹. مدل داده مرتبط
|
||||
|
||||
| مدل | نقش |
|
||||
|-----|-----|
|
||||
| `Withdraw` | درخواست واریز؛ `sts`: 1=در انتظار، 2=موفق، 3=ناموفق |
|
||||
| `reference_id` | `WD-{id}` برای Montify |
|
||||
| Wallet `TransactionInterface` | واریز نقدی پس از موفقیت |
|
||||
|
||||
واریز دستی تغییری نکرده؛ فقط راهنما + کپی کارت/شبا از Settings عمومی (`deposit_card_number` و …) است.
|
||||
|
||||
---
|
||||
|
||||
## ۱۰. راهاندازی عملیاتی
|
||||
|
||||
1. `php artisan migrate`
|
||||
2. در پنل ادمین / دیپلوی:
|
||||
- `wallet_payment_provider` = `montify`
|
||||
- `external_payment_base_url` / `client_id` / `client_secret` / `gateway`
|
||||
3. در پنل Montify:
|
||||
- ثبت `allowed_return_urls` و `webhook_url` مطابق URLهای سرور
|
||||
4. تست با مبلغ ≥ `min_deposit`
|
||||
5. تأیید: Network اپ کاربر فاقد secret باشد؛ فقط `options` و `initiate` دیده شود
|
||||
|
||||
---
|
||||
|
||||
## ۱۱. عیبیابی سریع
|
||||
|
||||
| مشکل | بررسی |
|
||||
|------|--------|
|
||||
| دکمه درگاه نیست | `options.online_enabled`؛ provider؛ کامل بودن credential؛ `isAvailable()` |
|
||||
| `مشکل در ارتباط با درگاه` | ساعت سرور (NTP)، `stableStringify`، PATH دقیق، body خام همان hash |
|
||||
| شارژ دوبل | باید idempotent باشد (`sts=2` + lock) |
|
||||
| secret در Network | مسیر باید `api/get_settings` باشد نه admin؛ فیلتر `Setting::forPublicApi` |
|
||||
|
||||
جزئیات امضای HMAC و خطاهای Montify: [`EXTERNAL_PAYMENT_PARTNER.md`](./EXTERNAL_PAYMENT_PARTNER.md)
|
||||
|
||||
---
|
||||
|
||||
## ۱۲. ارجاع فایلهای کلیدی
|
||||
|
||||
```
|
||||
server/docs/WALLET_ONLINE_PAYMENT.md ← این سند
|
||||
server/docs/EXTERNAL_PAYMENT_PARTNER.md ← پروتکل Montify
|
||||
server/app/Services/Payment/...
|
||||
server/app/Http/Controllers/pay/WalletPaymentController.php
|
||||
server/routes/customer.php
|
||||
server/app/Models/Setting.php ← فیلتر secret
|
||||
server/database/migrations/2026_07_24_000001_add_external_payment_settings.php
|
||||
server/database/migrations/2026_07_24_000002_add_wallet_payment_provider_setting.php
|
||||
khaneghi_user/src/api/wallet.ts
|
||||
khaneghi_user/src/views/base/wallet/...
|
||||
admin/src/pages/menuPages/configuration/settingTypes.ts
|
||||
admin/src/pages/menuPages/configuration/settingGroups.ts
|
||||
```
|
||||
Reference in New Issue
Block a user