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,135 @@
# 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.

View File

@@ -0,0 +1,258 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: payment-provider-stripe (add-payment-provider-stripe)
**Package**: `@suaveplan/payment-provider-stripe`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at
> authoring time. This is the inaugural delta for the
> `payment-provider-stripe` capability — every block is `## ADDED
> Requirements`.
## ADDED Requirements
### Requirement: StripeProvider implements PaymentProvider with literal name
The package SHALL export a `StripeProvider` class that implements the
`PaymentProvider` interface from `@suaveplan/payment`. The class SHALL
expose a `readonly name` field whose value is the string literal
`"stripe"` so consumers (including `ProviderRegistry`) can discriminate
it at both the type and value level. The constructor SHALL accept a
`StripeConfig` extending `ApiKeyConfig` from
`@suaveplan/payment-providers` — `apiKey` (Stripe secret key,
required), optional `baseUrl` (defaults to `https://api.stripe.com/v1`),
optional `webhookSecret`, plus every `ProviderCommonOptions` seam
(`fetch`, `timeoutMs`, `signal`).
#### Scenario: name is the literal "stripe"
- **WHEN** a `StripeProvider` instance is constructed with any config
- **THEN** `provider.name === "stripe"` and the field is typed as the literal `"stripe"`
#### Scenario: defaults to api.stripe.com when baseUrl omitted
- **WHEN** `new StripeProvider({ apiKey: "sk_test_x" })` is constructed and any method is invoked
- **THEN** the URL passed to the injected `FetchLike` starts with `https://api.stripe.com/v1`
**Verified by:** `src/stripe/stripe.test.ts::"exposes literal name 'stripe'"` AND `src/stripe/stripe.test.ts::"defaults baseUrl to api.stripe.com/v1 when omitted"`
---
### Requirement: All six PaymentProvider methods route through slim-core httpRequest
The class SHALL implement every method on the `PaymentProvider`
contract — `createIntent`, `confirmIntent`, `captureIntent`,
`cancelIntent`, `refund`, `getIntent` — and every implementation SHALL
delegate its single HTTP call to `httpRequest` from
`@suaveplan/payment-providers`. The provider SHALL forward the
caller-supplied `fetch`, `signal`, and `timeoutMs` from its
constructor `StripeConfig` to every `httpRequest` call so external
cancellation and timeout overrides flow through unchanged. Every
`httpRequest` call SHALL set `provider: "stripe"` on its options so
raised `PaymentProviderError`s carry the Stripe discriminant.
#### Scenario: createIntent POSTs to /payment_intents
- **WHEN** `createIntent({ amount: { amount: 5000n, currency: "USD" }, captureMethod: "manual", customerId: "cust_1", description: "Test", metadata: { k: "v" }, paymentMethodId: "pm_1" })` is called
- **THEN** the injected `FetchLike` receives one call with `method: "POST"`, URL ending `/payment_intents`, JSON body containing the Stripe-cased fields (`amount: 5000`, `currency: "usd"`, `capture_method: "manual"`, `customer: "cust_1"`, `description: "Test"`, `metadata: { k: "v" }`, `payment_method: "pm_1"`)
#### Scenario: confirmIntent POSTs to /payment_intents/:id/confirm
- **WHEN** `confirmIntent("pi_123")` is called
- **THEN** the injected `FetchLike` receives one call with URL ending `/payment_intents/pi_123/confirm`
#### Scenario: captureIntent POSTs to /payment_intents/:id/capture
- **WHEN** `captureIntent("pi_123")` is called
- **THEN** the injected `FetchLike` receives one call with URL ending `/payment_intents/pi_123/capture`
#### Scenario: cancelIntent POSTs to /payment_intents/:id/cancel
- **WHEN** `cancelIntent("pi_123")` is called
- **THEN** the injected `FetchLike` receives one call with URL ending `/payment_intents/pi_123/cancel`
#### Scenario: refund POSTs to /refunds
- **WHEN** `refund({ paymentIntentId: "pi_123", amount: { amount: 2000n, currency: "USD" }, reason: "requested_by_customer", metadata: {} })` is called
- **THEN** the injected `FetchLike` receives one call with URL ending `/refunds`, body `payment_intent: "pi_123"`, `amount: 2000`, `reason: "requested_by_customer"`
#### Scenario: getIntent GETs /payment_intents/:id
- **WHEN** `getIntent("pi_123")` is called
- **THEN** the injected `FetchLike` receives one call with `method: "GET"`, URL ending `/payment_intents/pi_123`
#### Scenario: provider discriminant flows to raised errors
- **WHEN** the injected `FetchLike` returns a 500 response on any method
- **THEN** the rejection is a `PaymentProviderError` whose `code === PAYMENT_PROVIDER.HTTP_ERROR` and `provider === "stripe"`
**Verified by:** `src/stripe/stripe.test.ts::"createIntent POSTs to /payment_intents with Stripe-cased body fields"` AND `src/stripe/stripe.test.ts::"confirmIntent POSTs to /payment_intents/:id/confirm"` AND `src/stripe/stripe.test.ts::"captureIntent POSTs to /payment_intents/:id/capture"` AND `src/stripe/stripe.test.ts::"cancelIntent POSTs to /payment_intents/:id/cancel"` AND `src/stripe/stripe.test.ts::"refund POSTs to /refunds with snake_case body"` AND `src/stripe/stripe.test.ts::"getIntent GETs /payment_intents/:id"` AND `src/stripe/stripe.test.ts::"surfaces non-ok responses as PaymentProviderError with provider 'stripe'"`
---
### Requirement: Bearer auth header on every request
Every HTTP call originated by `StripeProvider` SHALL include an
`Authorization: Bearer <apiKey>` header where `<apiKey>` is the
constructor-supplied secret. This header SHALL be present on read
calls (`getIntent`) as well as mutating calls
(`createIntent`/`confirmIntent`/`captureIntent`/`cancelIntent`/`refund`).
#### Scenario: Bearer header present on every method
- **WHEN** any `PaymentProvider` method is invoked
- **THEN** the `Authorization` header on the recorded fetch call equals `Bearer <constructor-apiKey>`
**Verified by:** `src/stripe/stripe.test.ts::"includes Bearer Authorization header on every method"`
---
### Requirement: Idempotency-Key forwarded when caller supplies it
When the caller supplies an `idempotencyKey` on `CreatePaymentIntentInput`
or `RefundInput`, `StripeProvider` SHALL forward it as an
`Idempotency-Key` header on the matching `POST` request. When the
caller omits `idempotencyKey`, no `Idempotency-Key` header SHALL be
present on the request. Read-only requests (`getIntent`) SHALL never
carry an `Idempotency-Key` header (the Stripe API ignores it on `GET`).
#### Scenario: createIntent forwards Idempotency-Key when supplied
- **WHEN** `createIntent({ ..., idempotencyKey: "idem_1" })` is called
- **THEN** the recorded fetch call carries an `Idempotency-Key: idem_1` header
#### Scenario: createIntent omits Idempotency-Key when not supplied
- **WHEN** `createIntent({ ... })` is called without `idempotencyKey`
- **THEN** the recorded fetch call carries no `Idempotency-Key` header
#### Scenario: refund forwards Idempotency-Key when supplied
- **WHEN** `refund({ ..., idempotencyKey: "ref_idem_1" })` is called
- **THEN** the recorded fetch call carries an `Idempotency-Key: ref_idem_1` header
#### Scenario: refund omits Idempotency-Key when not supplied
- **WHEN** `refund({ paymentIntentId: "pi_123" })` is called without `idempotencyKey`
- **THEN** the recorded fetch call carries no `Idempotency-Key` header
**Verified by:** `src/stripe/stripe.test.ts::"forwards Idempotency-Key header on createIntent when supplied"` AND `src/stripe/stripe.test.ts::"omits Idempotency-Key header on createIntent when not supplied"` AND `src/stripe/stripe.test.ts::"forwards Idempotency-Key header on refund when supplied"` AND `src/stripe/stripe.test.ts::"omits Idempotency-Key header on refund when not supplied"`
---
### Requirement: Stripe payment-intent status mapping
`StripeProvider` SHALL map Stripe payment-intent status strings to
`PaymentStatus` enum values using the following exhaustive table:
| Stripe status | PaymentStatus |
|---|---|
| `requires_payment_method` | `created` |
| `requires_confirmation` | `created` |
| `requires_action` | `pending` |
| `processing` | `pending` |
| `requires_capture` | `authorized` |
| `succeeded` | `captured` |
| `canceled` | `canceled` |
| _any other / unknown_ | `failed` |
#### Scenario: every documented mapping resolves correctly
- **WHEN** the Stripe API returns a payment intent whose `status` is any of the eight rows above
- **THEN** the mapped `PaymentIntent.status` equals the right-hand column
**Verified by:** `src/stripe/stripe.test.ts::"maps Stripe status 'requires_payment_method' to 'created'"` AND `src/stripe/stripe.test.ts::"maps Stripe status 'requires_confirmation' to 'created'"` AND `src/stripe/stripe.test.ts::"maps Stripe status 'requires_action' to 'pending'"` AND `src/stripe/stripe.test.ts::"maps Stripe status 'processing' to 'pending'"` AND `src/stripe/stripe.test.ts::"maps Stripe status 'requires_capture' to 'authorized'"` AND `src/stripe/stripe.test.ts::"maps Stripe status 'succeeded' to 'captured'"` AND `src/stripe/stripe.test.ts::"maps Stripe status 'canceled' to 'canceled'"` AND `src/stripe/stripe.test.ts::"maps unknown Stripe status to 'failed'"`
---
### Requirement: Stripe refund status mapping
`StripeProvider` SHALL map Stripe refund status strings to
`RefundStatus` enum values using the following exhaustive table:
| Stripe refund status | RefundStatus |
|---|---|
| `succeeded` | `succeeded` |
| `pending` | `pending` |
| _any other / unknown_ | `failed` |
#### Scenario: every documented mapping resolves correctly
- **WHEN** the Stripe API returns a refund whose `status` is any of the three rows above
- **THEN** the mapped `Refund.status` equals the right-hand column
**Verified by:** `src/stripe/stripe.test.ts::"maps Stripe refund status 'succeeded' to 'succeeded'"` AND `src/stripe/stripe.test.ts::"maps Stripe refund status 'pending' to 'pending'"` AND `src/stripe/stripe.test.ts::"maps unknown Stripe refund status to 'failed'"`
---
### Requirement: Stripe refund reason mapping
`StripeProvider` SHALL map Stripe refund reason strings to
`RefundReason` enum values using the following exhaustive table:
| Stripe reason | RefundReason |
|---|---|
| `duplicate` | `duplicate` |
| `fraudulent` | `fraudulent` |
| `requested_by_customer` | `requested_by_customer` |
| _any other / unknown_ | `other` |
When the Stripe refund object omits `reason` entirely, the mapped
`Refund.reason` SHALL be `"other"`.
#### Scenario: every documented mapping resolves correctly
- **WHEN** the Stripe API returns a refund whose `reason` is any of the listed values, an unknown value, or absent
- **THEN** the mapped `Refund.reason` equals the right-hand column (or `"other"` when absent)
**Verified by:** `src/stripe/stripe.test.ts::"maps Stripe refund reason 'duplicate' to 'duplicate'"` AND `src/stripe/stripe.test.ts::"maps Stripe refund reason 'fraudulent' to 'fraudulent'"` AND `src/stripe/stripe.test.ts::"maps Stripe refund reason 'requested_by_customer' to 'requested_by_customer'"` AND `src/stripe/stripe.test.ts::"maps unknown Stripe refund reason to 'other'"` AND `src/stripe/stripe.test.ts::"maps absent Stripe refund reason to 'other'"`
---
### Requirement: Payment-method mapping (card → wallet → absent)
When the Stripe payment intent carries a `payment_method` object,
`StripeProvider` SHALL surface it on the returned `PaymentIntent` as
a `PaymentMethod` with `id`, `type` (`"card"` if Stripe reports a card
type, otherwise `"wallet"`), `brand` (from `payment_method.card.brand`
when present), and `last4` (from `payment_method.card.last4` when
present). When the Stripe payment intent omits `payment_method`, the
mapped `PaymentIntent.paymentMethod` SHALL be `undefined`.
#### Scenario: card payment method is mapped to type 'card' with brand and last4
- **WHEN** the Stripe API returns a payment intent with `payment_method: { id: "pm_1", type: "card", card: { brand: "visa", last4: "4242" } }`
- **THEN** the mapped `PaymentIntent.paymentMethod` equals `{ id: "pm_1", type: "card", brand: "visa", last4: "4242" }`
#### Scenario: non-card payment method maps to type 'wallet'
- **WHEN** the Stripe API returns a payment intent with `payment_method: { id: "pm_w", type: "us_bank_account" }`
- **THEN** the mapped `PaymentIntent.paymentMethod.type` equals `"wallet"`
#### Scenario: absent payment method maps to undefined
- **WHEN** the Stripe API returns a payment intent without `payment_method`
- **THEN** the mapped `PaymentIntent.paymentMethod` is `undefined`
**Verified by:** `src/stripe/stripe.test.ts::"maps card payment_method with brand and last4"` AND `src/stripe/stripe.test.ts::"maps non-card payment_method type to 'wallet'"` AND `src/stripe/stripe.test.ts::"maps absent payment_method to undefined"`
---
### Requirement: Universal-tier purity
The package SHALL ship as a universal-tier package — no `node:*`,
`bun:*`, `fs`, `path`, `stream`, `net`, `http`, or bare `crypto`
imports in any file under `src/`. The package SHALL run unchanged
under Node ≥ 18, Bun, modern browsers, and Web Workers.
`package.json` SHALL declare `"sideEffects": false`. Every HTTP call
SHALL go through the slim-core `httpRequest` helper (and therefore
through the injectable `FetchLike`) so the package never reaches into
a runtime-specific HTTP stack.
#### Scenario: no platform imports in src/
- **WHEN** the dep-graph test walks every `.ts` file under `src/` and matches the banned-import regex
- **THEN** the resulting offender array is empty
**Verified by:** `tests/dep-graph.test.ts::"src contains no node/bun/fs/path/stream/net/http/crypto imports"`

