@roostjs/billing
Abstract billing provider interface with a Stripe adapter. Implemented using only fetch for Cloudflare Worker compatibility — no Node.js SDK.
Installation
bun add @roostjs/billingConfiguration
Required environment variables:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...Register the service provider:
import { BillingServiceProvider } from '@roostjs/billing';
app.register(BillingServiceProvider);Resolve the provider via the BillingProviderToken symbol:
import { BillingProviderToken } from '@roostjs/billing';
const billing = container.resolve(BillingProviderToken);BillingProvider Interface
The abstract interface implemented by StripeProvider (and FakeBillingProvider).
Resolve via BillingProviderToken to stay provider-agnostic.
createCustomer(params: CreateCustomerParams): Promise<CreateCustomerResult>
Create a billing customer record. Returns the provider-assigned customer ID.
subscribe(params: SubscribeParams): Promise<SubscribeResult>
Create a subscription for a customer on the given price.
cancelSubscription(subscriptionId: string): Promise<void>
Cancel a subscription. The subscription remains active until the end of the billing period.
resumeSubscription(subscriptionId: string): Promise<void>
Resume a cancelled subscription before the billing period ends.
swapSubscription(subscriptionId: string, newPriceId: string): Promise<SubscribeResult>
Change an existing subscription to a different price. Prorates immediately.
getSubscriptionStatus(subscriptionId: string): Promise<SubscriptionStatus>
Returns the current status string. Possible values: 'active',
'trialing', 'past_due', 'canceled',
'incomplete', 'incomplete_expired', 'paused'.
createCheckoutSession(params: CheckoutSessionParams): Promise<CheckoutSessionResult>
Create a hosted checkout session. The result contains a url to redirect the user to.
createPortalSession(params: PortalSessionParams): Promise<PortalSessionResult>
Create a customer portal session. The result contains a url for the portal.
reportUsage(params: UsageRecordParams): Promise<void>
Report metered usage for a subscription item.
parseWebhookEvent(request: Request, webhookSecret: string): Promise<WebhookEvent>
Verify the webhook signature using the Web Crypto API and parse the event payload. Throws if the signature is invalid.
StripeProvider
Concrete implementation of BillingProvider using Stripe's REST API
via fetch. Registered by BillingServiceProvider.
StripeClient
Low-level HTTP client for the Stripe REST API. Used internally by StripeProvider.
Import directly when you need raw Stripe API access beyond what BillingProvider exposes.
verifyStripeWebhook
verifyStripeWebhook(request: Request, secret: string): Promise<WebhookEvent>
Verify a Stripe webhook signature using the Web Crypto API and return the parsed event.
Throws WebhookVerificationError if the signature is invalid or missing.
FakeBillingProvider
In-memory implementation of BillingProvider for use in tests. Records
all calls and returns synthetic results without network requests.
import { FakeBillingProvider } from '@roostjs/billing';
const fake = new FakeBillingProvider();
const customer = await fake.createCustomer({ name: 'Test', email: 'test@example.com' });Use the Billing helper object to enable/disable the fake globally:
import { Billing } from '@roostjs/billing';
const fake = Billing.fake(); // enable fake mode, returns FakeBillingProvider
Billing.restore(); // disable fake modeSubscribedMiddleware
Middleware that gates access to users with an active subscription. Returns
402 Payment Required if the user has no active subscription.
import { SubscribedMiddleware } from '@roostjs/billing';
app.useMiddleware(SubscribedMiddleware);OnTrialMiddleware
Middleware that gates access to users currently on a trial subscription.
import { OnTrialMiddleware } from '@roostjs/billing';
app.useMiddleware(OnTrialMiddleware);Types
interface CreateCustomerParams {
name: string;
email: string;
metadata?: Record<string, string>;
}
interface CreateCustomerResult {
providerId: string;
}
interface SubscribeParams {
customerId: string;
priceId: string;
trialDays?: number;
metadata?: Record<string, string>;
}
interface SubscribeResult {
subscriptionId: string;
status: SubscriptionStatus;
currentPeriodEnd: string;
}
interface CheckoutSessionParams {
customerId: string;
priceId: string;
successUrl: string;
cancelUrl: string;
trialDays?: number;
}
interface CheckoutSessionResult {
sessionId: string;
url: string;
}
interface PortalSessionParams {
customerId: string;
returnUrl: string;
}
interface PortalSessionResult {
url: string;
}
interface UsageRecordParams {
subscriptionItemId: string;
quantity: number;
timestamp: number;
}
interface WebhookEvent {
type: string;
data: unknown;
}
type SubscriptionStatus =
| 'active'
| 'trialing'
| 'past_due'
| 'canceled'
| 'incomplete'
| 'incomplete_expired'
| 'paused';