# @suaveplan/payment-providers - Slim universal foundation for the @suaveplan/payment-provider-* sibling packages. Ships ProviderRegistry, definePaymentProvider factory, shared httpRequest/composeAbortSignal helpers, FetchLike, the ApiKeyConfig / OAuthConfig / MerchantKeyConfig / HttpOptions / ProviderResponse types, and the PAYMENT_PROVIDER_CODES + PaymentProviderError error model. Concrete Stripe / PayPal / Square / Mollie / Adyen adapters ship as separate @suaveplan/payment-provider- packages that depend on this foundation. Universal **foundation** for the `@suaveplan/payment-provider-*` sibling-package family. This package ships only the shared abstractions every concrete adapter needs — the `ProviderRegistry`, the `definePaymentProvider` factory, the `httpRequest` + `composeAbortSignal` HTTP helpers, the `FetchLike` type, the shared config shapes (`ApiKeyConfig`, `OAuthConfig`, `MerchantKeyConfig`, `HttpOptions`, `ProviderResponse`), and the `PAYMENT_PROVIDER_CODES` registry + `PaymentProviderError` class. Concrete adapters (Stripe, PayPal, Square, Mollie, Adyen) live in **sibling packages** so consumers pay only for the providers they install. ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — Stable error registry and typed error class for `@suaveplan/payment-providers`. Every production failure path — non-ok… - [error-codes](./src/error-codes/error-codes.md) — Stable error registry and typed error class for `@suaveplan/payment-providers`. Every production failure path — non-ok… - [http](./src/http/http.md) — Universal HTTP helper used by every provider to issue typed, cancellable JSON requests. The helper is a thin wrapper ov… - [http](./src/http/http.md) — Universal HTTP helper used by every provider to issue typed, cancellable JSON requests. The helper is a thin wrapper ov… - [registry](./src/registry/registry.md) — Process-local, synchronous registry that indexes `PaymentProvider` instances (from `@suaveplan/payment`) by their `name… - [define](./src/registry/define.md) — Factory helper for sibling `@suaveplan/payment-provider-*` packages that want to expose a `PaymentProvider` (from `@sua… - [registry](./src/registry/registry.md) — Process-local, synchronous registry that indexes `PaymentProvider` instances (from `@suaveplan/payment`) by their `name… - [types](./src/types/provider.md) — Shared configuration interfaces and injectable seams consumed by every sibling `@suaveplan/payment-provider-*` adapter… - [provider](./src/types/provider.md) — Shared configuration interfaces and injectable seams consumed by every sibling `@suaveplan/payment-provider-*` adapter… ## Install ```bash bun add @suaveplan/payment-providers @suaveplan/payment # plus one or more of: bun add @suaveplan/payment-provider-stripe bun add @suaveplan/payment-provider-paypal bun add @suaveplan/payment-provider-square bun add @suaveplan/payment-provider-mollie bun add @suaveplan/payment-provider-adyen ``` ## Quick start ```ts import { ProviderRegistry } from "@suaveplan/payment-providers"; import { StripeProvider } from "@suaveplan/payment-provider-stripe"; import { PaymentEngine } from "@suaveplan/payment"; const registry = new ProviderRegistry(); registry.register(new StripeProvider({ apiKey: process.env.STRIPE_SECRET_KEY ?? "", })); const engine = new PaymentEngine({ provider: registry.get("stripe") }); const intent = await engine.createIntent({ amount: { amount: 5000n, currency: "USD" }, captureMethod: "automatic", }); ``` ## Features - **`ProviderRegistry`** — process-local, synchronous, insertion-ordered registry of `PaymentProvider` instances keyed by `name`; strict `get` with typed miss, duplicate-safe `register`, idempotent `unregister`, frozen `list()` snapshot - **`definePaymentProvider(spec)`** — factory that returns a frozen, literal-typed `PaymentProvider` for sibling packages that prefer a closure to a class - **`httpRequest(options)`** — universal `FetchLike` wrapper with per-call timeout, JSON serialisation, custom-header passthrough, typed errors, empty-body handling, and external-`AbortSignal` composition - **`composeAbortSignal(internal, external?)`** — reusable abort-signal composer (extracted from `httpRequest` so sibling packages can compose their own out-of-band HTTP calls with caller-supplied cancellation) - **`PAYMENT_PROVIDER_CODES`** + **`PaymentProviderError`** — 6-code registry (`HTTP_ERROR`, `TIMEOUT`, `PARSE_ERROR`, `OAUTH_TOKEN_FAILURE`, `PROVIDER_NOT_REGISTERED`, `PROVIDER_ALREADY_REGISTERED`) with optional `provider` / `httpStatus` / `url` / `bodyExcerpt` discriminants - **Shared config shapes** — `ApiKeyConfig`, `OAuthConfig`, `MerchantKeyConfig`, `HttpOptions`, `ProviderResponse`, `ProviderCommonOptions`, plus `NowFn` / `RandomIdFn` / `Base64EncodeFn` injectable seams - **Universal tier** — zero Node, browser, React, or `node:crypto` imports; runs unchanged on Node, Bun, browsers, and Web Workers - **Tree-shakeable** — `sideEffects: false` plus per-feature subpath exports - **100% test coverage** (Istanbul) + 100% docs coverage (sibling `.md` per implementation file) ## Sibling provider packages The five concrete provider adapters that used to ship in this package now live in dedicated sibling packages, each depending on this foundation: - `@suaveplan/payment-provider-stripe` — `StripeProvider` - `@suaveplan/payment-provider-paypal` — `PayPalProvider` - `@suaveplan/payment-provider-square` — `SquareProvider` + the Square-specific `SquareConfig` type - `@suaveplan/payment-provider-mollie` — `MollieProvider` - `@suaveplan/payment-provider-adyen` — `AdyenProvider` Install one or more alongside this foundation. Every sibling exports the same class name + `PaymentProvider`-interface contract as before the slim-down, so migration from a bundled import is a one-line package swap (see `## Migration` below). ## Migration If you were importing a concrete provider from `@suaveplan/payment-providers`, switch to the matching sibling package. The class names are preserved verbatim: ```ts // Before import { StripeProvider } from "@suaveplan/payment-providers"; // After import { StripeProvider } from "@suaveplan/payment-provider-stripe"; ``` All shared types (`ApiKeyConfig`, `OAuthConfig`, `MerchantKeyConfig`, `HttpOptions`, `ProviderResponse`, `NowFn`, `RandomIdFn`, `Base64EncodeFn`), `httpRequest`, `composeAbortSignal`, the `PaymentProviderError` class, and the `PAYMENT_PROVIDER_CODES` registry continue to be exported from `@suaveplan/payment-providers` unchanged. The Square-specific `SquareConfig` type now ships with `@suaveplan/payment-provider-square`. ## Modules - [`src/types/provider.md`](./src/types/provider.md) — `ApiKeyConfig`, `OAuthConfig`, `MerchantKeyConfig`, `HttpOptions`, `ProviderResponse`, plus `NowFn` / `RandomIdFn` / `Base64EncodeFn` - [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — `PAYMENT_PROVIDER_CODES`, `PaymentProviderError`, factories (`httpError`, `timeoutError`, `parseError`, `oauthTokenFailure`, `providerNotRegistered`, `providerAlreadyRegistered`) - [`src/http/http.md`](./src/http/http.md) — `httpRequest` + `composeAbortSignal` + `FetchLike` - [`src/registry/registry.md`](./src/registry/registry.md) — `ProviderRegistry` - [`src/registry/define.md`](./src/registry/define.md) — `definePaymentProvider` factory ## Building a custom provider Sibling provider packages — and any consumer adding a non-standard provider — implement the `PaymentProvider` interface from `@suaveplan/payment` and use `httpRequest` from this foundation for every HTTP call: ```ts import { type ApiKeyConfig, httpRequest, PaymentProviderError, } from "@suaveplan/payment-providers"; import type { CreatePaymentIntentInput, PaymentIntent, PaymentProvider, } from "@suaveplan/payment"; export class CustomProvider implements PaymentProvider { public readonly name = "custom"; private readonly config: ApiKeyConfig; public constructor(config: ApiKeyConfig) { this.config = config; } public async createIntent( input: CreatePaymentIntentInput ): Promise { const { data } = await httpRequest<{ id: string }>({ method: "POST", url: `${this.config.baseUrl ?? "https://api.custom.com"}/intents`, headers: { "X-API-Key": this.config.apiKey }, body: { amount: Number(input.amount.amount) }, fetch: this.config.fetch, signal: this.config.signal, timeoutMs: this.config.timeoutMs, provider: this.name, }); return mapToPaymentIntent(data); } // ...implement remaining PaymentProvider methods } function mapToPaymentIntent(_raw: unknown): PaymentIntent { throw new PaymentProviderError( "PAYMENT_PROVIDER.PARSE_ERROR" as never, "implement mapping" ); } ``` ## Related - `@suaveplan/payment` — `PaymentEngine`, `WebhookVerifier`, `PaymentProvider` interface - `@suaveplan/money` — currency-aware arithmetic - `@suaveplan/error` — base error class + `defineCodes` registry helper ## License MIT