Subscriptions & Plans — QA Plan
Smoke
| # | Scenario | Expected |
|---|---|---|
| S1 | Create monthly subscription plan | Row created with interval='monthly', currency inherits from org. |
| S2 | Create class-pack plan without classCredits | 400 Class pack plans require classCredits. |
| S3 | Create subscription plan without interval | 400 Subscription plans require an interval. |
| S4 | Create class-pack on Lite tier | 403 — class_packs feature requires Pro. |
| S5 | Soft-delete plan | isActive=false. Existing subs continue to charge; new purchases blocked. |
| S6 | Member buys free plan | Sub created active immediately; no paymentPageUrl; no payment_transactions row. |
| S7 | Member buys paid plan | Sub pending + paymentPageUrl returned; on webhook, sub flips active. |
| S8 | Member retries abandoned checkout | Same pending sub row reused; fresh paymentPageUrl. No duplicate. |
| S9 | Admin enrolls member with skipPayment | Sub active; no payment provider call. |
Lifecycle
| # | Scenario | Expected |
|---|---|---|
| L1 | Freeze active sub | status='paused', pausedAt=now. Renewal cron skips it. Bookings reject. |
| L2 | Resume paused sub after 7 days | status='active', pausedAt=null, currentPeriodEnd += 7d. |
| L3 | Cancel active sub (admin) | status='cancelled', cancelledAt=now. If providerSubscriptionId was set, cancelRecurring is called. |
| L4 | Cancel already-cancelled sub | 400 Subscription is already cancelled. |
| L5 | Member self-cancels at period end | cancelAtPeriodEnd=true, sub stays active. Email + low-priority task. |
| L6 | Member resumes before period end | Flag cleared; reason/actor nulled. |
| L7 | Member tries to resume after cron sweeps | 400 Subscription is not scheduled to cancel. |
| L8 | Cancellation cron sweeps | At 02:30 UTC daily, all subs with cancelAtPeriodEnd=true and currentPeriodEnd ≤ now flip to cancelled; member email fires. |
Renewal failures (FIT-136 surface)
See payments/qa-plan.md for the same scenarios. Subscription-side check:
| # | Scenario | Expected |
|---|---|---|
| RN1 | Past_due sub renews on retry | status='active', attempts=0, period advanced. Membership flips to payment_status='current'. |
| RN2 | Sub enters debt | status='debt', debt_since=now, debt_amount_in_cents=plan.price. Renewal cron will NOT pick it up (selector excludes debt). |
| RN3 | Clear debt | Owner triggers DebtService.clearDebt. Charge fires for debt_amount_in_cents; on success, sub returns to active, debt zeroed. |
Cancellation request workflow (mid-cycle refund)
| # | Scenario | Expected |
|---|---|---|
| CR1 | Member submits request with refund | Row created, owner email, member email, urgent cancellation_review task. |
| CR2 | Duplicate request | 400 A cancellation request is already pending. |
| CR3 | Owner approves with refund | Sub canceled, refund flow runs (capability-aware), request → approved, refundTaskId filled if manual. Member email. |
| CR4 | Owner rejects | Request → rejected. Sub stays active. Member email. |
| CR5 | Member cancels someone else’s request | Service guard (cancellation-requests.service.ts:76): 403 You can only cancel your own subscription. |
| CR6 | Approve request whose sub has no completed charge | Sub still cancels; refund skipped silently. |
| CR7 | Approve request whose linked refund task already exists | Refused upstream by payment.service.ts:508 (Refund already in progress). |
Credits
| # | Scenario | Expected |
|---|---|---|
| C1 | Booking deducts credit from class-pack sub | remaining_credits--. |
| C2 | Cancel booking refunds credit | remaining_credits++. |
| C3 | Booking when credits = 0 | Service throws No credits remaining. |
| C4 | Booking on unlimited sub | No-op deduct. |
| C5 | Admin adjusts credits by +5 | remaining_credits += 5. |
| C6 | Admin adjusts credits by −100 (more than balance) | Clamped to 0. |
| C7 | Adjust credits on cancelled sub | 400 Subscription must be active or paused. |
| C8 | Adjust credits on unlimited plan | 400 This plan has unlimited credits. |
Permissions
| # | Scenario | Expected |
|---|---|---|
| P1 | Member creates plan | 403 |
| P2 | Coach creates plan | 403 |
| P3 | Owner creates class_pack on Lite | 403 (feature-gated) |
| P4 | Member force-cancels another member’s sub | 403 |
| P5 | Coach reads member’s subscriptions | 200 (allowed) |
| P6 | Owner reads cross-org sub | 404 (filtered out) |
E2E
apps/web/e2e/specs/plans-purchase.spec.ts— full member purchase including Cardcom test payment.apps/web/e2e/specs/cancellation-flow.spec.ts— member submit → owner approve → refund task.