feat(billing): queue subscriptions bought while active + cancel queued
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m1s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 49s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 34s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Successful in 3m9s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m1s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 49s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 34s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Successful in 3m9s
Before, buying a plan immediately switched the tier and stacked the duration.
Now a purchase made while the café still has paid coverage is QUEUED to start
when the current coverage ends, and the owner can cancel a queued one.
Model:
- SubscriptionPayment gains EffectiveFrom/EffectiveTo; status gains Scheduled
(paid, queued) and Cancelled. EF migration AddSubscriptionScheduling (nullable).
BillingService:
- On payment completion, compute coverage end (latest of active expiry + furthest
queued period). If it is in the future → Scheduled (queued, café tier/expiry
untouched); else activate immediately as before. Periods chain correctly.
- GetStatusAsync lazily promotes any due queued period to active, and returns the
queue (QueuedPlans).
- CancelQueuedAsync cancels a Scheduled period (owner-only) and re-packs the queue
so later periods slide earlier. Active prepaid plan is never cut short; no
automatic refund (manual, per product decision).
- Confirmation SMS distinguishes "activated until X" vs "queued, starts X".
API: BillingStatusDto.QueuedPlans + DELETE /api/billing/queued/{paymentId}.
Dashboard:
- Subscription screen shows a "Queued subscriptions" card (tier, window, cancel
with confirm).
- Checkout shows "you already have an active subscription — this will start on
{date}" when the café is still covered.
- i18n fa/en/ar.
81 API tests pass; dashboard typechecks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1047,7 +1047,18 @@
|
||||
"secureNote": "تتم المعالجة عبر بوابة دفع بنكية آمنة.",
|
||||
"payTotal": "ادفع {total}",
|
||||
"redirecting": "جارٍ التحويل إلى البوابة...",
|
||||
"paymentFailed": "فشل الدفع. الرجاء المحاولة مرة أخرى."
|
||||
"paymentFailed": "فشل الدفع. الرجاء المحاولة مرة أخرى.",
|
||||
"queuedNotice": "لديك اشتراك نشط بالفعل. ستتم إضافة هذا الشراء إلى قائمة الانتظار وسيبدأ في {date}."
|
||||
},
|
||||
"queued": {
|
||||
"title": "الاشتراكات في قائمة الانتظار",
|
||||
"subtitle": "تبدأ تلقائيًا عند انتهاء اشتراكك الحالي.",
|
||||
"months": "{count} أشهر",
|
||||
"window": "من {from} إلى {to}",
|
||||
"cancel": "إلغاء",
|
||||
"cancelled": "تم إلغاء الاشتراك في قائمة الانتظار",
|
||||
"cancelConfirmTitle": "إلغاء الاشتراك المجدول",
|
||||
"cancelConfirmDesc": "إلغاء اشتراك {plan} المقرر أن يبدأ في {from}؟ لن يتأثر اشتراكك الحالي."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1119,7 +1119,18 @@
|
||||
"secureNote": "Payment is processed through a secure bank gateway.",
|
||||
"payTotal": "Pay {total}",
|
||||
"redirecting": "Redirecting to gateway...",
|
||||
"paymentFailed": "Payment failed. Please try again."
|
||||
"paymentFailed": "Payment failed. Please try again.",
|
||||
"queuedNotice": "You already have an active subscription. This purchase will be queued and start on {date}."
|
||||
},
|
||||
"queued": {
|
||||
"title": "Queued subscriptions",
|
||||
"subtitle": "These start automatically when your current subscription ends.",
|
||||
"months": "{count} months",
|
||||
"window": "From {from} to {to}",
|
||||
"cancel": "Cancel",
|
||||
"cancelled": "Queued subscription cancelled",
|
||||
"cancelConfirmTitle": "Cancel queued subscription",
|
||||
"cancelConfirmDesc": "Cancel the {plan} subscription scheduled to start on {from}? Your current subscription is unaffected."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1120,7 +1120,18 @@
|
||||
"secureNote": "پرداخت از طریق درگاه امن بانکی انجام میشود.",
|
||||
"payTotal": "پرداخت {total}",
|
||||
"redirecting": "در حال انتقال به درگاه...",
|
||||
"paymentFailed": "پرداخت ناموفق بود. لطفاً دوباره امتحان کنید."
|
||||
"paymentFailed": "پرداخت ناموفق بود. لطفاً دوباره امتحان کنید.",
|
||||
"queuedNotice": "شما اشتراک فعالی دارید. این خرید در صف قرار میگیرد و از {date} آغاز میشود."
|
||||
},
|
||||
"queued": {
|
||||
"title": "اشتراکهای در صف",
|
||||
"subtitle": "این اشتراکها پس از پایان اشتراک فعلی بهصورت خودکار فعال میشوند.",
|
||||
"months": "{count} ماه",
|
||||
"window": "از {from} تا {to}",
|
||||
"cancel": "لغو",
|
||||
"cancelled": "اشتراک در صف لغو شد",
|
||||
"cancelConfirmTitle": "لغو اشتراک در صف",
|
||||
"cancelConfirmDesc": "اشتراک {plan} که قرار بود از {from} آغاز شود لغو شود؟ اشتراک فعلی شما دستنخورده میماند."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
Reference in New Issue
Block a user