Files
suaveplan/openspec/archive/2026-05-17-add-payment-provider-stripe/proposal.md
2026-05-27 23:29:53 +01:00

8.0 KiB

Add @suaveplan/payment-provider-stripe sibling adapter

Package: @suaveplan/payment-provider-stripe Status: PROPOSED Type: new-package

Why

The 2026-05-17-slim-payment-providers change extracted the five concrete provider adapters (Stripe, PayPal, Square, Mollie, Adyen) out of @suaveplan/payment-providers and into sibling packages so consumers pay only for the providers they install. This change lands the Stripe sibling.

@suaveplan/payment-provider-stripe re-lands the StripeProvider class (previously @suaveplan/payment-providers.StripeProvider prior to the slim-down) verbatim against the slim foundation. It implements PaymentProvider from @suaveplan/payment, uses httpRequest from the slim foundation for every HTTP call, forwards Bearer auth and the caller-supplied Idempotency-Key header on every mutating request, maps Stripe payment-intent / refund status codes to the domain PaymentStatus / RefundStatus enums, and raises only PaymentProviderError.

What changes

  • ADDED: @suaveplan/payment-provider-stripe package at packages/universal/payment-provider-stripe/ (0.1.0, universal tier, sideEffects: false).
  • ADDED: class StripeProvider implements PaymentProvider with literal name: "stripe". Constructor accepts a StripeConfig extending ApiKeyConfig (Stripe secret key + optional webhookSecret, plus the shared ProviderCommonOptions).
  • ADDED: All six PaymentProvider methods — createIntent, confirmIntent, captureIntent, cancelIntent, refund, getIntent — each routed through httpRequest against the Stripe REST API.
  • ADDED: Stripe-domain → PaymentIntent / Refund mappers (mapIntent, mapRefund, mapStatus, mapRefundStatus, mapRefundReason) that translate every Stripe lifecycle state into the @suaveplan/payment enums.
  • ADDED: StripeConfig type re-export so consumers can pin the construction shape without reaching into the slim core for ApiKeyConfig.
  • ADDED: Subpath exports for ./stripe/stripe so consumers can tree-shake the provider class directly.

Out of scope

  • Webhook signature verification — @suaveplan/payment already ships the WebhookVerifier; this package only carries the webhookSecret field on StripeConfig for downstream wiring.
  • OpenTelemetry spans / counters on the provider — defers to a monorepo-wide observability pass tracking @suaveplan/payment-level instrumentation.
  • Stripe Connect, Customer / Setup-intent / Subscription APIs — the PaymentProvider contract only covers payment-intent + refund lifecycle; non-contract Stripe surfaces are deferred to dedicated follow-up packages.
  • Currency conversion — Stripe's minor-unit (cents) amount is mapped to MoneyLike.amount: bigint 1:1 with currency uppercased; no zero-decimal currency adjustment is performed in this change (parity with the pre-slim shape).

Source mapping

This change derives from ../suaveplan/enterprise/payment-providers.md and the verbatim upstream Stripe implementation that was bundled into the just-slimmed @suaveplan/payment-providers package.

Requirement Source lines Notes
Universal-tier purity (no Node / browser / React deps) enterprise/payment-providers.md L37 verbatim cross-cutting rule
Zero Math.random / Date.now at module scope; injectable seams enterprise/payment-providers.md L40 satisfied via shared ProviderCommonOptions (fetch/timeoutMs/signal)
Structured errors extending @suaveplan/error enterprise/payment-providers.md L42, L61 uses PaymentProviderError from the slim core; raises no raw Error
AbortSignal everywhere async, per-call timeout enterprise/payment-providers.md L43 inherits httpRequest composition of caller signal + internal timeout
Idempotency-key wiring on mutating calls enterprise/payment-providers.md L44 createIntent / refund forward Idempotency-Key when caller supplies it
Tree-shakeable, sideEffects: false enterprise/payment-providers.md L55 verbatim
workspace:^ internal deps + Bun catalog externals enterprise/payment-providers.md L56 verbatim
100% Istanbul coverage + 100% docs coverage enterprise/payment-providers.md L50-L51 genesis-wide policy as well
File-size budget (250 soft / 300 hard / 500 test) enterprise/payment-providers.md L52 verbatim
StripeProvider full lifecycle (createIntent / confirmIntent / captureIntent / cancelIntent / refund / getIntent) /mnt/s/web/suaveplan/packages/universal/payment-providers/src/stripe.ts L130-L209 verbatim port against slim foundation; previously folded as a removed requirement by 2026-05-17-slim-payment-providers (specs/payment-providers/spec.md "Requirement: StripeProvider full lifecycle")
Stripe status → PaymentStatus mapping table /mnt/s/web/suaveplan/packages/universal/payment-providers/src/stripe.ts L45-L56 verbatim 7-row mapping including unknown → failed fallback
Stripe refund status → RefundStatus mapping /mnt/s/web/suaveplan/packages/universal/payment-providers/src/stripe.ts L58-L62 verbatim 3-branch mapping (succeeded / pending / failed)
Stripe refund reason → RefundReason mapping /mnt/s/web/suaveplan/packages/universal/payment-providers/src/stripe.ts L104-L111 verbatim 4-branch mapping (duplicate / fraudulent / requested_by_customer / other)
Bearer auth header on every request /mnt/s/web/suaveplan/packages/universal/payment-providers/src/stripe.ts L211-L217 verbatim Authorization: Bearer <apiKey> header
StripeProvider.name === "stripe" /mnt/s/web/suaveplan/packages/universal/payment-providers/src/stripe.ts L119 verbatim literal name

Requirements with no source line

Requirement Justification
provider: "stripe" discriminant on every raised PaymentProviderError Slim-foundation httpRequest accepts a provider field on HttpOptions; wiring it on every call lets consumers discriminate Stripe failures from sibling-package failures without parsing messages. Not required by the upstream file but mandatory for the slim-core contract.
Injectable fetch / signal / timeoutMs forwarded to httpRequest on every call The upstream file held its own timeoutMs but never forwarded the caller's external signal or fetch. Required by the slim foundation (every sibling MUST flow through ProviderCommonOptions); upgraded from the upstream surface in this migration.

Verification approach

Every requirement in specs/payment-provider-stripe/spec.md carries a **Verified by:** line citing concrete it(...) titles in:

  • src/stripe/stripe.test.ts — every PaymentProvider method, the Bearer / Idempotency-Key header forwarding, every status / refund- status / refund-reason branch, provider discriminant on raised errors, injected FetchLike + signal forwarding.
  • tests/dep-graph.test.ts — pins universal-tier purity (no node: / bun: / fs / path / etc imports under src/).

Tests use a real recording FetchLike closure — no vi.mock, no vi.stubGlobal. The Stripe REST API surface is exercised purely through the injected fetch.

Migration impact

None — additive only. This package is brand new under packages/universal/payment-provider-stripe/ and is not yet consumed by any other package in the monorepo. Consumers that previously imported StripeProvider from @suaveplan/payment-providers (none exist on genesis; that surface was removed by 2026-05-17-slim-payment-providers before any consumer landed) will import from the new package per the slim-core migration table.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/payment-provider-stripe to enumerate. The package is brand new with no current dependents.

Dependent Action
(none) [x] no action needed — new package, no consumers yet

Future dependents will be: @suaveplan/api-plugin-payment (when it lands as a Stripe-configurable host) and any application-layer package that wires the PaymentEngine to Stripe.