Files
2026-05-27 23:29:53 +01:00

136 lines
8.0 KiB
Markdown

# 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.