BSS HostKhaneh

پس‌پرداخت تقویمی جلالی

← بازگشت به راهنما

پس‌پرداخت تقویمی جلالی



> 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` بلافاصله بازمحاسبه می‌کند.

تست پیشنهادی



  • محصول پس‌پرداخت جلالی وسط ماه → فاکتور اول پروریت

  • افزونه میان‌دوره → پروریت تا همان `ends_at`

  • ارتقا میان‌دوره → پرداخت مابه‌التفاوت → پلن عوض، `ends_at` ثابت

  • **کاهش میان‌دوره** → سند `RET-…` در لیست فاکتورها + موجودی کیف پول + پلن ارزان‌تر

  • تمدید دوره بعد → مبلغ کامل و جلو رفتن تاریخ


  • مرتبط



  • [billing-document-mode.md](./billing-document-mode.md)

  • [product-cycle-pricing.md](./product-cycle-pricing.md)

  • [ADR-034](../DECISIONS.md)