# StripeProvider _(no submodules)_ ## Purpose `StripeProvider` is the Stripe adapter for `@suaveplan/payment`. It implements the `PaymentProvider` contract (six methods covering the payment-intent + refund lifecycle) against the Stripe REST API, using the slim `@suaveplan/payment-providers` foundation for every HTTP call. The class adds nothing beyond what `PaymentProvider` requires: no Stripe-specific surface bleeds into the consumer; no in-flight state is held between calls. Provider construction takes the same `ApiKeyConfig` shape every API-key-based sibling uses (`apiKey`, optional `baseUrl`, optional `webhookSecret`, plus the `ProviderCommonOptions` injectable seams — `fetch`, `timeoutMs`, `signal`). Every mutating Stripe call (`createIntent`, `refund`) forwards the caller-supplied `idempotencyKey` as the standard `Idempotency-Key` HTTP header so retries against Stripe collapse into a single side effect. Bearer auth (`Authorization: Bearer `) is sent on every call, including read-only `getIntent`, so the provider works against either a Stripe live secret or test secret without any further wiring. Stripe lifecycle states are mapped exhaustively into the domain enums from `@suaveplan/payment`: seven explicit Stripe status strings collapse into the five user-visible `PaymentStatus` values, an unknown status maps to `failed`, refund statuses collapse to `succeeded` / `pending` / `failed`, refund reasons collapse to the four `RefundReason` values with `other` as the fallback. ## Features - Implements `PaymentProvider` from `@suaveplan/payment` — class with literal `name: "stripe"`, all six methods, every return value conforms to `PaymentIntent` / `Refund`. - Routes every HTTP call through the slim-core `httpRequest` helper — inherits per-call timeout, `AbortSignal` composition, typed errors, empty-body handling, JSON parse-error handling. - Bearer authentication on every request (read + write). - `Idempotency-Key` header forwarded verbatim when the caller supplies `idempotencyKey` on `CreatePaymentIntentInput` or `RefundInput`. Omitted on read calls and on writes where the caller did not supply a key. - Exhaustive Stripe → domain status / refund-status / refund-reason mappers; unknown values fall back to `failed` / `other`. - Payment-method normalisation: Stripe `payment_method.type === "card"` maps to `"card"` with `brand` and `last4`; any other type maps to `"wallet"`; absent payment method maps to `undefined`. - Every raised `PaymentProviderError` carries `provider: "stripe"` so consumers running multiple providers can discriminate without parsing messages. - Universal — no `node:*`, no `bun:*`, no platform-specific imports anywhere under `src/`. Runs unchanged on Node, Bun, browsers, and Web Workers. ## Usage: Basic ```ts import { PaymentEngine } from "@suaveplan/payment"; import { StripeProvider } from "@suaveplan/payment-provider-stripe"; const provider = new StripeProvider({ apiKey: process.env.STRIPE_SECRET_KEY ?? "", }); const engine = new PaymentEngine({ provider }); const intent = await engine.createIntent({ amount: { amount: 5000n, currency: "USD" }, captureMethod: "automatic", }); ``` ## Usage: Advanced Compose multiple providers via the slim-core `ProviderRegistry`, inject a custom `FetchLike` for tracing/retries, scope every call to a caller-supplied `AbortSignal`, and forward an `Idempotency-Key` through the engine to collapse retried `createIntent` calls into a single Stripe side effect: ```ts import { ProviderRegistry, type FetchLike, } from "@suaveplan/payment-providers"; import { StripeProvider } from "@suaveplan/payment-provider-stripe"; const tracingFetch: FetchLike = async (url, init) => { const start = performance.now(); try { return await globalThis.fetch(url, init); } finally { // emit a span with (url, performance.now() - start) } }; const ac = new AbortController(); const provider = new StripeProvider({ apiKey: process.env.STRIPE_SECRET_KEY ?? "", baseUrl: "https://api.stripe.com/v1", webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, fetch: tracingFetch, timeoutMs: 10_000, signal: ac.signal, }); const registry = new ProviderRegistry(); registry.register(provider); const intent = await registry.get("stripe").createIntent({ amount: { amount: 12_99n, currency: "USD" }, captureMethod: "manual", idempotencyKey: crypto.randomUUID(), }); ``` ## API Reference ### `class StripeProvider implements PaymentProvider` ```ts new StripeProvider(config: StripeConfig) ``` - **`name`** — `"stripe"` (literal type, frozen on the class). - **`createIntent(input: CreatePaymentIntentInput): Promise`** — `POST /payment_intents`. Maps `input.amount.amount` to Stripe's minor-unit integer (`Number`), lowercases the currency, forwards `customer` / `description` / `metadata` / `payment_method` when supplied. Forwards `Idempotency-Key` from `input.idempotencyKey`. - **`confirmIntent(intentId): Promise`** — `POST /payment_intents/:id/confirm`. No body, no `Idempotency-Key`. - **`captureIntent(intentId): Promise`** — `POST /payment_intents/:id/capture`. No body, no `Idempotency-Key`. - **`cancelIntent(intentId): Promise`** — `POST /payment_intents/:id/cancel`. No body, no `Idempotency-Key`. - **`refund(input: RefundInput): Promise`** — `POST /refunds`. Forwards `amount` / `reason` / `metadata` when supplied. Forwards `Idempotency-Key` from `input.idempotencyKey`. - **`getIntent(intentId): Promise`** — `GET /payment_intents/:id`. No body, no `Idempotency-Key`. ### `type StripeConfig = ApiKeyConfig` Construction shape — alias for the shared `ApiKeyConfig` from `@suaveplan/payment-providers`. Re-exported by name so consumers can pin construction shape locally: - `apiKey: string` — Stripe secret key (live or test). - `baseUrl?: string` — defaults to `https://api.stripe.com/v1`. - `webhookSecret?: string` — surfaced for downstream `WebhookVerifier` wiring; not used by the provider itself. - `fetch?: FetchLike` — defaults to `globalThis.fetch`. - `timeoutMs?: number` — per-call timeout (default 30 000 ms). - `signal?: AbortSignal` — caller cancellation, composed with the internal timeout signal. ### Error semantics Every failure raised under `StripeProvider` is a `PaymentProviderError` from the slim core. Possible codes: - `PAYMENT_PROVIDER.HTTP_ERROR` — Stripe returned non-2xx; `httpStatus` carries the status code, `bodyExcerpt` carries up to 256 chars of the response. - `PAYMENT_PROVIDER.TIMEOUT` — the per-call timeout fired before Stripe responded. - `PAYMENT_PROVIDER.PARSE_ERROR` — Stripe returned a malformed JSON response. Every raised error carries `provider: "stripe"` so consumers discriminate without parsing messages. ## Implementation Notes - The provider holds no in-flight Promise cache and no token cache — Stripe uses static Bearer auth, so there's nothing to memoise. Idempotency is delegated to Stripe via the `Idempotency-Key` header; the provider just forwards the caller-supplied key. - The status / refund-status / refund-reason mappings are exhaustive and tested per-row. The fallback `failed` / `other` branches are exercised by passing an unmapped Stripe enum value. - `mapPaymentMethod` exists as a small dedicated helper so the card vs. wallet vs. absent branches stay testable in isolation; consumers can rely on `paymentMethod.type === "card"` always carrying `brand` and `last4` when Stripe surfaced them. - The `commonOptions` private accessor exists so every method forwards `fetch`, `signal`, `timeoutMs`, and the `provider` field in lockstep. Calling sites stay symmetric — adding a new `PaymentProvider` method only needs to spread `this.commonOptions()` into the `httpRequest` call. - `package.json` declares `"sideEffects": false` and the only runtime imports are `@suaveplan/payment` (types-only) and `@suaveplan/payment-providers` (one named symbol: `httpRequest`). The dep-graph test pins universal purity.