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 registry
resolveConfig runs a zod schema over tenant and idempotency (it validates tenant.enabled
is a boolean and idempotency.strategy is 'auto' | 'manual'). Payment providers are optional and
resolve to an empty registry when omitted:
const entries = Object.entries(config.providers ?? {});
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.
authorization?: AuthorizationConfig
- Type.
{ enabled: boolean }. - Required. Optional.
- Default.
undefined; resolves toauthorizationEnabled: false. - Behavior. When
enabledistrue, mutating actions enforce anAuthorizationContextthroughassertAuthorizedand the policies insrc/application/policies/. The context is supplied per call. When authorization is disabled, policy checks are skipped.
providers?: Record<string, PaymentProvider>
- Type. Map of provider name to a
PaymentProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Stored as a
Mapand wrapped byProviderRegistry. When no provider name is passed to a provider-bound operation, the first registered provider is used. When the registry is empty, provider-bound operations throwProviderNotFoundErrorwith codePROVIDER_NOT_FOUNDbefore side effects. An explicit unknown provider name produces the same coded error. Payable never installs or infers a mock provider. Webhook routing with more than one provider registered requires/webhooks/:provider; otherwisePayablethrowsPayableErrorcodeWEBHOOK_PROVIDER_AMBIGUOUS.
Local and provider-bound dependencies
LocalDependencies contains storage, tenant context, clock, authorization state, idempotency,
audit, events, and logging. Canonical collection reads use this contract and do not select a
provider. BillingDependencies extends it with an explicitly resolved provider and
providerName for operations that call an external payment API.
Storage-only instances can list canonical customers, subscriptions, and payments, and retrieve a stored local subscription. Customer synchronization, catalogue provider operations, checkout, charges, refunds, billing portals, and payment webhooks remain provider-bound.
accountingProviders?: Record<string, AccountingProvider>
- Type. Map of provider name to an
AccountingProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.accountingProviders().
identityProviders?: Record<string, IdentityProvider>
- Type. Map of provider name to an
IdentityProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.identityProviders().
issuingProviders?: Record<string, IssuingProvider>
- Type. Map of provider name to an
IssuingProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.issuingProviders().
marketplaceProviders?: Record<string, MarketplaceProvider>
- Type. Map of provider name to a
MarketplaceProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.marketplaceProviders().
taxProviders?: Record<string, TaxProvider>
- Type. Map of provider name to a
TaxProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.taxProviders().
terminalProviders?: Record<string, TerminalProvider>
- Type. Map of provider name to a
TerminalProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.terminalProviders().
treasuryProviders?: Record<string, TreasuryProvider>
- Type. Map of provider name to a
TreasuryProviderimplementation. - Required. Optional.
- Default. An empty map.
- Behavior. Each entry is registered by name and exposed through
Payable.treasuryProviders().
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/22-queue.
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 28-security.
idempotency?: IdempotencyConfig
- Type.
{ enabled?: boolean; strategy?: 'auto' | 'manual'; store?: IdempotencyStore }. - Required. Optional.
- Default. Resolved to
{ enabled: true, strategy: 'auto', 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 automatically; manual expects caller-provided keys. |
store | IdempotencyStore? | undefined | Persists idempotency records (find/acquire/takeOver/put/markCompleted/markFailed). The bundled Knex-backed store is KnexIdempotencyRepository. |
There is no resolver field on IdempotencyConfig. Key derivation is handled inside the
operations (via IdempotencyKey.forCharge/forRefund/etc.); the exported DefaultIdempotencyKeyResolver
and the IdempotencyKeyResolver contract belong to ResolveIdempotencyKeyAction. Operation authors
can pass entity or global resolvers through ExecuteIdempotentOperationAction; the action sends the
resolved string key to IdempotencyService. 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 |
authorizationEnabled | config.authorization?.enabled | false |
providers | new Map(entries) | empty Map |
accountingProviders | config.accountingProviders | empty Map |
identityProviders | config.identityProviders | empty Map |
issuingProviders | config.issuingProviders | empty Map |
marketplaceProviders | config.marketplaceProviders | empty Map |
taxProviders | config.taxProviders | empty Map |
terminalProviders | config.terminalProviders | empty Map |
treasuryProviders | config.treasuryProviders | empty Map |
storage | config.storage | 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.store | config.idempotency?.store | undefined |
Cache and lock utilities
cache and locks are not PayableConfig keys. resolveConfig rejects either key with
CONFIG_OPTION_UNSUPPORTED. CacheDriver, LockDriver, MemoryCacheDriver, and
MemoryLockDriver remain available for direct composition outside createPayable. Internal Redis
scaffolds are not supported configuration and must not be deep-imported.