Overview
@akira-io/payable is a billing engine for Node.js that stays out of your stack’s way. It is
framework, provider, storage, and queue agnostic: the core depends only on contracts, DTOs,
actions, value objects, and state machines, never on a provider SDK, HTTP framework, or database
client. You bring Stripe or Paddle, your storage, and your framework; the engine owns the billing
logic.
The problem it solves
Adding billing to a Node.js backend usually forces three problems at once:
- Billing complexity. Subscriptions, trials, coupons, multi-item plans, swaps, cancellations, resumes, one-off charges, refunds, invoices, and webhook reconciliation each carry their own edge cases. The engine encapsulates these as explicit actions, pipelines, and state machines rather than ad hoc provider calls.
- Provider lock-in. Stripe and Paddle have different SDKs and event shapes. Payable hides both
behind a single
PaymentProvidercontract (src/domain/contracts/payment-provider.contract.ts), so application code does not import a provider SDK directly. - Float money bugs. Money is always handled in minor units through a
Moneyvalue object backed by Dinero.js, so monetary logic never touches floats.dinero.jsis the engine’s only runtime dependency and ships inside the published bundle.
Target users
Node.js backend developers who need Stripe or Paddle billing without committing to a specific HTTP framework, database client, queue, or event system. The library reads nothing from the environment itself (see System boundaries below); the integrating application injects every dependency.
Key capabilities
- Providers: Stripe and Paddle behind one
PaymentProvidercontract, viaStripeProviderandPaddleProvider. - Billing: checkout, subscriptions (trials, coupons, multiple items, swap/cancel/resume),
one-off charges, refunds, invoices, and the customer billing portal. Available through the fluent
builders and the actions
CreateSubscriptionAction,SwapSubscriptionAction,CancelSubscriptionAction,CancelSubscriptionNowAction,ResumeSubscriptionAction,UpdateSubscriptionQuantityAction,ChargeAction,RefundPaymentAction,ListInvoicesAction, andDownloadInvoicePdfAction. - Webhooks: signature verification, event normalization, deduplication, async processing,
local state reconciliation, and replay. Available through
ReceiveWebhookAction,ProcessWebhookAction,ProcessWebhookPipeline,ReplayWebhookAction,StoreWebhookEventAction, andStripeEventNormalizer/StripeWebhookVerifier. - Reliability: idempotency by default, an immutable audit log, and a transactional outbox.
Available through
IdempotencyService,ExecuteIdempotentOperationAction,AuditService, andOutboxService. - Storage / queue: Knex storage driver (
KnexStorageDriver,migrate); synchronous (SyncQueueDriver) or BullMQ (BullMQQueueDriver) queue driver. - HTTP adapters: Express, Fastify, and NestJS, each on its own subpath export
(
./express,./fastify,./nest).
System boundaries - what it does NOT do
- No UI. The package ships only library code and HTTP route adapters. There are no view or template files.
- No authentication or authorization of HTTP callers. There is no built-in auth. Only the
webhook routes are protected (by signature). The checkout and subscription-management routes take
billablefrom the request body with no ownership check. Integrators must add their own auth and verify ownership of thebillable. - No environment reading. The library never calls
process.env. The application reads its own secrets (such asprocess.env.STRIPE_SECRET_KEY) and passes them intonew StripeProvider(...); the reading happens in user code, not the library. Configuration is supplied entirely throughcreatePayable(config)and injected drivers. - No bundled provider SDK, HTTP framework, or database client in the core. Every such dependency is an optional peer, and the core runtime bundle imports none of them (see 02-architecture).