Skip to content

FRD: Billing Provider Wrapper

Document Summary

FieldDetails
Feature NameBilling Provider Wrapper
StatusDraft
OwnerDavid Holmes
ContributorsEngineering
Target Releasev2.0.0
Related LinksADR-051 (Provider Pattern), src/components/patterns/billing-history-table.tsx, ADR-027 (Default Tech Stack), ADR-014 (Open Source First)
Last Updated2026-06-02
Open Source Librariesstripe
DocumentationStripe API Docs · Node.js Quickstart · Billing Overview · Webhooks

This FRD ships @dmwd-io/billing as an optional package that re-exports stripe as the platform standard for subscription billing. The value is standards and drift prevention — not a custom abstraction. Platform conventions (env var names, event naming) are documented on top of the library’s own API.


Introduction

Overview

Per ADR-014 (Open Source First), the platform designates stripe as the community library for subscription billing. Rather than building a custom BillingProvider interface, @dmwd-io/billing re-exports stripe directly and documents the platform’s conventions: STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as the standard env var names, and billing.{resource}.{action} as the internal event naming convention per ADR-065. Each consuming app gets a single, consistent import path and a documented baseline — without the maintenance burden of a custom abstraction layer.

Goals

  • Designate stripe as the platform standard for subscription billing per ADR-014.
  • Re-export stripe via @dmwd-io/billing to give apps a single, stable import path.
  • Document STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as the platform env var conventions.
  • Normalize webhook event naming to billing.{resource}.{action} per ADR-065.
  • Include TypeScript examples showing package wiring, lifecycle flows, and widget integration.

Non-Goals

  • Building a custom provider interface or adapter layer — the library’s own API is the interface (per ADR-014).
  • Building a production vendor adapter from scratch (Stripe is the designated vendor).
  • Implementing payment method collection (see FRD: Payments Provider Wrapper).
  • Building billing-specific UI components beyond what already exists.
  • Supporting multi-currency conversion logic inside the package.
  • Acting as a tax calculation engine.

Scope

In Scope

AreaDescription
Package re-export@dmwd-io/billing re-exports stripe with stripe as a peer dependency
Env var conventionsDocument STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as platform standard names
Type re-exportsRe-export key stripe types (Stripe.Subscription, Stripe.Invoice, etc.) for consuming apps
Webhook event namingDocument billing.{resource}.{action} naming convention per ADR-065
DocumentationStorybook docs page with usage examples and integration guidance

Out of Scope

AreaReason
Custom BillingProvider interfaceADR-014: library’s own API is the interface
Vendor adapter implementationsNot needed — stripe is the designated standard
Payment method CRUDCovered by FRD: Payments Provider Wrapper
Tax calculationDelegated to the billing vendor or a dedicated tax service
Billing UI componentsExisting widgets are presentational; new widgets are a separate effort
Multi-tenant billing isolationApp-level concern, not a library contract responsibility

Users and Pain Points

User Groups

UserDescriptionNeeds
App developersEngineers building SaaS products on the design systemA single import for billing operations that works consistently across the platform
QA engineersTesters validating subscription flowsDeterministic mock data for every subscription state
Design system maintainersContributors to the shared libraryA clean, low-maintenance package that follows ADR-014

Pain Points

UserPain PointImpact
App developersEach app imports stripe directly with inconsistent env var names and error handlingDuplicated effort; configuration drift across services
App developersNo shared entry point for billing operationsDifficult to apply platform-wide conventions (event naming, error handling)
QA engineersCannot reproduce specific subscription states (past-due, trialing, canceled) deterministicallyIncomplete test coverage of billing edge cases

Definitions

TermDefinition
PlanA product pricing tier with an ID, name, interval (monthly/yearly), and unit price
SubscriptionA customer’s active relationship to a plan with a status lifecycle
InvoiceA billing document recording charges, discounts, tax, and payment status
Usage recordA metered event reported against a subscription for usage-based billing
Customer portalA vendor-hosted page where customers manage payment methods and invoices
Billing eventA normalized internal event derived from a vendor webhook payload
ProrationAdjusting charges when a customer changes plans mid-cycle

Current State

Existing Behavior

The design system ships billing-history-table.tsx as a presentational pattern component. It accepts props for rendering invoice rows but has no data-fetching layer. Consuming apps import Stripe SDK directly in API routes, duplicating subscription CRUD, webhook verification, and invoice retrieval logic with inconsistent env var names.

