214 lines
9.1 KiB
Markdown
214 lines
9.1 KiB
Markdown
# @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-<name> 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.
|
|
|
|
<!-- begin:auto-docs -->
|
|
## 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…
|
|
<!-- end:auto-docs -->
|
|
|
|
## 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<TName>(spec)`** — factory that returns a
|
|
frozen, literal-typed `PaymentProvider` for sibling packages that
|
|
prefer a closure to a class
|
|
- **`httpRequest<T>(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<PaymentIntent> {
|
|
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
|
|
|