feat(payment): add refund and reversal functionality to payment gateways
- Implemented `refund` and `reverse` methods in `PaymentGatewayInterface`. - Added `PaymentRefundResult` class to handle refund operation results. - Enhanced `MockGateway` and `SepGateway` to support refund and reversal operations. - Updated `PaymentManager` to include `refundPayment` and `reversePayment` methods for handling refunds and reversals in transactions. - Modified `ClinicSubscriptionRepository` and `SubscriptionService` to manage subscriptions during refunds. - Added admin API endpoints for processing refunds and reversals. - Updated security headers to allow form actions to the sandbox environment. - Documented the new refund and reversal features in the API documentation.
This commit is contained in:
+29
-1
@@ -647,6 +647,7 @@ List all payments.
|
||||
"gateway": "mellat",
|
||||
"type": "appointment",
|
||||
"ref_id": "1234567",
|
||||
"card_pan": "502229******2928",
|
||||
"patient_mobile": "0912...",
|
||||
"patient_name": "علی احمدی",
|
||||
"appointment_uuid": "...",
|
||||
@@ -656,10 +657,37 @@ List all payments.
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
> `card_pan` شمارهٔ کارت ماسکشدهٔ پرداختکننده (۶ رقم اول + ۴ رقم آخر) است که درگاه در callback برمیگرداند (ملت: `CardHolderPan`) و در `metadata.card_pan` پرداخت ذخیره میشود؛ اگر درگاه آن را نفرستد `null`. `refunds[]` تاریخچهٔ استردادها (`amount` ریال، `ref` شماره پیگیری، `at` unix).
|
||||
|
||||
### POST `/api/v1/admin/payments/{uuid}/refund`
|
||||
|
||||
استرداد وجه یک پرداخت **موفق** (کل یا جزئی). فقط `ROLE_ADMIN`. فقط درگاه ملت پشتیبانی میشود (سپ خطا میدهد).
|
||||
|
||||
**Request body:**
|
||||
| فیلد | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| `amount` | integer? | مبلغ استرداد به **ریال**. اگر ندهی = کل باقیماندهٔ قابل استرداد. |
|
||||
|
||||
استرداد جزئی چندباره مجاز است تا سقف مبلغ خرید. استرداد کامل (رسیدن جمع به مبلغ کل) وضعیت را `refunded` میکند.
|
||||
|
||||
**Response 200:**
|
||||
```json
|
||||
{ "success": true, "data": { "status": "refunded", "refund_ref": "183800538958" } }
|
||||
```
|
||||
|
||||
> کد `0` درگاه ملت فقط «پذیرش اولیهٔ درخواست استرداد» است؛ عودت نهایی به کارت ممکن است چند روز طول بکشد.
|
||||
|
||||
### POST `/api/v1/admin/payments/{uuid}/reverse`
|
||||
|
||||
برگشت وجه یک پرداخت **موفقِ settleنشده** (بدون body). فقط `ROLE_ADMIN`. در موفقیت وضعیت `refunded`.
|
||||
|
||||
**Response 200:** `{ "success": true, "data": { "status": "refunded" } }`
|
||||
|
||||
### Errors (payment refund/reverse/detail)
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | پرداخت یافت نشد |
|
||||
| `ERR_PAYMENT_002` | 422 | مبلغ نامعتبر / پرداخت غیرقابل استرداد / درگاه پشتیبانی نمیکند |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -62,6 +62,24 @@
|
||||
- **چک ضد-دستکاری (اجباری مستند):** در callback، `RefId` بازگشتی باید با `gateway_token` ذخیرهشده و `SaleOrderId` با `payment.id` برابر باشد؛ در غیر اینصورت تراکنش `failed` میشود (این چک برای درگاههایی که این فیلدها را برنمیگردانند، مثل سپ، رد میشود).
|
||||
- **دامنهٔ callback/Referer:** ملت `Referer` و `callBackUrl` را با دامنهٔ ثبتشدهٔ پذیرنده مقایسه میکند؛ در صورت عدم تطابق خطای `62`. مطمئن شوید دامنهٔ بکاند = دامنهٔ ثبتشده نزد ملت.
|
||||
|
||||
**استرداد / برگشت وجه ملت:** درگاه دو متد عودت دارد که از پنل ادمین (`POST /api/v1/admin/payments/{uuid}/refund` و `/reverse`) در دسترساند:
|
||||
- **Refund (`bpRefundRequest`):** برای تراکنش **settleشده**؛ کل یا جزئی (چندباره تا سقف مبلغ خرید). خروجی `0,RefId`؛ کد `0` فقط پذیرش اولیه است. استردادها در `payment.metadata.refunds[]` نگه داشته میشوند؛ استرداد کامل → وضعیت `refunded`.
|
||||
- **Reversal (`bpReversalRequest`):** فقط برای تراکنش **settleنشده** (قبل از واریز)؛ کد `0`/`48` موفق. چون جریان ما بلافاصله verify+settle میکند، مسیر اصلی Refund است.
|
||||
- در `MellatGateway`: sandbox=REST (`/ipg2/rest/bpRefundRequest`,`/bpReversalRequest` با Basic Auth)، prod=SOAP. `orderId` هر درخواست یکتای عددی است. `SepGateway`/`MockGateway` هم متدها را دارند (سپ = عدم پشتیبانی، mock = موفق).
|
||||
- **معکوسسازی post-action:** هنگام **استرداد کامل** (یا برگشت وجه)، اثر پرداخت هم برگردانده میشود (`runReversePostAction`): نوبت → `cancelled_by_user` (اسلات آزاد میشود)؛ اشتراک → حذف `ClinicSubscription` ساختهشده از آن پرداخت؛ کیفپول پیامک → `deduct` مبلغ شارژ. استرداد **جزئی** post-action را برنمیگرداند.
|
||||
|
||||
**حالت Sandbox ملت (موقت — banktest.ir):** برای تست بدون درگاه واقعی، فلگ تنظیماتی `mellat_sandbox` وجود دارد (روی branch `feature/mellat-sandbox`؛ موقت).
|
||||
- روشنکردن: `mellat_sandbox=1` **و** `payment_test_mode=0` (sandbox اتصال واقعی است، نه `MockGateway`؛ اگر `payment_test_mode=1` هم باشد sandbox اولویت دارد). خاموش (نبود/`0`) = رفتار prod.
|
||||
- **پروتکل sandbox = REST** (نه SOAP): SOAP آزمایشیِ banktest روی `pgwchannel` خطای `502` میدهد؛ فقط REST (`.../ipg2/rest/…`) سالم است. پس در حالت sandbox، `MellatGateway`:
|
||||
- `initiate` → `POST .../ipg2/rest/bpPayRequest` با JSON + هدر `Authorization: Basic base64(userName:userPassword)`؛ پاسخ رشتهٔ `0,RefId`.
|
||||
- فرم پرداخت → `https://sandbox.banktest.ir/mellat/bpm.shaparak.ir/pgwchannel/startpay.mellat` (POST `RefId`). توجه: API روی `ipg2/rest` است ولی صفحهٔ فرم فقط روی `pgwchannel/startpay.mellat` سالم است (`ipg2/startpay.mellat` روی banktest خطای `404` میدهد).
|
||||
- `verify` → sandbox متد ترکیبی `bpVerifySettleRequest` را پشتیبانی نمیکند (کد `44`)؛ پس دو فراخوانی جدا: `POST .../ipg2/rest/bpVerifyRequest` (قبول `0`/`43`) سپس `POST .../ipg2/rest/bpSettleRequest` (قبول `0`/`45`). در prod همان `bpVerifySettleRequest` ترکیبی (SOAP) استفاده میشود.
|
||||
- callback در sandbox از IP خارج از رنج شاپرک میآید؛ کنترلر برای `gateway=mellat` + `mellat_sandbox` چک IP را رد میکند (مثل `payment_test_mode`).
|
||||
- credentials sandbox در خودِ `MellatGateway` بهصورت const است (terminalId `134759344` / user `user134759344`)؛ در حالت sandbox عمداً از `site_config` خوانده نمیشود تا credentials واقعیِ prod نشتی نکند. کل رفتار prod (SOAP روی `bpm.shaparak.ir`) دستنخورده باقی میماند.
|
||||
- CSP صفحات پرداخت `form-action` را علاوه بر `*.shaparak.ir` به `sandbox.banktest.ir` هم میدهد.
|
||||
- **موقتی:** بعد از اتمام تست، این branch/فلگ باید حذف شود.
|
||||
- **محدودیت sandbox:** banktest عملیات **استرداد/برگشت وجه** را شبیهسازی نمیکند؛ `bpRefundRequest` همیشه کد `34` (خطای سیستمی) میدهد. کدهای پاسخ ملت با `mellatMessage()` به پیام فارسی نگاشت میشوند. استرداد واقعی فقط در prod (SOAP) کار میکند.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/payment/config`
|
||||
|
||||
Reference in New Issue
Block a user