Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# @suaveplan/payment-provider-stripe - Universal Stripe adapter for @suaveplan/payment. Implements PaymentProvider against the Stripe REST API via the slim @suaveplan/payment-providers foundation: Bearer auth on every call, Idempotency-Key forwarding on every mutating call, exhaustive Stripe status/refund-status/refund-reason mapping, payment-method normalisation, every error raised as PaymentProviderError with provider: 'stripe' discriminant. No Node / browser / React deps; every HTTP call flows through the injectable FetchLike from the slim core.
|
||||
|
||||
Universal Stripe adapter for `@suaveplan/payment`. Implements
|
||||
`PaymentProvider` against the Stripe REST API using the slim
|
||||
`@suaveplan/payment-providers` foundation: Bearer auth on every call,
|
||||
`Idempotency-Key` forwarding on every mutating call, exhaustive
|
||||
Stripe → domain status / refund-status / refund-reason mapping,
|
||||
payment-method normalisation, every error raised as
|
||||
`PaymentProviderError` with `provider: "stripe"` discriminant.
|
||||
|
||||
This package is one of the five sibling adapters that re-landed
|
||||
after `2026-05-17-slim-payment-providers` split the bundled
|
||||
`@suaveplan/payment-providers` into a foundation-with-sibling-plugins
|
||||
shape.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [stripe](./src/stripe/stripe.md) — `StripeProvider` is the Stripe adapter for `@suaveplan/payment`. It implements the `PaymentProvider` contract (six meth…
|
||||
- [stripe](./src/stripe/stripe.md) — `StripeProvider` is the Stripe adapter for `@suaveplan/payment`. It implements the `PaymentProvider` contract (six meth…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/payment-provider-stripe \
|
||||
@suaveplan/payment-providers \
|
||||
@suaveplan/payment
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
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",
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
});
|
||||
```
|
||||
|
||||
## Compose with the foundation registry
|
||||
|
||||
```ts
|
||||
import { ProviderRegistry } from "@suaveplan/payment-providers";
|
||||
import { StripeProvider } from "@suaveplan/payment-provider-stripe";
|
||||
|
||||
const registry = new ProviderRegistry();
|
||||
registry.register(
|
||||
new StripeProvider({ apiKey: process.env.STRIPE_SECRET_KEY ?? "" })
|
||||
);
|
||||
|
||||
const stripe = registry.get("stripe"); // typed as PaymentProvider
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **`StripeProvider`** — implements `PaymentProvider` from
|
||||
`@suaveplan/payment` (literal `name: "stripe"`, six lifecycle
|
||||
methods).
|
||||
- **Bearer authentication** on every request (read + write).
|
||||
- **`Idempotency-Key` forwarding** — when the caller supplies
|
||||
`idempotencyKey` on `CreatePaymentIntentInput` or `RefundInput`,
|
||||
the provider forwards it verbatim to Stripe so retries collapse
|
||||
into a single side effect.
|
||||
- **Exhaustive status mappings** — seven Stripe payment-intent
|
||||
statuses, three Stripe refund statuses, four Stripe refund reasons,
|
||||
every unmapped value falls back to `failed` / `other`.
|
||||
- **Payment-method normalisation** — `card` carries `brand` + `last4`;
|
||||
any other Stripe `type` maps to `wallet`; absent payment method
|
||||
maps to `undefined`.
|
||||
- **Typed errors** — every failure is a `PaymentProviderError` from
|
||||
the slim core with `provider: "stripe"` discriminant.
|
||||
- **Universal tier** — zero Node, browser, React, or
|
||||
platform-specific imports. Runs unchanged on Node, Bun, browsers,
|
||||
and Web Workers.
|
||||
- **Tree-shakeable** — `sideEffects: false` plus per-feature subpath
|
||||
exports.
|
||||
- **100% test coverage** (Istanbul) and **100% docs coverage**.
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/stripe/stripe.md`](./src/stripe/stripe.md) — `StripeProvider`
|
||||
class, `StripeConfig`, `StripeIntent` / `StripeRefund` wire-format
|
||||
interfaces.
|
||||
|
||||
## Related
|
||||
|
||||
- `@suaveplan/payment` — `PaymentEngine`, `WebhookVerifier`,
|
||||
`PaymentProvider` interface, `PaymentIntent` / `Refund` / status
|
||||
enums.
|
||||
- `@suaveplan/payment-providers` — slim foundation with
|
||||
`ProviderRegistry`, `httpRequest`, `composeAbortSignal`,
|
||||
`PaymentProviderError`, shared config shapes.
|
||||
- `@suaveplan/payment-provider-paypal`,
|
||||
`@suaveplan/payment-provider-square`,
|
||||
`@suaveplan/payment-provider-mollie`,
|
||||
`@suaveplan/payment-provider-adyen` — sibling provider packages.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# StripeProvider
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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 `PaymentProvider` from `@suaveplan/payment` — class
|
||||
with literal `name: "stripe"`, all six methods, every return value
|
||||
conforms to `PaymentIntent` / `Refund`.
|
||||
- Routes every HTTP call through the slim-core `httpRequest` helper
|
||||
— inherits per-call timeout, `AbortSignal` composition, typed
|
||||
errors, empty-body handling, JSON parse-error handling.
|
||||
- Bearer authentication on every request (read + write).
|
||||
- `Idempotency-Key` header forwarded verbatim when the caller
|
||||
supplies `idempotencyKey` on `CreatePaymentIntentInput` or
|
||||
`RefundInput`. 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"` with `brand` and `last4`; any other type maps to
|
||||
`"wallet"`; absent payment method maps to `undefined`.
|
||||
- Every raised `PaymentProviderError` carries `provider: "stripe"` so
|
||||
consumers running multiple providers can discriminate without
|
||||
parsing messages.
|
||||
- Universal — no `node:*`, no `bun:*`, no platform-specific
|
||||
imports anywhere under `src/`. Runs unchanged on Node, Bun,
|
||||
browsers, and Web Workers.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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`
|
||||
|
||||
```ts
|
||||
new StripeProvider(config: StripeConfig)
|
||||
```
|
||||
|
||||
- **`name`** — `"stripe"` (literal type, frozen on the class).
|
||||
- **`createIntent(input: CreatePaymentIntentInput): Promise<PaymentIntent>`**
|
||||
— `POST /payment_intents`. Maps `input.amount.amount` to Stripe's
|
||||
minor-unit integer (`Number`), lowercases the currency, forwards
|
||||
`customer` / `description` / `metadata` / `payment_method` when
|
||||
supplied. Forwards `Idempotency-Key` from `input.idempotencyKey`.
|
||||
- **`confirmIntent(intentId): Promise<PaymentIntent>`** — `POST
|
||||
/payment_intents/:id/confirm`. No body, no `Idempotency-Key`.
|
||||
- **`captureIntent(intentId): Promise<PaymentIntent>`** — `POST
|
||||
/payment_intents/:id/capture`. No body, no `Idempotency-Key`.
|
||||
- **`cancelIntent(intentId): Promise<PaymentIntent>`** — `POST
|
||||
/payment_intents/:id/cancel`. No body, no `Idempotency-Key`.
|
||||
- **`refund(input: RefundInput): Promise<Refund>`** — `POST
|
||||
/refunds`. Forwards `amount` / `reason` / `metadata` when supplied.
|
||||
Forwards `Idempotency-Key` from `input.idempotencyKey`.
|
||||
- **`getIntent(intentId): Promise<PaymentIntent>`** — `GET
|
||||
/payment_intents/:id`. No body, no `Idempotency-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 to `https://api.stripe.com/v1`.
|
||||
- `webhookSecret?: string` — surfaced for downstream
|
||||
`WebhookVerifier` wiring; not used by the provider itself.
|
||||
- `fetch?: FetchLike` — defaults to `globalThis.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; `httpStatus`
|
||||
carries the status code, `bodyExcerpt` carries 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-Key`
|
||||
header; the provider just forwards the caller-supplied key.
|
||||
- The status / refund-status / refund-reason mappings are exhaustive
|
||||
and tested per-row. The fallback `failed` / `other` branches are
|
||||
exercised by passing an unmapped Stripe enum value.
|
||||
- `mapPaymentMethod` exists as a small dedicated helper so the
|
||||
card vs. wallet vs. absent branches stay testable in isolation;
|
||||
consumers can rely on `paymentMethod.type === "card"` always
|
||||
carrying `brand` and `last4` when Stripe surfaced them.
|
||||
- The `commonOptions` private accessor exists so every method
|
||||
forwards `fetch`, `signal`, `timeoutMs`, and the `provider` field
|
||||
in lockstep. Calling sites stay symmetric — adding a new
|
||||
`PaymentProvider` method only needs to spread `this.commonOptions()`
|
||||
into the `httpRequest` call.
|
||||
- `package.json` declares `"sideEffects": false` and the only
|
||||
runtime imports are `@suaveplan/payment` (types-only) and
|
||||
`@suaveplan/payment-providers` (one named symbol: `httpRequest`).
|
||||
The dep-graph test pins universal purity.
|
||||
Reference in New Issue
Block a user