Files
clinicpro/docs/api/settlement.md
T
hamedandClaude Opus 4.8 61ac775175 perf(settlement,rating): paginate 3 unbounded list endpoints (M10-M12)
- M10 GET /settlement: was unbounded; add page/limit + countByUser + data.meta.
- M11 GET /admin/comments/pending: paginate findPending + countPending.
- M12 GET /comments/{doctor}: paginate the fetch-joined roots query via
  Paginator(fetchJoinCollection) + countApprovedRootsByDoctor.

All keep the existing { data: { data: [...] } } envelope and add data.meta
(backward compatible). Default limit 50 / max 100.

Regressions: SettlementListPaginationTest, CommentPaginationTest (both fail
without the limits). Also de-flaked SendCodeMobileRateLimitTest (randomised the
IP block so the persistent per-IP limiter buckets don't accumulate across runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:36:41 +03:30

324 lines
9.7 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 (paginated, newest first).
**Permission:** `AUTH`
### Query Parameters
| پارامتر | نوع | توضیح |
|---------|-----|-------|
| `page` | int | شماره صفحه (پیش‌فرض ۱) |
| `limit` | int | تعداد در هر صفحه (پیش‌فرض ۵۰، حداکثر ۱۰۰) |
### Response `200`
```json
{
"success": true,
"data": {
"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
}
],
"meta": { "totalRecords": 124, "totalPages": 3, "currentPage": 1 }
}
}
```
**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 (paginated, newest first).
**Permission:** `AUTH`
> **صفحه‌بندی:** `?page` و `?limit` (پیش‌فرض ۵۰، حداکثر ۱۰۰). پاسخ علاوه بر `data.data` یک `data.meta` (`totalRecords`/`totalPages`/`currentPage`) دارد؛ پاکت قبلی دست‌نخورده است.
### 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`) |