Current Limitations

  • No shared entry point for billing — each app imports stripe independently.
  • No platform-standard env var names — apps use STRIPE_KEY, STRIPE_SECRET, STRIPE_SECRET_KEY interchangeably.
  • No webhook event naming convention — each app names internal events differently.
  • Storybook stories hard-code invoice data inline rather than using stripe’s own fixture patterns.

Existing Workarounds

  • Apps copy-paste Stripe integration code from a private template repo.
  • Storybook stories hard-code invoice data inline rather than using a shared fixture pattern.

Proposed Solution

Summary

Create packages/billing/ with a single src/index.ts that re-exports stripe. The package documents STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as the platform env var names and billing.{resource}.{action} as the internal event naming convention. No custom interface is built — the library’s API is the interface.

import Stripe, { type Stripe as StripeTypes } from '@dmwd-io/billing';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

Key Capabilities

  • Single import path (@dmwd-io/billing) for all stripe usage across the platform.
  • Platform-documented env var names: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET.
  • Re-exported stripe types for consuming apps — no need to install stripe separately.
  • Webhook event naming convention aligned to ADR-065 (billing.subscription.created, billing.invoice.paid, etc.).

User Experience

End users are not directly affected. The package powers billing UI components and API routes behind the scenes.

Developer Experience

Developers import from @dmwd-io/billing instead of directly from stripe. They get the full stripe API with platform conventions documented in one place. The package guarantees a consistent stripe version across all services and eliminates per-app configuration guesswork.


Requirements

IDRequirementPriorityNotes
FR-001@dmwd-io/billing re-exports stripe as its default and named exportsMustPer ADR-014 — library API is the interface
FR-002Re-export key stripe TypeScript types for consuming appsMust-
FR-003Document STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as platform env var namesMust-
FR-004Document billing.{resource}.{action} webhook event naming conventionShouldPer ADR-065
FR-005stripe listed as a peer dependency in package.jsonMust-
FR-006Storybook docs page with usage examplesMust-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001import Stripe from '@dmwd-io/billing' works identically to import Stripe from 'stripe'Apps migrate with a one-line import changeMust
FUNC-002Platform env var names are documented in the package README and Storybook docsTeams configure consistently across servicesMust
FUNC-003Webhook event naming examples in docs show billing.subscription.created, billing.invoice.paid patternTeams name internal events consistentlyShould
FUNC-004billing-history-table Storybook story updated to show integration with stripe data shapesDevelopers see a working end-to-end exampleMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Package adds no runtime code beyond re-exportsPerformanceMust
NFR-002stripe is a peer dependency, not a bundled dependencyBundle sizeMust
NFR-003pnpm typecheck passes with zero errorsMaintainabilityMust
NFR-004Webhook event naming follows billing.{resource}.{action} per ADR-065MaintainabilityShould

API / Interface Requirements

Public API

NameTypeDescriptionRequired
defaulttypeof StripeThe stripe constructor, re-exported as defaultYes
StripenamespaceThe stripe type namespace, re-exported for consuming appsYes

Example Usage

import Stripe from '@dmwd-io/billing';
// Instantiate using platform env var convention
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// Webhook verification using platform env var convention
const event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Map to internal billing event naming per ADR-065
// billing.subscription.created, billing.invoice.paid, etc.

API Notes

  • All monetary amounts follow stripe’s convention: integers in the smallest currency unit (cents for USD).
  • No custom wrapper types are added — use stripe’s own TypeScript types directly.
  • The package version pins a minimum stripe peer dependency version to ensure consistent API surface.

Accessibility Requirements

IDRequirementNotes
A11Y-001Package is a data layer with no direct UI — accessibility requirements apply to consuming componentsBilling UI widgets handle their own a11y
A11Y-002Error messages from stripe must be surfaced as human-readable strings suitable for aria-live regionsConsuming components should surface errors without transformation