View File

@@ -0,0 +1,94 @@
# Tasks: add-payment-provider-stripe
> Every checkbox reflects work actually done in this change. No
> retroactive ticking.
## 1. Spec authoring
- [x] 1.1 Read `../suaveplan/enterprise/payment-providers.md` end-to-end
- [x] 1.2 Read `openspec/specs/payment-providers/spec.md` (slim-core canonical spec)
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14
- [x] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md`
- [x] 1.5 Fill in `## Source mapping` — every requirement cites a line
range in the upstream source or appears in the "no source line"
table with justification
- [x] 1.6 Draft `specs/payment-provider-stripe/spec.md` from
`openspec/templates/spec-delta.md`
- [x] 1.7 Every requirement in the spec delta carries a
`**Verified by:**` line naming concrete `it(...)` titles
## 2. Implementation
- [x] 2.1 Scaffold `packages/universal/payment-provider-stripe/`
(`package.json` at `0.1.0`, `vite.config.ts`, `vitest.config.ts`,
`tsconfig.json`, `tsconfig.lib.json`)
- [x] 2.2 Implement `src/stripe/stripe.types.ts` — `StripeConfig`,
`StripeIntent`, `StripeRefund` wire-format types
- [x] 2.3 Implement `src/stripe/stripe.ts` — `StripeProvider` class,
every `PaymentProvider` method routed through `httpRequest` with
Bearer auth + `Idempotency-Key` forwarding, mappers from Stripe
domain to `PaymentIntent` / `Refund`, every error raised through
the slim-core `PaymentProviderError`
- [x] 2.4 `src/stripe/index.ts` — explicit named barrel
- [x] 2.5 `src/index.ts` — package root barrel re-exporting
`StripeProvider` + `StripeConfig`
- [x] 2.6 `package.json#exports` — root + `./stripe/stripe` subpath
## 3. Tests
- [x] 3.1 `src/stripe/stripe.test.ts` — covers every public method,
Bearer-auth header presence, `Idempotency-Key` forwarding when
caller supplies it AND absence when caller omits, every status /
refund-status / refund-reason branch, payment-method absence,
`provider: "stripe"` discriminant propagation on raised errors,
injected `FetchLike` + caller `signal` forwarding
- [x] 3.2 `tests/dep-graph.test.ts` — pins universal-tier purity (no
`node:` / `bun:` / `fs` / `path` / `stream` / `net` / `http` /
`crypto` imports under `src/`)
- [x] 3.3 Every `it(...)` carries `{ timeout: 5000 }`
- [x] 3.4 Zero `vi.mock` / `vi.stubGlobal` / `jest.mock` — all tests
use real recording `FetchLike` closures
## 4. Documentation
- [x] 4.1 `src/stripe/stripe.md` — sibling doc with Purpose,
Features, Basic Usage, Advanced Usage, API Reference,
Implementation Notes (≥200 words excl. code)
- [x] 4.2 `README.md` at package root linking the module doc under
`## Modules`
- [x] 4.3 Update `CLAUDE.md` package listing under the Network or
Foundation section — add `payment-provider-stripe` to the
`payment-providers` family description
## 5. Pre-archive verification (the 5-gate triple)
- [x] 5.1 `bun run check` — Biome 0/0/0 across the monorepo
- [x] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/payment-provider-stripe` — green
- [x] 5.3 `bunx turbo test:coverage --filter=@suaveplan/payment-provider-stripe` — 100/100/100/100
- [x] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/payment-provider-stripe` — exit 0 (PASS 0 error, 0 warn, 0 info)
- [x] 5.5 `bunx turbo build --filter=@suaveplan/payment-provider-stripe` — green
## 6. Pre-archive 9-row checklist (AGENTS.md Section 13)
- [x] 6.1 Generics threaded; literal `name: "stripe"` pinned at the type level
- [x] 6.2 Wire-format Stripe responses parsed through typed `StripeIntent` / `StripeRefund` interfaces
- [x] 6.3 Every slim-core error code raised by `httpRequest` flows through this provider with `provider: "stripe"` discriminant
- [x] 6.4 N/A — no strict-mode toggle on the provider
- [x] 6.5 N/A — no OTel instruments in this change
- [x] 6.6 N/A — provider holds no in-flight dedup state
- [x] 6.7 No retroactive tick
- [x] 6.8 No `export *` in any feature barrel — scan returns zero output
- [x] 6.9 No direct `vitest` / `bun:test` imports — scan returns zero output
## 7. Root-doc refresh (§13b)
- [x] 7.1 `bun scripts/folder-reorg/generate-manifest.ts` — `MANIFEST.md` refreshed (258 packages, 34 plugins; new row for payment-provider-stripe)
- [ ] 7.2 `bun scripts/generate-missing.ts` — DEFERRED: pre-existing failure on missing `FOLDERS.yml` (unrelated to this change)
- [x] 7.3 Added `@suaveplan/payment-provider-stripe` row to `STATUS.md`
- [x] 7.4 Added per-package paragraph to `SUMMARY.md`
- [x] 7.5 `bunx markdownlint-cli2 --fix` — exit 0; no findings in touched files
## 8. Archive
- [x] 8.1 Fold `specs/payment-provider-stripe/spec.md` into `openspec/specs/payment-provider-stripe/spec.md`
- [x] 8.2 Move `openspec/changes/add-payment-provider-stripe/` → `openspec/archive/2026-05-17-add-payment-provider-stripe/`