قیمتگذاری چنددورهای محصول اصلی
> version: 1.7 | last_updated: 2026-09-27 | audience: admin
هدف
یک محصول recurring میتواند همزمان قیمت **ساعتی / ماهانه / سهماهه / ششماهه / سالانه** و در صورت نیاز **دورههای سفارشی بر اساس روز** (مثلاً ۲۵ روزه) داشته باشد؛ بدون ساخت چند SKU جدا.
دورهٔ **ساعتی** درگاه بانکی ندارد. قیمت همان ردیف، مبلغ هر ساعت است. خرید فقط وقتی انجام میشود که کیف پول حداقل یک ساعتِ همهٔ سرویسهای ساعتی فعال (با مالیات) را بپوشاند. برای هر مشتری یک ردیف در `hourly_customer_coverages` ساخته میشود: نرخ سوختن ساعتی، تعداد ساعت قابلپوشش، مبلغ رزروشده و `next_check_at`. کران `billing:process-hourly` هر دقیقه فقط ردیفهایی را میخواند که زمان پوشششان رسیده باشد. خرید، جمعآوری، شارژ و خرج کیف پول همان ردیف را دوباره حساب میکند. اگر موجودی به یک ساعت مشترک نرسد، سرویسهای ساعتی با `hourly_wallet_hold` معلق میشوند و با شارژ کافی دوباره روشن میشوند. توقف توسط مشتری همان **درخواست جمعآوری** است. افزونه بدون قیمت ساعتی در این ساعت مبلغی ندارد.
تنظیم در ادمین
تب **قیمت** محصول اصلی با مدل **دورهای**:
| فیلد | نقش |
|------|-----|
| دوره پیشفرض | فقط مقادیر استاندارد enum (`monthly` …) — نمایش کاتالوگ + fallback ستون |
| قیمت ماهانه / سهماهه / … | ماتریس `cycle_pricing` — **قیمت صفر = دوره در فروشگاه نیست** |
| دورههای سفارشی (Repeater) | روز + برچسب + قیمت + نصب → کلید `custom_{days}` داخل همان JSON |
| فیلدهای تکقیمت قدیمی | برای مدل یکبار / پیشپرداخت |
پس از ذخیره، `price` و `setup_fee` از ردیف دورهٔ پیشفرض استاندارد (یا اولین دوره با قیمت > 0) همگام میشوند. ستون `billing_cycle` محصول **هرگز** مقدار `custom_*` نمیگیرد.
نمونه `cycle_pricing`
```json
{
"monthly": { "price": 1000000, "setup": 0 },
"yearly": { "price": 10000000, "setup": 0 },
"custom_25": { "days": 25, "label": "۲۵ روزه", "price": 850000, "setup": 0 }
}
```
خرید
- دوره استاندارد → ردیف همان دوره روی choice / افزونه
- دوره سفارشی → اگر روی choice قیمت `custom_*` تعریف شده همان؛ وگرنه ماهانه × (روز / ۳۰)
- در فرم ادمین choice: Repeater «دورههای سفارشی» کنار ماتریس ماهانه/…