161 lines
5.2 KiB
Markdown
161 lines
5.2 KiB
Markdown
# تسک ۱۱: پنل اشتراکی (Subscription Tiers)
|
||
|
||
## توضیح
|
||
پیادهسازی سیستم پنلهای اشتراکی سهسطحی. ادمین دورهها و قیمتها را تعریف میکند.
|
||
این تسک **gate check** برای تسکهای ۱۲، ۱۳، ۱۵ فراهم میکند.
|
||
|
||
**نکته مهم:** `/api/v1/subscription-payment` و callback آن از قبل در `PaymentController` موجود است.
|
||
در این تسک فقط باید بعد از callback موفق، `ClinicSubscription` ساخته شود + endpoint های Subscription خودش.
|
||
|
||
## سطوح پنل
|
||
|
||
| Level | Name | منشی | patient_records | services |
|
||
|-------|------|------|----------------|---------|
|
||
| 0 | free | ۱ | ❌ | ❌ |
|
||
| 1 | basic | ۲ | ✅ | ✅ |
|
||
| 2 | professional | ۵ | ✅ | ✅ |
|
||
|
||
## Endpoint ها
|
||
|
||
| متد | مسیر | Permission | توضیح |
|
||
|-----|------|-----------|-------|
|
||
| GET | `/api/v1/subscription/plans` | public | لیست پنلها + دورهها + قیمت |
|
||
| GET | `/api/v1/subscription/my` | doctor/clinic | اشتراک فعال + `used_trial` |
|
||
| POST | `/api/v1/subscription/trial` | doctor/clinic | فعالسازی تریال Basic (یکبار) |
|
||
| GET | `/api/v1/admin/subscription/plans` | ROLE_ADMIN | لیست مدیریت پنلها |
|
||
| POST | `/api/v1/admin/subscription/plan` | ROLE_ADMIN | ایجاد پنل |
|
||
| PATCH | `/api/v1/admin/subscription/plan/{uuid}` | ROLE_ADMIN | ویرایش پنل |
|
||
| POST | `/api/v1/admin/subscription/period` | ROLE_ADMIN | افزودن دوره |
|
||
| PATCH | `/api/v1/admin/subscription/period/{uuid}` | ROLE_ADMIN | ویرایش دوره/قیمت |
|
||
| DELETE | `/api/v1/admin/subscription/period/{uuid}` | ROLE_ADMIN | غیرفعالسازی دوره |
|
||
| GET | `/api/v1/admin/subscription/report` | ROLE_ADMIN | گزارش فروش + تریالها |
|
||
|
||
**موجود (تغییر نمیکند):**
|
||
- `POST /api/v1/subscription-payment` — شروع پرداخت (body: `{ period_uuid }`)
|
||
- `GET|POST /api/v1/subscription-payment/callback/{gateway}` — callback gateway
|
||
|
||
## پیشنیازها
|
||
- تسک ۰۲ (Auth/JWT)
|
||
- تسک ۱۵-payment (PaymentController — موجود)
|
||
|
||
## زمان تخمینی
|
||
۱۴ تا ۱۶ ساعت
|
||
|
||
## نمونه Request
|
||
|
||
### POST /api/v1/subscription/trial
|
||
```json
|
||
{}
|
||
```
|
||
(body خالی — entity از JWT گرفته میشود)
|
||
|
||
### POST /api/v1/admin/subscription/period
|
||
```json
|
||
{
|
||
"plan_uuid": "uuid-of-basic-plan",
|
||
"label": "۶ ماهه",
|
||
"duration_months": 6,
|
||
"price_rials": 1200000,
|
||
"is_trial": false,
|
||
"sort_order": 3
|
||
}
|
||
```
|
||
|
||
### POST /api/v1/subscription-payment (موجود)
|
||
```json
|
||
{
|
||
"period_uuid": "uuid-of-selected-period",
|
||
"gateway": "mellat"
|
||
}
|
||
```
|
||
|
||
## نمونه Response
|
||
|
||
### GET /api/v1/subscription/plans
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"name": "basic",
|
||
"level": 1,
|
||
"max_secretaries": 2,
|
||
"features": { "patient_records": true, "services": true, "sms_panel": false },
|
||
"periods": [
|
||
{ "uuid": "...", "label": "تریال ۱ ماهه", "duration_months": 1, "price_rials": 0, "is_trial": true },
|
||
{ "uuid": "...", "label": "۱ ماهه", "duration_months": 1, "price_rials": 250000, "is_trial": false },
|
||
{ "uuid": "...", "label": "۶ ماهه", "duration_months": 6, "price_rials": 1200000, "is_trial": false }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### GET /api/v1/subscription/my
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"plan": { "name": "basic", "level": 1, "features": { "patient_records": true, "services": true } },
|
||
"period": { "label": "۶ ماهه", "duration_months": 6 },
|
||
"is_trial": false,
|
||
"starts_at": 1718000000,
|
||
"expires_at": 1733360000,
|
||
"used_trial": true,
|
||
"days_remaining": 42
|
||
}
|
||
}
|
||
```
|
||
|
||
وقتی اشتراک فعال ندارد:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"plan": { "name": "free", "level": 0 },
|
||
"expires_at": null,
|
||
"used_trial": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### POST /api/v1/subscription/trial (موفق)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"plan": "basic",
|
||
"starts_at": 1718000000,
|
||
"expires_at": 1720678400
|
||
}
|
||
}
|
||
```
|
||
|
||
### POST /api/v1/subscription/trial (خطا — قبلاً استفاده شده)
|
||
```json
|
||
{
|
||
"success": false,
|
||
"errors": [{ "code": "ERR_TRIAL_ALREADY_USED", "message": "قبلاً از تریال استفاده کردهاید" }]
|
||
}
|
||
```
|
||
|
||
## قوانین تریال
|
||
- فقط برای پنل **basic** (level=1)
|
||
- هر entity یکبار — constraint `UNIQUE(entity_type, entity_id, is_trial)` + مقدار `is_trial=1` در DB
|
||
- بدون پرداخت — `payment_id = null`
|
||
- بعد از انقضا → برگشت به Free (دادهها حفظ میشوند)
|
||
- ادمین میتواند تریال را کلاً غیرفعال کند: `SiteConfig.trial_enabled = false`
|
||
|
||
## قانون تمدید
|
||
```
|
||
expires_at جدید = max(expires_at فعلی, time()) + duration_months × 30 × 86400
|
||
```
|
||
یعنی اگر اشتراک هنوز منقضی نشده، تمدید از تاریخ انقضا محاسبه میشود (نه از now).
|
||
|
||
## gate check در تسکهای بعدی
|
||
```php
|
||
// SubscriptionService::hasFeature('patient_records') → bool
|
||
// false → $this->error(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, '...', 403)
|
||
```
|