8.1 KiB
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 <apiKey>) 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
PaymentProviderfrom@suaveplan/payment— class with literalname: "stripe", all six methods, every return value conforms toPaymentIntent/Refund. - Routes every HTTP call through the slim-core
httpRequesthelper — inherits per-call timeout,AbortSignalcomposition, typed errors, empty-body handling, JSON parse-error handling. - Bearer authentication on every request (read + write).
Idempotency-Keyheader forwarded verbatim when the caller suppliesidempotencyKeyonCreatePaymentIntentInputorRefundInput. 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"withbrandandlast4; any other type maps to"wallet"; absent payment method maps toundefined. - Every raised
PaymentProviderErrorcarriesprovider: "stripe"so consumers running multiple providers can discriminate without parsing messages. - Universal — no
node:*, nobun:*, no platform-specific imports anywhere undersrc/. Runs unchanged on Node, Bun, browsers, and Web Workers.
Usage: Basic
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:
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
new StripeProvider(config: StripeConfig)
name—"stripe"(literal type, frozen on the class).createIntent(input: CreatePaymentIntentInput): Promise<PaymentIntent>—POST /payment_intents. Mapsinput.amount.amountto Stripe's minor-unit integer (Number), lowercases the currency, forwardscustomer/description/metadata/payment_methodwhen supplied. ForwardsIdempotency-Keyfrominput.idempotencyKey.confirmIntent(intentId): Promise<PaymentIntent>—POST /payment_intents/:id/confirm. No body, noIdempotency-Key.captureIntent(intentId): Promise<PaymentIntent>—POST /payment_intents/:id/capture. No body, noIdempotency-Key.cancelIntent(intentId): Promise<PaymentIntent>—POST /payment_intents/:id/cancel. No body, noIdempotency-Key.refund(input: RefundInput): Promise<Refund>—POST /refunds. Forwardsamount/reason/metadatawhen supplied. ForwardsIdempotency-Keyfrominput.idempotencyKey.getIntent(intentId): Promise<PaymentIntent>—GET /payment_intents/:id. No body, noIdempotency-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 tohttps://api.stripe.com/v1.webhookSecret?: string— surfaced for downstreamWebhookVerifierwiring; not used by the provider itself.fetch?: FetchLike— defaults toglobalThis.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;httpStatuscarries the status code,bodyExcerptcarries 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-Keyheader; the provider just forwards the caller-supplied key. - The status / refund-status / refund-reason mappings are exhaustive
and tested per-row. The fallback
failed/otherbranches are exercised by passing an unmapped Stripe enum value. mapPaymentMethodexists as a small dedicated helper so the card vs. wallet vs. absent branches stay testable in isolation; consumers can rely onpaymentMethod.type === "card"always carryingbrandandlast4when Stripe surfaced them.- The
commonOptionsprivate accessor exists so every method forwardsfetch,signal,timeoutMs, and theproviderfield in lockstep. Calling sites stay symmetric — adding a newPaymentProvidermethod only needs to spreadthis.commonOptions()into thehttpRequestcall. package.jsondeclares"sideEffects": falseand the only runtime imports are@suaveplan/payment(types-only) and@suaveplan/payment-providers(one named symbol:httpRequest). The dep-graph test pins universal purity.