payable payable Docs
Coming soon

Getting Started

Requirements

  • Node.js >= 20. The package targets node20.
  • The core has two runtime dependencies only: dinero.js and zod. Everything else is an optional peer.

Install

npm install @akira-io/payable   # or: pnpm add / bun add

Optional peers

Install only the peers for the features you use:

FeatureInstallPeer range
Stripe providernpm i stripe>=15
Paddle providernpm i @paddle/paddle-node-sdk>=2
Knex storagenpm i knex + a driver (pg, better-sqlite3, …)>=3
BullMQ queuenpm i bullmq>=5
Express adapternpm i express>=4.18
Fastify adapternpm i fastify>=4
NestJS adapternpm i @nestjs/common reflect-metadata@nestjs/common >=10, reflect-metadata >=0.2

All eight are marked optional, so package managers do not require them at install time.

Minimal example

createPayable requires at least one payment provider; resolveConfig throws TypeError('Payable requires at least one payment provider') if providers is empty.

import { createPayable, Money, StripeProvider } from '@akira-io/payable';

const payable = createPayable({
  providers: {
    stripe: new StripeProvider({
      secretKey: process.env.STRIPE_SECRET_KEY ?? '',
      webhookSecret: process.env.STRIPE_WEBHOOK_SECRET ?? '',
    }),
  },
});

Money.of(9900, 'USD').format(); // "$99.00"

With only providers supplied, the resolved defaults are: SyncQueueDriver for the queue, SystemClock for the clock, NullLogger for the logger, InMemoryEventBus for events, and idempotency enabled: true with strategy auto. Storage, cache, locks, and encryption stay undefined, which disables features that require them (see 04-configuration).

Full example with storage, queue, and events

Storage is required for webhooks, idempotency, the audit log, the outbox, charges, refunds, and subscription management. The Knex driver provisions its schema with migrate(db).

import knex from 'knex';
import {
  createPayable,
  KnexStorageDriver,
  migrate,
  BullMQQueueDriver,
  InMemoryEventBus,
  StripeProvider,
} from '@akira-io/payable';

const db = knex({ client: 'pg', connection: process.env.DATABASE_URL });
await migrate(db); // creates tables and applies additive column migrations; safe to run repeatedly

const events = new InMemoryEventBus();
events.listen('subscription.created', async (event) => {
  // react to the domain event
});

const payable = createPayable({
  providers: {
    stripe: new StripeProvider({
      secretKey: process.env.STRIPE_SECRET_KEY ?? '',
      webhookSecret: process.env.STRIPE_WEBHOOK_SECRET ?? '',
    }),
  },
  storage: new KnexStorageDriver(db),
  queue: new BullMQQueueDriver(/* … */),
  events,
});

First subscription checkout

The fluent entry point is payable.customer(billable), which returns a CustomerContext. From there, newSubscription(name) returns a SubscriptionBuilder. A price is required before checkout(); otherwise the builder throws PayableError with code CHECKOUT_PRICE_REQUIRED.

const billable = { billableType: 'User', billableId: user.id, email: user.email };

const session = await payable
  .customer(billable)
  .newSubscription('default')
  .price('price_pro_monthly')
  .trialDays(14)
  .checkout({
    successUrl: 'https://app.com/success',
    cancelUrl: 'https://app.com/cancel',
  });

// session is a CheckoutSessionDTO; redirect the user to its provider checkout URL.

A Billable is { billableType: string; billableId: string; email: string; name?: string }. CustomerContext also exposes checkout() (payment mode), charge(...), billingPortal(returnUrl), and subscription(name) for swap/cancel/cancelNow/resume/updateQuantity.

Nothing is read from the environment

The library never reads process.env itself. The application reads its own secrets and passes them into the provider constructor and createPayable. Configuration is fully explicit and injected.