Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
# Add @suaveplan/payment-providers — universal-tier provider plugins (Stripe / PayPal / Square / Mollie / Adyen)
**Package**: `@suaveplan/payment-providers`
**Status**: PROPOSED
**Type**: migration (upstream `../suaveplan/packages/universal/payment-providers`)
- **Spec delta**: ./specs/payment-providers/spec.md
## Why
`STATUS.md` line 133 lists `@suaveplan/payment-providers` as Pending. The
upstream package supplies the five reference adapters that implement the
`@suaveplan/payment` `PaymentProvider` interface — Stripe, PayPal,
Square, Mollie, and Adyen. With `@suaveplan/payment` shipped in the same
wave, every commerce-tier downstream
(`@suaveplan/api-plugin-payment`, `@suaveplan/booking`,
`@suaveplan/api-plugin-banking`) needs at least one of these adapters to
deliver an end-to-end payment lifecycle.
The migration preserves the entire upstream public surface verbatim
(`StripeProvider`, `PayPalProvider`, `SquareProvider`, `MollieProvider`,
`AdyenProvider`, `httpRequest`, `ApiKeyConfig`, `OAuthConfig`,
`MerchantKeyConfig`, `ProviderResponse`, `HttpOptions`) while upgrading
five foundational concerns to the genesis quality bar:
1. **Injectable `FetchLike`** — every provider accepts an optional
`fetch` config field defaulting to `globalThis.fetch`. Tests use a
real recording `FetchLike` rather than `vi.spyOn(globalThis, "fetch")`,
so the no-mocks rule (CLAUDE.md §2) is preserved without losing
coverage.
2. **Injectable `now` / `randomId`** — `PayPalProvider`'s token-cache
clock and `SquareProvider` / `AdyenProvider`'s default
idempotency-key generator are config fields. This removes module-scope
`Date.now()` and `crypto.randomUUID()`, aligning with the deterministic
universal-tier rule.
3. **Injectable `base64Encode`** — PayPal's OAuth Basic header uses
`btoa` by default but accepts an injected encoder so the package runs
on every runtime that names Base-64 differently.
4. **Typed `PaymentProviderError`** extending
`SuaveplanError<PaymentProviderErrorCode>` replaces the upstream
`throw new Error(...)` raised inside `httpRequest`. The
`PAYMENT_PROVIDER_CODES` registry is declared via
`defineCodes("PAYMENT_PROVIDER", [...])` (4 codes:
`HTTP_ERROR`, `TIMEOUT`, `PARSE_ERROR`, `OAUTH_TOKEN_FAILURE`) and
every former `Error` boundary upgrades to a typed factory carrying
the HTTP status, response body excerpt, and provider name as
discriminants.
5. **Folder layout** — the upstream flat `src/{stripe,paypal,...}.ts`
becomes `src/providers/{stripe,paypal,square,mollie,adyen}/<name>.ts`
per genesis convention (one feature module per folder, sibling `.md`,
tests, types, barrel). The folder-naming rule §10 is respected by
naming children only with the provider name (no `-provider` suffix).
`httpRequest` moves to `src/http/http.ts` and its config types to
`src/types/provider.ts` so consumers can tree-shake.
The 8 upstream source files expand to 15 (5 provider impls × {`<name>.ts`, `<name>.md`, `<name>.test.ts`, `index.ts`} per folder + flat `http/`, `types/`, `error-codes/` modules). One sibling `.md` per implementation file lands at genesis quality (≥200 words, basic + advanced examples, full API reference).
## What changes
- ADDED: `StripeProvider` — Stripe REST API adapter, Bearer auth, manual
- automatic capture, idempotency keys, status + refund-reason mapping.
- ADDED: `PayPalProvider` — PayPal Orders v2 adapter, OAuth2 client-
credentials flow with token cache, injectable `now` for cache TTL,
injectable `base64Encode` for the Basic auth header.
- ADDED: `SquareProvider` — Square Payments adapter, Bearer auth,
manual + automatic capture, `Square-Version` header, injectable
`randomId` for default idempotency keys.
- ADDED: `MollieProvider` — Mollie Payments v2 adapter, Bearer auth,
string-based amount marshalling (`"50.00"`), redirect URL handling.
- ADDED: `AdyenProvider` — Adyen Checkout v71 adapter, `X-API-Key`
header, merchant-account context, injectable `randomId`.
- ADDED: `httpRequest` — universal `FetchLike` wrapper with timeout
via `AbortSignal`, JSON serialisation, typed errors.
- ADDED: `PAYMENT_PROVIDER_CODES` registry via
`defineCodes("PAYMENT_PROVIDER", [...])` — 4 codes (`HTTP_ERROR`,
`TIMEOUT`, `PARSE_ERROR`, `OAUTH_TOKEN_FAILURE`).
- ADDED: `PaymentProviderError extends SuaveplanError<PaymentProviderErrorCode>`
carrying optional `provider`, `httpStatus`, `bodyExcerpt`, and `url`
discriminants; factories `httpError`, `timeoutError`, `parseError`,
`oauthTokenFailure`.
- ADDED: `FetchLike` type alias re-exported from
`@suaveplan/payment-providers/http/http`.
- ADDED: Subpath exports per public module — 9 entries covering every
feature; tree-shakeable.
## Out of scope
- **`@suaveplan/idempotency` integration.** Providers accept
`idempotencyKey` and forward it to the provider; auto-enforcement
via the `@suaveplan/idempotency` middleware is a follow-up change
once that package lands.
- **OTel spans / counters.** The enterprise audit calls for per-provider
observability; the genesis `@suaveplan/payment` migration also
deferred this. Wiring spans here without spans in the engine creates
asymmetry. Defer until both packages land OTel together.
- **Webhook signature verification.** Each provider supports webhook
signing (Stripe HMAC, Adyen HMAC, PayPal cert chain, etc.). The
upstream package accepts `webhookSecret` / `webhookHmacKey` config
fields but does NOT implement verification — that lives in
`@suaveplan/payment`'s `WebhookVerifier`. The config fields are
preserved as opaque pass-throughs so future verifier integration
has the secret material it needs.
- **Schema-registry integration.** The `PaymentProvider` interface is
validated at the type level; runtime Zod schemas around provider
inputs / outputs are a `@suaveplan/payment` concern.
- **Adyen notification HMAC.** The `webhookHmacKey` config field is
preserved verbatim from upstream but the matching HMAC verifier ships
with `@suaveplan/payment`'s `WebhookVerifier`, not here.
## Source mapping
This change derives from `../suaveplan/enterprise/payment-providers.md`.
| Requirement | Source lines | Notes |
|---|---|---|
| Universal-tier purity (no Node / browser / React) | L37 | verbatim |
| Structured errors extending `@suaveplan/error` | L42, L61 | verbatim |
| 100% test coverage / Istanbul | L50 | verbatim |
| 100% docs coverage with sibling `.md` per symbol | L51 | verbatim |
| File-size budget 250 soft / 300 hard / 500 test | L52 | verbatim |
| No mocks — real services / injectable seams | L53 | verbatim |
| No re-exports of external deps | L54 | verbatim |
| `sideEffects: false` + tree-shake friendly | L55 | verbatim |
| `workspace:^` internal / catalog external deps | L56 | verbatim |
| Biome clean | L58 | verbatim |
| TypeScript strict; no `any` / `ts-ignore` | L59 | verbatim |
| Five providers implementing `PaymentProvider` (Stripe, PayPal, Square, Mollie, Adyen) | L10-L13 (Purpose), upstream `src/index.ts` L1-L7 | verbatim — preserve full public surface |
| Cancellation via `AbortSignal` | L44 | verbatim |
| Deterministic clock / RNG | L41 | enhancement above upstream — providers gain injectable `now` / `randomId` / `base64Encode` |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| Injectable `FetchLike` per provider | Required to satisfy `CLAUDE.md` rule 2 ("NEVER use mocks") while preserving 100% coverage. Upstream tests `vi.spyOn(globalThis, "fetch")`; genesis cannot. |
| Folder-per-provider with sibling `.md` | Required by `CLAUDE.md` "Package Structure (library packages)" — five files per feature module. Upstream uses a flat `src/<provider>.ts` layout. |
| `PAYMENT_PROVIDER_CODES` registry (4 codes) | Required by `CLAUDE.md` rule 8 + enterprise §3 "Structured errors". Upstream `httpRequest` throws raw `Error`; genesis upgrades to typed. |
| Subpath exports per provider | Required by `openspec/AGENTS.md` §6 — every feature module owns an `exports` entry for tree-shaking. |
## Verification approach
Every requirement in the spec delta is verified by tests in the package's `src/<feature>/<feature>.test.ts` files plus a cross-cutting tier-purity grep test in `tests/dep-graph.test.ts`. Each provider's test file uses a real `FetchLike` (recording adapter) — never `vi.spyOn` / `vi.mock` — and walks every method (`createIntent`, `confirmIntent`, `captureIntent`, `cancelIntent`, `refund`, `getIntent`) plus the full status-mapping matrix.
- `src/http/http.test.ts` — covers `httpRequest`, timeout, error wrapping, typed `PaymentProviderError`
- `src/error-codes/error-codes.test.ts` — every code reachable, cause propagation, discriminants
- `src/providers/stripe/stripe.test.ts` — `StripeProvider` full surface + status / refund-reason mapping
- `src/providers/paypal/paypal.test.ts` — `PayPalProvider` full surface + OAuth token cache + refresh
- `src/providers/square/square.test.ts` — `SquareProvider` full surface + `randomId` injection
- `src/providers/mollie/mollie.test.ts` — `MollieProvider` full surface + amount marshalling
- `src/providers/adyen/adyen.test.ts` — `AdyenProvider` full surface + merchant-account context
- `tests/dep-graph.test.ts` — universal-tier purity grep (no `node:*` / `bun:*` / `fs` / `path` imports)
## Migration impact
None — additive only. No genesis consumer imports `@suaveplan/payment-providers` yet. Future commerce-tier consumers (`@suaveplan/api-plugin-payment`, `@suaveplan/booking`) will import these providers by their public class names — every upstream export is preserved.
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/payment-providers` to enumerate. No dependents on the genesis tree yet (only `@suaveplan/payment` is published; downstream consumers are still pending). The change is additive.
- No dependents (verified by `bunx turbo ls --filter=...@suaveplan/payment-providers` — empty result).
## Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
| `StripeProvider` (createIntent, confirmIntent, captureIntent, cancelIntent, refund, getIntent) | yes | yes | full surface preserved |
| `PayPalProvider` + OAuth token cache | yes | yes | now injectable `now()` for TTL + `base64Encode` for Basic header |
| `SquareProvider` | yes | yes | now injectable `randomId` for default idempotency keys |
| `MollieProvider` | yes | yes | full surface preserved |
| `AdyenProvider` + merchant-account context | yes | yes | now injectable `randomId` |
| `httpRequest` helper | yes | yes | now raises typed `PaymentProviderError` instead of raw `Error`; accepts injectable `FetchLike` |
| `ApiKeyConfig` / `OAuthConfig` / `MerchantKeyConfig` / `ProviderResponse` / `HttpOptions` types | yes | yes | full surface preserved + added `fetch`, `now`, `randomId`, `base64Encode` optional fields |
| `vi.spyOn(globalThis, "fetch")` in tests | yes | replaced | uses real `FetchLike` recorder per CLAUDE.md rule 2 |
| Webhook HMAC verifier | scoped out | scoped out | upstream defers to `@suaveplan/payment` `WebhookVerifier`; genesis preserves the config fields verbatim |
| OTel spans / counters | absent | absent | deferred per `## Out of scope` to align with `@suaveplan/payment` |
| `@suaveplan/idempotency` middleware | absent | absent | deferred — pass-through preserved |