پسپرداخت تقویمی جلالی
> version: 1.9 | last_updated: 2026-09-13 | audience: admin
قانون (ADR-034)
پسپرداخت تقویمی **فقط جلالی** است. مسیر میلادی وجود ندارد.
مدل محصول
در تب قیمت، مدل **پسپرداخت (Postpaid)**:
| فیلد | نقش |
|------|-----|
| همترازی تقویم | `jalali_calendar` = اول ماه شمسی · `jalali_anniversary` = همان روز ماه بعد |
| ماتریس دوره | ماهانه / سهماهه / ششماهه / سالانه (مثل recurring) |
| دوره سفارشی روز | برای پسپرداخت **نیست** (فقط recurring) |
ستونها: `billing_alignment` روی محصول و اشتراک.
محاسبه دوره
سرویس: `JalaliBillingSchedule` + `BillingPeriod::addInterval(..., $alignment)`
| همترازی | پایان دوره |
|----------|------------|
| تقویم جلالی | اول ماه شمسیِ جاری + N ماه (N از دوره) |
| سالگرد جلالی | همان روز شمسی + N ماه (`addMonths` جلالی) |
مثال تقویم ماهانه: فعالسازی ۱۵ فروردین → `ends_at` / `next_billing_at` = ۱ اردیبهشت.
`metadata.period_started_at` روی اشتراک شروع دورهٔ جاری را نگه میدارد (برای پروریت و نمایش).
اولین فاکتور
اگر وسط ماه تقویمی فعال شود، مبلغ سفارش = قیمت کامل دوره × (روزهای باقیمانده / روزهای دورهٔ ایدهآل).
روی اشتراک `unit_price` = **قیمت کامل** ذخیره میشود تا تمدیدها درست باشند.
تمدید دوره بعد
کرون و تمدید دستی همان مرز جلالی را جلو میبرند و فاکتور با قیمت کامل دوره صادر میکنند.
پس از پرداخت فاکتور، `SubscriptionRenewalApplicationService` اعمال میشود (از `BillingService::applyPaymentToInvoice`).
فاز ۳ — میاندوره (نسخه اصلی، نه MVP)
افزونه / گزینه وسط دوره
`AddonProrationService` برای والدین جلالی طول دوره را از `ends_at` و `period_started_at` میگیرد.
تغییر پلن میاندوره
| حالت (`change_mode`) | رفتار |
|----------------------|--------|
| `extend` | فقط برای **همان پلن فعلی**؛ مبلغ کامل؛ `ends_at` جلو میرود |
| `mid_cycle` | **تنها حالت تغییر پلن** به محصول دیگر؛ مابهالتفاوت؛ `ends_at` ثابت؛ بعد از پرداخت تغییر پکیج در `provisioning_queue` با action=`change_package` میرود و تا موفقیت، نام/پلن در پنل مشتری عوض نمیشود |
| جهت | نتیجه مالی |
|-----|------------|
| ارتقا (`charge > 0`) | فاکتور عادی؛ پس از پرداخت پلن عوض میشود — مگر `allow_mid_cycle_plan_upgrade=false` (فقط تمدید دوره بعد) |
| کاهش — با برگشت وجه | طبق تنظیم دسته (`refund_downgrade_credit`): فاکتور `RET-` + کیف پول |
| کاهش — بدون برگشت وجه | فقط پلن عوض میشود؛ مبلغی واریز نمیشود |
| کاهش ممنوع | دسته `allow_plan_downgrade=false` → پلن ارزانتر در UI و API مسدود است |
| ارتقا میاندوره ممنوع | دسته `allow_mid_cycle_plan_upgrade=false` → مابهالتفاوت ارتقا در همین دوره مسدود؛ تمدید دوره بعد OK |
| پلن فعلی (همان offering) | فقط `extend`؛ UI گزینهٔ میاندوره را نشان نمیدهد؛ API با `mid_cycle` رد میشود |
| تمدید خودکار دسته (`renewal_mode=automatic`) | منوی پورتال «ارتقا / کاهش»؛ پلن فعلی در کاتالوگ تمدید نیست؛ اشتراک جدید `auto_renew=true` |
| تغییر پلن در صف | پس از پرداخت میاندوره، `product_offering` تا موفقیت `change_package` عوض نمیشود؛ شکست → `exhausted` در منوی صف ساخت سرویس |
| بدون اختلاف | اعمال پلن بدون سند مالی (همچنان از مسیر صف اگر ماژول ریموت باشد) |
محاسبه: `RenewalPricingService::midCyclePlanChangeBreakdown`
اعمال: `SubscriptionRenewalApplicationService`
صدور برگشت: `BillingService::issueSalesReturnInvoice` (کاهش `amount_paid` فاکتور منبع در صورت وجود + بستانکار wallet)
نمایش UI
برچسب چرخه و بازه شمسی در پورتال/ریسلر؛ روی کارت تمدید مبلغ برگشت از فروش برای کاهش نشان داده میشود. روی کارت **پلن فعلی** فقط تمدید دوره بعد است (بدون رادیو «تغییر پلن در همین دوره»).
نمایش UI خرید (مشتری / ریسلر)
برای محصول پسپرداخت جلالی در checkout و ثبت سفارش ریسلر:
| ردیف | معنی |
|------|------|
| قیمت دوره کامل | مبلغ کاتالوگ یک دورهٔ کامل |
| قیمت تا پایان دوره | مبلغ پروریتشدهٔ قابل پرداخت الان (× روز باقیمانده) |
گزینهٔ تعدادی / قابل تنظیم روی همان محصول یا افزونه:
| ردیف | معنی |
|------|------|
| قیمت واحد (دوره کامل) | قیمت کاتالوگ یک واحد |
| قیمت واحد (تا پایان دوره) | واحد × ضریب پروریت |
| جمع دوره کامل / تا پایان دوره | تعداد × واحد (کامل یا پروریت) |
محاسبه بکاند (`AddonProrationService`) با همان مرز جلالی محصول اصلی همتراز است.
در UI ریسلر/checkout، تغییر دوره، گزینه، تعداد یا **قیمت سفارشی** همهٔ این ردیفها را از طریق `addon-pricing-script` بلافاصله بازمحاسبه میکند.