- Added a new column `national_code_verified` to the `users` table. - Normalized the `bank_account` field in the `representations` table from a single object to an array of IBANs with a default `verified` status of false. feat(ApiIrService): implement identity verification client for api.ir - Created `ApiIrService` to handle identity verification via api.ir. - Implemented methods for matching national code with mobile and IBAN with national code and birth date. - Added error handling and logging for external API requests.
313 lines
9.1 KiB
Markdown
313 lines
9.1 KiB
Markdown
# Settlement & Wallet API
|
|
|
|
> **Prefix:** `/api/v1/wallet`, `/api/v1/settlement`
|
|
|
|
---
|
|
|
|
## GET `/api/v1/wallet/balance`
|
|
|
|
Get authenticated user's wallet balance.
|
|
|
|
**Permission:** `AUTH`
|
|
|
|
### Response `200`
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"balance_rials": 2500000,
|
|
"recent_transactions": [
|
|
{
|
|
"uuid": "...",
|
|
"type": "credit",
|
|
"amount_rials": 500000,
|
|
"balance_after": 2500000,
|
|
"description": "دریافت از نوبت شماره ...",
|
|
"created_at": 1717000000
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
|
|
---
|
|
|
|
## GET `/api/v1/wallet/transactions`
|
|
|
|
Get wallet transaction history for the authenticated user.
|
|
|
|
**Permission:** `AUTH`
|
|
|
|
### Response `200`
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"type": "credit",
|
|
"amount_rials": 500000,
|
|
"balance_after": 2500000,
|
|
"description": "دریافت از نوبت",
|
|
"created_at": 1717000000
|
|
},
|
|
{
|
|
"uuid": "...",
|
|
"type": "debit",
|
|
"amount_rials": 200000,
|
|
"balance_after": 2300000,
|
|
"description": "تسویهحساب",
|
|
"created_at": 1716900000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Transaction Type Values:**
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `credit` | Money added to wallet |
|
|
| `debit` | Money removed from wallet |
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
|
|
---
|
|
|
|
## POST `/api/v1/settlement`
|
|
|
|
Request a settlement (withdrawal from wallet to bank account).
|
|
|
|
**Permission:** `AUTH`
|
|
|
|
### Request Body (`application/json`)
|
|
```json
|
|
{
|
|
"amount_rials": 1000000,
|
|
"iban_id": "iban-uuid-..."
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `amount_rials` | integer | ✅ | Amount to withdraw (must be ≤ wallet balance) |
|
|
| `iban_id` | string | ✅ | شناسهی یکی از شباهای **تأییدشدهی** نماینده (از `GET /api/v1/representation/me` → `bank_account[].id`) |
|
|
|
|
> شبای انتخابی بهصورت snapshot (`iban`, `bank_name`, `owner_name`) داخل خود رکورد تسویه ذخیره میشود؛ حذف بعدی شبا در پروفایل، این رکورد را تغییر نمیدهد. مبلغ همان لحظهی ثبت از کیفپول کسر (debit) میشود.
|
|
|
|
### Response `201`
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "settle-uuid-...",
|
|
"amount_rials": 1000000,
|
|
"status": "pending",
|
|
"bank_account": {
|
|
"iban": "IR...",
|
|
"bank_name": "بانک ملت",
|
|
"owner_name": "علی احمدی"
|
|
},
|
|
"created_at": 1717000000
|
|
}
|
|
}
|
|
```
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_VALIDATION_001` | 422 | مبلغ نامعتبر/بیش از موجودی، یا شبا تأییدنشده/نامعتبر (`field: iban_id`) |
|
|
| `ERR_VALIDATION_002` | 422 | `iban_id` ارسال نشده (`field: iban_id`) |
|
|
| `ERR_CONFLICT_001` | 422 | موجودی کافی نیست |
|
|
|
|
---
|
|
|
|
## GET `/api/v1/settlement`
|
|
|
|
Get authenticated user's settlement requests.
|
|
|
|
**Permission:** `AUTH`
|
|
|
|
### Response `200`
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"amount_rials": 1000000,
|
|
"status": "pending",
|
|
"note": null,
|
|
"created_at": 1717000000,
|
|
"processed_at": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Settlement Status Values:**
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `pending` | Awaiting admin review |
|
|
| `approved` | Approved, payment sent |
|
|
| `rejected` | Rejected by admin |
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
|
|
---
|
|
|
|
## GET `/api/v1/settlement/{uuid}`
|
|
|
|
Get a single settlement.
|
|
|
|
**Permission:** `AUTH` — must be the owner or `ROLE_ADMIN`
|
|
|
|
### Response `200`
|
|
Settlement object with full details including `bank_account`.
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
|
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
|
|
|
---
|
|
|
|
## POST `/api/v1/settlement/{uuid}/approve`
|
|
|
|
Approve a settlement request. Marks it as paid and debits the wallet.
|
|
|
|
**Permission:** `ROLE_ADMIN`
|
|
|
|
### Path Parameters
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| `uuid` | string (UUID) | Settlement UUID |
|
|
|
|
### Request Body (`application/json`)
|
|
```json
|
|
{
|
|
"note": "پرداخت شد — شناسه پیگیری: 123456"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required |
|
|
|-------|------|----------|
|
|
| `note` | string | ❌ |
|
|
|
|
### Response `200`
|
|
Updated settlement object with `status: "approved"`.
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_AUTH_006` | 403 | Not admin |
|
|
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
|
| `ERR_VALIDATION_001` | 422 | Already processed |
|
|
|
|
---
|
|
|
|
## POST `/api/v1/settlement/{uuid}/reject`
|
|
|
|
Reject a settlement request. Returns the amount back to wallet.
|
|
|
|
**Permission:** `ROLE_ADMIN`
|
|
|
|
### Request Body
|
|
```json
|
|
{
|
|
"note": "حساب بانکی نادرست است"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required |
|
|
|-------|------|----------|
|
|
| `note` | string | ✅ | Rejection reason (required) |
|
|
|
|
### Response `200`
|
|
Updated settlement object with `status: "rejected"`.
|
|
|
|
### Errors
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_AUTH_006` | 403 | Not admin |
|
|
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
|
| `ERR_VALIDATION_002` | 422 | Missing note |
|
|
|
|
---
|
|
|
|
## FinancialBreakdown (لاگ مالی)
|
|
|
|
علاوه بر تسویهحساب دستی، کیفپول نماینده بهصورت خودکار از طریق `CommissionService` هنگام پرداخت موفقِ نوبت/اشتراک شارژ میشود (`WalletTransaction` credit). هر واریز یک ردیف `FinancialBreakdown` ثبت میکند که تفکیک کامل تراکنش (ناخالص، هزینه پیامک، مالیات، خالص، درصد و سهم پورسانت، سهم سیستم) را نگه میدارد. ثبت idempotent است (بر اساس `payment_id`). گزارشها از طریق `GET /api/v1/admin/financial-breakdowns` و `GET /api/v1/admin/financial-summary` در دسترساند — جزئیات در `docs/api/admin.md`.
|
|
|
|
---
|
|
|
|
## رسید پرداخت و ثبت پرداخت نهایی (ROLE_ADMIN)
|
|
|
|
> منطق کیفپول: مبلغ هنگام **ثبت درخواست** از کیفپول نماینده کسر (reserve/debit) میشود؛ **رد** آن را برمیگرداند (credit). تأیید و پرداختِ نهایی کیفپول را دوباره دست نمیزنند (جلوگیری از double-debit).
|
|
|
|
### POST `/file/upload/clinic_pro/settlement/receipt`
|
|
|
|
آپلود تصویر رسید پرداخت. بدنه = محتوای خام فایل؛ هدر `Content-Disposition: filename="..."`. **Permission:** `ROLE_ADMIN`
|
|
|
|
**Response `200`:**
|
|
```json
|
|
{ "success": true, "data": { "uuid": "...", "url": "/uploads/settlements/receipts/2026-06/...", "filename": "...", "filemime": "image/jpeg" } }
|
|
```
|
|
|
|
### POST `/api/v1/settlement/{uuid}/receipt`
|
|
|
|
ذخیرهی رسید پرداخت **بدون** نهاییسازی. فقط روی تسویهی **approved**؛ وضعیت تغییر نمیکند (همچنان `approved`). پس از این مرحله دکمهی «تکمیل» در پنل فعال میشود. **Permission:** `ROLE_ADMIN`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{ "receipt": "/uploads/settlements/receipts/2026-06/..." }
|
|
```
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `receipt` | string | ✅ | URL رسیدِ آپلودشده |
|
|
|
|
**Response `200`:** آبجکت تسویه (با `receipt` ذخیرهشده، `status: "approved"`).
|
|
|
|
**Errors:**
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_NOT_FOUND_001` | 404 | درخواست یافت نشد |
|
|
| `ERR_VALIDATION_001` | 422 | تسویه approved نیست |
|
|
| `ERR_VALIDATION_002` | 422 | `receipt` خالی (`field: receipt`) |
|
|
|
|
### POST `/api/v1/settlement/{uuid}/paid`
|
|
|
|
**«تکمیل»** — ثبت پرداخت نهایی. فقط روی تسویهی **approved**. وضعیت → `paid` و `receipt` ذخیره میشود. کیفپول تغییر نمیکند (مبلغ هنگام ثبت درخواست کسر شده). پس از تکمیل، آن مبلغ دیگر قابل درخواست تسویه نیست. **Permission:** `ROLE_ADMIN`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{ "receipt": "/uploads/settlements/receipts/2026-06/..." }
|
|
```
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `receipt` | string | ✅ | URL رسیدِ آپلودشده |
|
|
|
|
**Response `200`:** آبجکت تسویه (شامل `receipt` و `status: "paid"`).
|
|
|
|
**Errors:**
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `ERR_NOT_FOUND_001` | 404 | درخواست یافت نشد |
|
|
| `ERR_VALIDATION_001` | 422 | تسویه approved نیست |
|
|
| `ERR_VALIDATION_002` | 422 | `receipt` خالی (`field: receipt`) |
|