Checklist

  • Keyboard support is defined. (N/A — no UI)
  • Focus behavior is defined. (N/A — no UI)
  • Screen reader behavior is defined. (N/A — no UI)
  • Color contrast requirements are met. (N/A — no UI)
  • Reduced motion behavior is considered. (N/A — no UI)
  • Semantic HTML expectations are documented. (N/A — no UI)
  • ARIA usage is defined only where needed. (N/A — no UI)

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page explaining package purpose and platform conventionsStorybookMust
DOC-002Document STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET env var namesStorybook + READMEMust
DOC-003Example: wiring billing-history-table with stripe data shapesStorybookMust
DOC-004Example: webhook handler using platform event naming conventionStorybookShould
DOC-005Example: subscription lifecycle flow (create → upgrade → cancel) using stripe directlyStorybookShould

Dependencies

DependencyTypeOwnerStatusNotes
ADR-014 Open Source FirstArchitectureEngineeringReadyDefines re-export-over-custom-interface principle
ADR-051 provider patternArchitectureEngineeringReadyContext for how other platform packages are structured
ADR-065 webhook handlingArchitectureEngineeringReadyDefines webhook event naming convention
billing-history-table.tsxComponentDesign systemReadyExisting presentational component to show in integration example
Payments provider wrapperLibraryEngineeringNot StartedHandles payment method collection; billing package is complementary

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Stripe API changes require package version bumpApps may lag on stripe API versionPin peer dependency minimum version; document upgrade path
Thin re-export adds little value if teams still import stripe directlyPlatform drift continuesEnforce @dmwd-io/billing import via lint rule; document in onboarding
No mock layer for tests — stripe must be mocked at the SDK levelTests require explicit stripe mock setupDocument recommended mock patterns (e.g. jest.mock('@dmwd-io/billing')) in Storybook docs
Webhook timing edge cases are not abstractedIntegration tests may miss race conditionsDocument that webhook integration tests should use a real vendor sandbox

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should the package include a thin webhook event normalizer that maps stripe events to billing.{resource}.{action}?David HolmesOpen
Q-002Should @dmwd-io/billing also re-export Stripe’s Webhook helper as a named export for convenience?David HolmesOpen
Q-003Should the package include a createStripeClient() factory that reads env vars automatically?David HolmesOpen
Q-004Should the interface support multi-subscription customers (one customer, multiple active plans)?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001import Stripe from '@dmwd-io/billing' resolves to the stripe constructorFR-001
AC-002stripe TypeScript types are available via import type { Stripe } from '@dmwd-io/billing'FR-002
AC-003Package README and Storybook docs name STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as platform env varsFR-003
AC-004Storybook docs include a webhook handler example using billing.{resource}.{action} event namingFR-004
AC-005stripe appears as a peer dependency in packages/billing/package.jsonFR-005
AC-006pnpm typecheck passes with zero errorsNFR-003
AC-007Storybook example wires billing-history-table with stripe invoice data shapesDOC-003
AC-008No custom BillingProvider interface or adapter is implementedADR-014

LLM Handoff Instructions

Expected LLM Behavior

  • Create packages/billing/ if it does not exist.
  • Add packages/billing/package.json with stripe as a peer dependency and @dmwd-io/billing as the package name.
  • Create packages/billing/src/index.ts that does export { default } from 'stripe'; export type { Stripe } from 'stripe';.
  • Document STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as the platform env var names in the Storybook docs page.
  • Add a Storybook docs page showing import usage, env var names, and a webhook handler example with billing.{resource}.{action} event naming.
  • Run pnpm typecheck to confirm zero errors before declaring done.

LLM Should Not

  • Build a custom BillingProvider interface or adapter — ADR-014 says the library’s own API is the interface.
  • Implement a custom factory function wrapping stripe.
  • Modify existing UI components — the package is a data layer only.
  • Add runtime dependencies beyond stripe as a peer.
  • Create webhook HTTP handlers — only document the event naming convention.

Decision Log

DateDecisionReasonOwner
2026-05-26Use integer cents for monetary amountsAvoids floating-point precision issues; matches Stripe conventionDavid Holmes
2026-05-26Model subscription status as a closed union, not an enumAligns with TypeScript best practices used elsewhere in the design systemDavid Holmes
2026-05-26Include usage methods in the base interface (not a separate provider)Usage billing is tightly coupled to subscriptions; splitting would fragment the APIDavid Holmes
2026-06-02Reframed per ADR-014 (Open Source First): adopt stripe as thin re-export rather than building a custom provider interface. Library API is the interface.ADR-014 eliminates the need for a custom abstraction when a well-maintained community library existsDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft
2026-06-02David HolmesReframed per ADR-014: ship as thin re-export of stripe, drop custom interface.