# Subscription API مدیریت پنل‌های اشتراکی (Free / Basic / Professional). --- ## GET /api/v1/subscription/plans لیست پنل‌ها با دوره‌های فعال (عمومی — بدون auth). > پلن `free`: همه امکانات (`patient_records`, `services`, `sms_panel`, `insurance`) فعال‌اند؛ تنها محدودیت آن تعداد منشی (`max_secretaries`) است. **Response 200:** ```json { "success": true, "data": [ { "uuid": "...", "name": "free", "level": 0, "max_secretaries": 1, "features": { "patient_records": true, "services": true, "sms_panel": true, "insurance": true }, "active": true, "periods": [] }, { "uuid": "...", "name": "basic", "level": 1, "max_secretaries": 3, "features": { "patient_records": true, "services": true, "sms_panel": false }, "active": true, "periods": [ { "uuid": "...", "plan_uuid": "...", "label": "یک ماهه", "duration_months": 1, "price_rials": 290000, "is_trial": false, "active": true, "sort_order": 1 } ] } ] } ``` --- ## GET /api/v1/subscription/my اشتراک فعال کاربر جاری. **Permission:** `IS_AUTHENTICATED_FULLY` **نکته:** از نسخه فعلی، این endpoint برای `ROLE_SECRETARY` نیز کار می‌کند. منشی از طریق `UserActiveContextRepository` به `db_uuid` entity مربوطه (doctor یا clinic) دسترسی پیدا می‌کند و اشتراک همان entity برگردانده می‌شود. **Response 200:** ```json { "success": true, "data": { "subscription": { "uuid": "...", "plan": { "name": "basic", "level": 1, "max_secretaries": 3, "features": {...} }, "period": { "label": "یک ماهه", "duration_months": 1, "price_rials": 290000 }, "is_trial": false, "starts_at": 1718000000, "expires_at": 1720678400, "days_remaining": 30, "is_active": true }, "used_trial": false, "effective_plan": { "name": "basic", "level": 1, "max_secretaries": 3, "features": {...} } } } ``` اگر اشتراک فعالی نداشت `subscription` برابر `null` است، اما `effective_plan` همیشه مقدار دارد: پلن اشتراک فعال، یا در نبود اشتراک، **پلن پیش‌فرض `free`**. فرانت‌اند برای تعیین دسترسی به امکانات (`hasFeature`) باید از `effective_plan` استفاده کند (نه `subscription`) تا کاربرانِ بدون اشتراک هم امکانات پلن free را داشته باشند. `subscription`/`hasPlan` صرفاً برای نمایش وضعیت اشتراک پولی است. ### پاسخ کاهش‌یافته برای کاربرِ بدون مجوزِ `subscription.view` (2026-08) پیش از این، منشیِ بدون این مجوز `403` می‌گرفت. نتیجه‌اش یک **قفلِ دروغین در پنل** بود: سایدبار هر آیتم feature-دار (پروندهٔ بیماران، بیمه) را با `hasFeature()` گیت می‌کند و بدون این پاسخ، `features` خالی می‌ماند و آیتم قفل و به صفحهٔ اشتراک هدایت می‌شد — حتی وقتی خودِ API آن قابلیت را به همان منشی می‌داد. حالا پاسخ `200` است ولی فقط توانمندی‌های پلن را دارد: ```json {"success":true,"data":{ "subscription": null, "used_trial": false, "effective_plan": { "features": { "patient_records": true, "…": true }, "max_secretaries": 1 } }} ``` - `subscription`، `used_trial` و فیلدهای هویتی/سطحِ پلن (`name`, `level`, `uuid`, `active`, `periods`) در این حالت **نمی‌آیند**. - افشای تازه‌ای نیست: `GET /subscription/plans` عمومی است و همین `features` را (به‌همراه قیمت‌ها) برای همهٔ پلن‌ها می‌دهد. - سایر نقش‌ها و منشیِ دارای `subscription.view` همان پاسخ کامل بالا را می‌گیرند. --- ## POST /api/v1/subscription/trial فعال‌سازی تریال رایگان (یکبار برای هر entity). **Permission:** `IS_AUTHENTICATED_FULLY` **Response 201:** همان ساختار ClinicSubscription **Errors:** | Code | HTTP | توضیح | |------|------|-------| | ERR_TRIAL_ALREADY_USED | 422 | قبلاً از تریال استفاده شده | | ERR_TRIAL_DISABLED | 422 | تریال غیرفعال است — یا `SiteConfig: trial_enabled=0`، یا پلن `basic` هیچ دورهٔ تریالِ `active` ندارد | | ERR_FORBIDDEN_001 | 403 | پروفایل doctor/clinic یافت نشد | | ERR_NOT_FOUND_001 | 500 | پلن `basic` وجود ندارد یا غیرفعال است (نصب ناقص) | --- ## POST /api/v1/subscription-payment شروع پرداخت اشتراک. **Permission:** `IS_AUTHENTICATED_FULLY` **Request Body:** ```json { "gateway": "mellat", "amount_rials": 290000, "period_uuid": "uuid-of-subscription-period", "frontend_address": "https://example.com/payment-result" } ``` | فیلد | نوع | الزامی | |------|-----|--------| | gateway | string (mellat\|sep) | ✅ | | amount_rials | integer | ✅ | | period_uuid | string (UUID) | ✅ | | frontend_address | string (URL) | ❌ | **Response 200:** ```json { "success": true, "data": { "payment_uuid": "...", "redirect_url": "https://gateway.shaparak.ir/...", "order_id": "ORD-XXXXXXXXXXXXXXXX" } } ``` --- ## GET /api/v1/subscription-payment/callback/{gateway} callback درگاه پرداخت — پس از پرداخت موفق، `ClinicSubscription` به صورت خودکار ایجاد می‌شود (بر اساس `period_uuid` ذخیره‌شده در metadata پرداخت). --- ## Admin Endpoints ### GET /api/v1/admin/subscription/plans **Permission:** `ROLE_ADMIN` — لیست **همه** پلن‌ها شامل غیرفعال‌ها (بر خلاف endpoint عمومی که فقط فعال‌ها را برمی‌گرداند). هر پلن فیلد `active` دارد و `periods` همیشه یک آرایه است (فقط دوره‌های فعال). ### POST /api/v1/admin/subscription/plan **Permission:** `ROLE_ADMIN` ```json { "name": "enterprise", "level": 3, "max_secretaries": 20, "features": { "patient_records": true, "services": true, "sms_panel": true } } ``` **خطاها:** | کد | HTTP | شرح | |----|------|-----| | ERR_VALIDATION_001 | 422 | `name` یا `level` ارسال نشده | | ERR_VALIDATION_001 | 422 | پلنی با این نام از قبل وجود دارد (نام یکتاست) | ### PATCH /api/v1/admin/subscription/plan/{uuid} **Permission:** `ROLE_ADMIN` — ویرایش پنل (همه فیلدها اختیاری) **خطاها:** | کد | HTTP | شرح | |----|------|-----| | ERR_SUBSCRIPTION_NOT_FOUND | 404 | پلن یافت نشد | | ERR_VALIDATION_001 | 422 | نام جدید متعلق به پلن دیگری است | ### POST /api/v1/admin/subscription/period **Permission:** `ROLE_ADMIN` ```json { "plan_uuid": "...", "label": "شش ماهه", "duration_months": 6, "price_rials": 1500000, "is_trial": false, "sort_order": 2 } ``` ### PATCH /api/v1/admin/subscription/period/{uuid} **Permission:** `ROLE_ADMIN` — ویرایش دوره ### DELETE /api/v1/admin/subscription/period/{uuid} **Permission:** `ROLE_ADMIN` — غیرفعال کردن دوره (soft delete: `active=false`) ### GET /api/v1/admin/subscription/report **Permission:** `ROLE_ADMIN` Query params: `page`, `limit` ```json { "success": true, "data": [ { "uuid": "...", "entity_type": "clinic", "entity_id": 5, "is_trial": false, "starts_at": 1718000000, "expires_at": 1720678400, "plan_name": "basic", "plan_level": 1 } ], "meta": { "totalRecords": 50, "totalPages": 3, "currentPage": 1 } } ``` --- ## Error Codes | Code | HTTP | توضیح | |------|------|-------| | ERR_SUBSCRIPTION_REQUIRED | 403 | قابلیت نیاز به پنل Basic+ دارد | | ERR_TRIAL_ALREADY_USED | 422 | تریال قبلاً استفاده شده | | ERR_TRIAL_DISABLED | 422 | تریال غیرفعال است | | ERR_SUBSCRIPTION_NOT_FOUND | 404 | پنل یافت نشد |