Analytics & reporting
Subscriptions & Recurring Billing
Indicative price, not an offer: $39-99/mo + 2% transaction fee; anchor: Appstle $30 entry
Generated from docs/plan/paid/subscriptions-recurring-billing/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Merged from duplicate proposals: “Subscriptions & Recurring Revenue”
A major paid module providing end-to-end subscription management (billing cycles, retry logic, fraud detection, dunning workflows, churn recovery) without reliance on Stripe. Core provides order system; this module owns the entire recurring billing state machine, PCI compliance, and merchant SLA.
The problem
Subscriptions are complex: charge every month, handle failed cards with retries, detect fraud, send dunning notices before cancellation, let customers pause subscriptions. Building this is a 6-month project; merchants need a turnkey solution with liability (PCI compliance, SLA).
What it does
- Subscription state machine: active → payment_due → payment_failed → dunning → canceled (state transitions with rules)
- Billing cycles: automatic charge on billing date, retry on failure (day 1, day 3, day 5 exponential backoff)
- Fraud detection: flag high-risk transactions (impossible velocity, card mismatch), auto-decline or require additional verification
- Dunning workflow: send email on payment failure (day 1), escalate to manager (day 3), final notice (day 5), cancel if 7 days past due
- Churn recovery: offer pause (2 months free) or discount (20% off next bill) before cancellation
- PCI compliance: never store full card data, handle via tokenization (Stripe tokens or similar), pass PCI audit annually
- Subscription management: pause/resume, plan change (upgrade/downgrade), cancel with prorated refund
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Merchant-specific dunning templates—too custom; merchants use email module to build dunning sequences
- Churn prediction (who will leave)—owned by analytics module
- Subscription-specific tax calculation—owned by tax module
- Affiliate commission on subscriptions—owned by affiliate module
- Subscription pause/resume scheduling (pause until date X)—too niche; pause is manual resume
Data model
New entities: Subscription (id, customerId, productVariantId, status, billingCycle[monthly|annual], subscriptionStartDate, nextBillingDate, currentPeriodEnd, pausedUntil, churnRecoveryAttempts, createdAt); SubscriptionPayment (id, subscriptionId, attemptNumber, status[success|failed], chargeAmount, error, retriedAt, scheduledRetryDate); SubscriptionMetrics (id, merchantId, activeCount, churnedCount, pausedCount, failedPaymentCount, calculatedAt). Links: Subscription.customerId → Customer, Subscription.productVariantId → ProductVariant. Migration: none (new data).
API
- POST /api/v1/subscriptions — create subscription (customer/staff) (tokenized payment required)
- GET /api/v1/subscriptions/{id} — fetch subscription state + payment history (customer/staff)
- PATCH /api/v1/subscriptions/{id}/plan — upgrade/downgrade plan (customer, prorated)
- PATCH /api/v1/subscriptions/{id}/pause — pause subscription until date (customer, prorated)
- PATCH /api/v1/subscriptions/{id}/resume — resume paused subscription (customer)
- DELETE /api/v1/subscriptions/{id} — cancel subscription (customer/staff, generates refund)
- POST /api/v1/subscriptions/billing-run — trigger monthly billing cycle for all subscriptions (async job) (staff only)
- GET /api/v1/subscriptions/metrics — subscription metrics (active, churned, failed payments) (staff only)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Subscriptions page: active/paused/canceled filters, list view with customer name, plan, next billing date, status. Subscription detail: state machine diagram (current state highlighted), payment history (date, amount, status, retry attempts), actions (pause, resume, cancel, upgrade). Metrics dashboard: active subscriptions count, churn rate (this month), failed payments count, dunning notices sent (this month). Billing run history: log of last 10 billing cycles (date, processed count, failed count, errors).
The seam — why this is paid
Core owns: order system, email, payment tokenization (assume Stripe tokens or similar). Paid module owns: entire Subscription entity, state machine transitions, billing cycle orchestration, retry/dunning logic, fraud detection, PCI compliance (audit, documentation, SLA). Seam drawn here because recurring billing is high-liability—merchants need support SLA, PCI audit partnership, and ongoing fraud/compliance monitoring.
Paid module owns recurring billing state machine, PCI DSS compliance, fraud detection, dunning logic, and retry management; no core implementation
Dependencies
- core:orders — billing creates orders and line items
- core:email — send billing notifications, dunning emails
- core:payment-tokenization — assume Stripe/Adyen tokens available
- core:scheduler — run billing cycles nightly
- core:audit-log — log all subscription state changes and payment attempts
- external:fraud-detection-service — assume integration with fraud API (optional but recommended)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- A subscription is created for customer with plan €50/month, billing date 2026-09-01; status is ‘active’, nextBillingDate is 2026-09-01
- On billing date (2026-09-01), a SubscriptionPayment is created for €50, charged via tokenized payment. If successful, status is ‘success’, order line item created. If failed, status is ‘failed’, error recorded, retry scheduled for day 3
- Payment retries: day 1 (initial) fails, day 3 (retry 1) fails, day 5 (retry 2) succeeds; SubscriptionPayment records all 3 attempts with timestamps. Email sent on day 1 (failure) and day 5 (recovery)
- Customer pauses subscription on 2026-09-01; subscription status changes to ‘paused’, pausedUntil is 2026-11-01 (2 months), no charge on 2026-10-01
- Customer resumes on 2026-10-15 (before pausedUntil); nextBillingDate is recalculated to 2026-11-15 (prorated charge for Oct 15-31 is €16, billed immediately)
- Subscription reaches 7 days past due (no successful payment); status changes to ‘canceled’, email sent ‘Subscription canceled due to unpaid balance’, customer is marked as churned
- Churn recovery offer: ‘Subscription paused due to payment failure. Get 2 months free if you resume in next 3 days.’ Email sent, customer clicks link, status changes to ‘paused’ (churn recovery), churnRecoveryAttempts increments
- Metrics dashboard shows: 250 active subscriptions, 15 paused, 30 churned this month, 5 failed payments pending retry
Risks
State machine must be robust—impossible transitions must error (e.g., resume a canceled subscription). Payment retry logic must not double-charge—idempotency keys are critical. Fraud detection false positives block legitimate subscriptions—require support review. Tokenization must never leak card data—audit code for logging/error messages. PCI audit requires annual renewal—schedule and document. Churn recovery logic can spiral (infinite pause-resume)—cap attempts. Prorated calculations must be exact (no penny-off errors); test edge cases (29-31 day months). If billing run is interrupted (server crash), idempotency ensures no duplicate charges on restart.
Commercial context
| Suggested price | $39-99/mo + 2% transaction fee; anchor: Appstle $30 entry |
| Rival anchor | Appstle $30-299/mo, Recharge monthly pricing, Loop $30-50/mo, Seal freemium |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.