Configuration
All configuration is passed to createPayable(config), which calls resolveConfig(config) to
produce a ResolvedConfig. This page documents every field of PayableConfig, the default
resolveConfig applies, and what each field unlocks.
Validation and the provider requirement
resolveConfig runs a zod schema over tenant and idempotency (it validates tenant.enabled
is a boolean and idempotency.strategy is 'auto' | 'manual'), then requires at least one
provider:
const entries = Object.entries(config.providers ?? {});
if (entries.length === 0) {
throw new TypeError('Payable requires at least one payment provider');
}
PayableConfig fields
tenant?: TenantConfig
- Type.
{ enabled: boolean; resolver?: TenantResolver }. - Required. Optional.
- Default. When omitted,
tenantEnabledresolves tofalseandtenantResolverisundefined. - Behavior. When
enabledistrue, every fluent operation requires a tenant id;Payable.customer(...)throwsPayableErrorcodeTENANT_REQUIREDif the tenant id isundefinedornull. The optionalresolver(aTenantResolver) derives a tenant id from an incoming webhook’s provider, headers, and raw payload. See features/16-multi-tenancy.
providers: Record<string, PaymentProvider>
- Type. Map of provider name to a
PaymentProviderimplementation. - Required. Yes. At least one entry, or
resolveConfigthrowsTypeError. - Default. None.
- Behavior. Stored as a
Mapand wrapped byProviderRegistry. When no provider name is passed tocustomer(...), the first registered provider is used. Webhook routing with more than one provider registered requires/webhooks/:provider; otherwisePayablethrowsPayableErrorcodeWEBHOOK_PROVIDER_AMBIGUOUS.
storage?: StorageDriver
- Type.
StorageDriver(aRepositoriesbundle plus atransaction()method). - Required. Optional.
- Default.
undefined. - Behavior. Persists customers, products, prices, subscriptions, subscription items, invoices,
payments, refunds, webhook events, audit logs, and the outbox. When omitted, the features that
need it throw a
PayableError: outbox (OUTBOX_STORAGE_REQUIRED), webhook processing (WEBHOOK_STORAGE_REQUIRED), and subscription management (SUBSCRIPTION_STORAGE_REQUIRED). Charges and refunds also require storage. The bundled implementation isKnexStorageDriver.
queue?: QueueDriver
- Type.
QueueDriverwithdispatch(job)andprocess(name, handler). - Required. Optional.
- Default.
new SyncQueueDriver(). ThePayableconstructor registers the webhook job handler viaqueue.process(PROCESS_WEBHOOK_JOB, …). - Behavior. Drives async webhook processing.
SyncQueueDriverruns the handler inline ondispatch, so webhook processing happens synchronously in-process. SupplyingBullMQQueueDrivermoves processing onto a BullMQ queue/worker. See persistence/21-queue.
cache?: CacheDriver
- Type.
CacheDriverwithget/set/delete/has. - Required. Optional.
- Default.
undefined. - Behavior. Available for caching needs; not substituted with a default. Bundled
implementations are
MemoryCacheDriverandRedisCacheDriver.
locks?: LockDriver
- Type.
LockDriverwithacquire(key, ttlMs)andwithLock(key, ttlMs, work). - Required. Optional.
- Default.
undefined. - Behavior. Provides distributed locking. Bundled implementations are
MemoryLockDriverandRedisLockDriver. See features/15-reliability.
clock?: Clock
- Type.
Clockwithnow(): Date. - Required. Optional.
- Default.
new SystemClock()(SystemClock.now()returnsnew Date()). - Behavior. All time reads go through the clock, so tests can inject
FakeClock. Exposed viaPayable.clock().
logger?: Logger
- Type.
Loggerwithdebug/info/warn/error. - Required. Optional.
- Default.
new NullLogger()(every method is a no-op). - Behavior. With the default, nothing is logged. Supply
ConsoleLoggeror your own logger to capture output. Exposed viaPayable.logger().
events?: EventBus
- Type.
EventBuswithlisten(name, listener)andemit(event). - Required. Optional.
- Default.
new InMemoryEventBus(). - Behavior. Domain events (subscription/payment/invoice/webhook/etc.) are emitted through this
bus.
InMemoryEventBusdispatches to listeners registered by exact event name plus any'*'wildcard listeners, awaiting each in turn. Exposed viaPayable.events().
encryption?: Encryption
- Type.
Encryptionwithencrypt(plaintext)anddecrypt(ciphertext), both async. - Required. Optional.
- Default.
undefined. - Behavior. Used to encrypt/decrypt sensitive stored values when supplied. The bundled
implementation is
NodeEncryptionDriver. See 26-security.
idempotency?: IdempotencyConfig
- Type.
{ enabled?: boolean; strategy?: 'auto' | 'manual'; resolver?: IdempotencyKeyResolver; store?: IdempotencyStore }. - Required. Optional.
- Default. Resolved to
{ enabled: true, strategy: 'auto', resolver: undefined, store: undefined }. - Behavior. Controls idempotent execution of operations. Documented in detail below.
IdempotencyConfig
| Field | Type | Default (after resolve) | Meaning |
|---|---|---|---|
enabled | boolean? | true | Whether idempotency is applied. On by default. |
strategy | 'auto' | 'manual'? | 'auto' | auto derives keys via the resolver; manual expects caller-provided keys. |
resolver | IdempotencyKeyResolver? | undefined | Derives an idempotency key from { operation, provider?, resourceType?, resourceId? }. The bundled default is DefaultIdempotencyKeyResolver. |
store | IdempotencyStore? | undefined | Persists idempotency records (find/acquire/takeOver/put/markCompleted/markFailed). The bundled Knex-backed store is KnexIdempotencyRepository. |
IdempotencyStrategy is available as a type. The IdempotencyStore record status is one of
'processing' | 'completed' | 'failed' | 'expired'. See features/14-idempotency.
TenantConfig
| Field | Type | Required | Meaning |
|---|---|---|---|
enabled | boolean | Yes (when tenant is supplied) | Turns tenant scoping on. When on, a tenant id is mandatory for fluent operations. |
resolver | TenantResolver? | No | Resolves a tenant id from a webhook’s { provider, headers, payload }. |
Resolved-config reference
resolveConfig returns a ResolvedConfig with these fields. Note the shape differs from the input:
providers becomes a Map, tenant is flattened into tenantEnabled + tenantResolver, and
queue/clock/logger/events/idempotency are always present (defaulted).
| Resolved field | Source | Default applied |
|---|---|---|
tenantEnabled | config.tenant?.enabled | false |
tenantResolver | config.tenant?.resolver | undefined |
providers | new Map(entries) | required (throws if empty) |
storage | config.storage | undefined |
cache | config.cache | undefined |
locks | config.locks | undefined |
queue | config.queue | new SyncQueueDriver() |
clock | config.clock | new SystemClock() |
logger | config.logger | new NullLogger() |
events | config.events | new InMemoryEventBus() |
encryption | config.encryption | undefined |
idempotency.enabled | config.idempotency?.enabled | true |
idempotency.strategy | config.idempotency?.strategy | 'auto' |
idempotency.resolver | config.idempotency?.resolver | undefined |
idempotency.store | config.idempotency?.store | undefined |