Files
clinicpro/docs/api/settlement.md
T
hamed 8d2b0d908a feat: Add online share functionality for secretaries
- Introduced `online_share_enabled` and `online_share_percent` fields in the `doctor_secretaries` table to manage secretary shares from online appointments.
- Added `bank_account` field in the `profiles` table to store user-level IBANs for settlements.
- Created `secretary_earnings` table to track earnings per secretary from online appointments, including a foreign key relationship with `financial_breakdowns`.
- Implemented `SecretaryEarning` entity and repository for managing secretary earnings.
- Developed `SecretaryShareResolver` service to determine which secretaries earn from online payments.
- Added `UserIbanResolver` service to handle user IBAN retrieval and management.
- Created `HasIbansTrait` for entities to manage IBANs in a JSON format.
- Implemented tests for secretary earnings and API endpoints for managing secretary shares and IBANs.
2026-07-25 18:34:18 +03:30

12 KiB
Raw Blame History

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

{
  "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

{
  "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)

{
  "amount_rials": 1000000,
  "iban_id": "iban-uuid-..."
}
Field Type Required Description
amount_rials integer Amount to withdraw (must be ≤ wallet balance)
iban_id string شناسه‌ی یکی از شباهای تأییدشده‌ی همان کاربر

شبا از کجا خوانده می‌شود؟ تسویه دیگر مخصوص نماینده نیست: UserIbanResolver ابتدا شبای نماینده (GET /api/v1/representation/mebank_account[].id) و در نبودش شبای پروفایل کاربر (GET /api/v1/secretary/mebank_account[].id) را بررسی می‌کند. بنابراین هر نقشی که موجودی کیف پول دارد — از جمله منشی با سهم نوبت‌های آنلاین — می‌تواند برداشت کند.

شبای انتخابی به‌صورت snapshot (iban, bank_name, owner_name) داخل خود رکورد تسویه ذخیره می‌شود؛ حذف بعدی شبا در پروفایل، این رکورد را تغییر نمی‌دهد. مبلغ همان لحظه‌ی ثبت از کیف‌پول کسر (debit) می‌شود.

Response 201

{
  "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

{
  "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)

{
  "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

{
  "note": "حساب بانکی نادرست است"
}
Field Type Required
note string

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.

ترتیب تقسیم و سهم منشی

۱) هزینهٔ پنل پیامک        ← از ناخالص کم می‌شود
۲) مالیات                  ← استخراجی از باقی‌مانده: tax = amount × p/(100+p)
۳) سهم‌ها، همه از «خالصِ پس از مالیات»:
     پورسانت نماینده = netAfterTax × commission_percent / 100
     سهم هر منشی     = netAfterTax × online_share_percent / 100
   سهم سیستم = ناخالص − پیامک − مالیات − پورسانت − مجموع سهم منشی‌ها
  • سهم منشی مستقل از نماینده است: نوبتِ بدون نمایندهٔ منطبق هم اگر منشیِ سهم‌بر داشته باشد، تفکیک مالی می‌سازد.
  • financial_breakdowns.secretary_share_rials مجموع سهم منشی‌های همان پرداخت است؛ تفکیک هر منشی در جدول secretary_earnings (با share_percent و relation_uuid) ذخیره می‌شود و گزارش پنل منشی از همان خوانده می‌شود (secretary.md).
  • اگر مجموع درصدها (پورسانت + سهم منشی‌ها) از ۱۰۰ بگذرد، به نسبت کلیپ و یک هشدار با payment_uuid لاگ می‌شود تا سهم سیستم منفی نشود.

رسید پرداخت و ثبت پرداخت نهایی (ROLE_ADMIN)

منطق کیف‌پول: مبلغ هنگام ثبت درخواست از کیف‌پول نماینده کسر (reserve/debit) می‌شود؛ رد آن را برمی‌گرداند (credit). تأیید و پرداختِ نهایی کیف‌پول را دوباره دست نمی‌زنند (جلوگیری از double-debit).

POST /file/upload/clinic_pro/settlement/receipt

آپلود تصویر رسید پرداخت. بدنه = محتوای خام فایل؛ هدر Content-Disposition: filename="...". Permission: ROLE_ADMIN

Response 200:

{ "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:

{ "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:

{ "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)