@seifer-webapp-factory/billing
v0.1.1
Published
billing — zesde capability-module: verticale, pluggable full-stack betaal-feature (eenmalige checkout + abonnementen) via Mollie hosted checkout, die de kits samenbindt via één contract. Componeert de jobs-kit (webhook-idempotentie + reconciliatie-schedul
Readme
@seifer-webapp-factory/billing
The billing capability module — a vertical, full-stack payments feature: one-time checkout and
recurring subscriptions via Mollie hosted checkout, with invoices and an immutable audit trail. It
composes the foundation kits behind one front↔back contract, and it is the sixth capability module (after
authentication, authorization, account, media, user-settings).
- Design doc / decisions (verified against primary sources):
../billing.md - Tier model:
../README.md - Manifest spec:
@seifer-webapp-factory/capability-spec(this module'smanifest.tsis a facade over it)
What it is
| Part | Contents |
|---|---|
| contract/ | the seam: endpoints, DTOs (zod), error taxonomy, events, config schema — both halves generate types from it |
| backend/src | mechanism (pinned dependency): framework-free flow services + the PaymentProvider port; the 3-path webhook reconciliation |
| backend/templates | surface (materialized, project-owned): NestJS controller/module, migrations, pg BillingStore, the Mollie adapter, config, security |
| frontend/src | mechanism: the typed client + Vue composables (usePlans / useCheckout / useSubscriptions / usePayments / useInvoices) |
| frontend/templates | surface: plans.vue / checkout.vue / subscriptions.vue / invoices.vue / return.vue (folded-in payment-module-*), i18n, runtime |
| manifest.ts | provides / requires (+modules: ['authentication']) / config / migrations / contract / templateVersion |
| scaffolder/ | version-aware materializer + the module→module presence-check |
| tests/ | contract, backend unit, Mollie-adapter, frontend component, scaffolder, and (infra-gated) backend e2e |
Requires
- Module dependency:
authentication(a billing customer is always a subject; ownership is keyed on the subject id). The scaffolder refuses to assemble billing if authentication is absent (checkRequiredModules). - Kits: backend
jobs,audit-log,persistence,http-kernel,config,mailer,observability,i18n,rate-limit; frontendpayment,forms,http-client,data,notifications,auth. - Requires-ports (host provides):
PaymentProvider(Mollie adapter ships in the surface),SubjectProvider,Mailer,Database,DesignSystem.
Install (backend)
import { BillingModule } from '@seifer-webapp-factory/billing/backend/templates/nestjs/billing.module.js';
BillingModule.forRoot({
pool, // persistence-kit pool
subjectProvider, // from the authentication module (bearer → subject)
config: {
mollieApiKey: process.env.MOLLIE_API_KEY, // config-kit Secret — never logged
webhookUrl: 'https://your.app/billing/webhook',
returnUrl: 'https://your.app/billing/return',
plans: [{ id: 'pro', name: 'Pro', amountMinor: 999, currency: 'EUR', interval: '1 month' }],
},
// provider: fakeProvider(), // override the PSP in tests; default is the Mollie adapter from config
});Run the migrations (billing_customers, billing_payments, billing_plans, billing_subscriptions,
billing_invoices, billing_webhook_events + the invoice sequence) via the persistence-kit runner
(billingMigrations).
Config (divergence level 1)
provider (mollie) · mollieApiKey (Secret, required) · profileId · webhookUrl + returnUrl
(required) · defaultCurrency (EUR) · plans (id · name · amountMinor · currency · interval ·
trialDays) · maxOneTimeAmountMinor · dunning (notify-only: notifyOnFailure + graceWindowDays) ·
invoiceNumberPrefix.
Divergence & eject
- Configure — the knobs above (plans, currency, dunning window).
- Extend — add a payment method; hook a
billing.payment.succeededhandler; add a plan field. - Materialize & edit — eject
checkout.vue/ the invoice surface, or the Mollie adapter itself, and edit freely; the mechanism keeps upgrading via semver. - Fork — replace the
PaymentProvidermechanism / embed card fields (knowingly enters PCI SAQ-D).
Design invariants (verified against primary sources — see ../billing.md)
- No card data — Mollie hosted redirect only; the host stays in PCI-DSS SAQ-A.
- Webhook trusts only the id — the server re-fetches authoritative status from Mollie; a spoofed body
cannot mark an unpaid order paid. Idempotent on
provider_ref+ status. - Money is integer minor units + ISO-4217; the Mollie adapter converts currency-aware (EUR/JPY/BHD).
- Recurrence is delegated to Mollie mandates + subscriptions; dunning is notify-only (Mollie owns the retry ladder — the module never issues its own retry charge). Plan changes update the subscription in-place (no proration in v1).
