Background
There is no first-class billing/subscription module. SaaS apps building on SimpleModule have to integrate Stripe themselves: customer + subscription objects, webhooks, proration, trial flow, invoice PDFs, tax. Laravel Cashier solves all of this.
Motivation
- Multi-tenant SaaS is an obvious target audience for SimpleModule
- Billing is high-risk and high-effort to build well; commoditizing it is a big sell
- Webhook handling, proration, retries, and dunning are common foot-guns
Design sketch
New optional module modules/Billing (Stripe driver only at first; abstraction allows Paddle later):
Capabilities:
IBilling service: CreateCustomerAsync, SubscribeAsync(customer, plan), SwapPlanAsync, CancelAsync, ResumeAsync, OnTrial(customer)
IsSubscribed(customer) / Subscribed(plan) predicates
BillingPortalAsync(returnUrl) — Stripe Customer Portal redirect
CheckoutAsync(plan, successUrl, cancelUrl) — Stripe Checkout
- Webhooks endpoint at
/billing/webhook with signature verification; emits IEventBus events (SubscriptionCreated, SubscriptionCanceled, InvoicePaid, InvoiceFailed)
- Invoices:
Invoices(customer) returns paginated list; DownloadInvoicePdf(id)
- Models stored locally (mirrored from Stripe):
Customer, Subscription, SubscriptionItem, Invoice
- Multi-tenant aware (
Customer belongs to a tenant)
Permissions: Billing.View, Billing.Manage.
UI: admin pages — current subscription, plan switcher, payment methods, invoices.
Acceptance criteria
References
Background
There is no first-class billing/subscription module. SaaS apps building on SimpleModule have to integrate Stripe themselves: customer + subscription objects, webhooks, proration, trial flow, invoice PDFs, tax. Laravel Cashier solves all of this.
Motivation
Design sketch
New optional module
modules/Billing(Stripe driver only at first; abstraction allows Paddle later):Capabilities:
IBillingservice:CreateCustomerAsync,SubscribeAsync(customer, plan),SwapPlanAsync,CancelAsync,ResumeAsync,OnTrial(customer)IsSubscribed(customer)/Subscribed(plan)predicatesBillingPortalAsync(returnUrl)— Stripe Customer Portal redirectCheckoutAsync(plan, successUrl, cancelUrl)— Stripe Checkout/billing/webhookwith signature verification; emitsIEventBusevents (SubscriptionCreated,SubscriptionCanceled,InvoicePaid,InvoiceFailed)Invoices(customer)returns paginated list;DownloadInvoicePdf(id)Customer,Subscription,SubscriptionItem,InvoiceCustomerbelongs to a tenant)Permissions:
Billing.View,Billing.Manage.UI: admin pages — current subscription, plan switcher, payment methods, invoices.
Acceptance criteria
Stripe.netevent_id)Stripe.NETmock + integration test against Stripe test modeReferences