Files
clinicpro/docs/phase2_taskes/task-11-subscription/task.md
T

5.2 KiB
Raw Blame History

تسک ۱۱: پنل اشتراکی (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

{}

(body خالی — entity از JWT گرفته می‌شود)

POST /api/v1/admin/subscription/period

{
  "plan_uuid": "uuid-of-basic-plan",
  "label": "۶ ماهه",
  "duration_months": 6,
  "price_rials": 1200000,
  "is_trial": false,
  "sort_order": 3
}

POST /api/v1/subscription-payment (موجود)

{
  "period_uuid": "uuid-of-selected-period",
  "gateway": "mellat"
}

نمونه Response

GET /api/v1/subscription/plans

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

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

وقتی اشتراک فعال ندارد:

{
  "success": true,
  "data": {
    "plan": { "name": "free", "level": 0 },
    "expires_at": null,
    "used_trial": false
  }
}

POST /api/v1/subscription/trial (موفق)

{
  "success": true,
  "data": {
    "plan": "basic",
    "starts_at": 1718000000,
    "expires_at": 1720678400
  }
}

POST /api/v1/subscription/trial (خطا — قبلاً استفاده شده)

{
  "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 در تسک‌های بعدی

// SubscriptionService::hasFeature('patient_records') → bool
// false → $this->error(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, '...', 403)