first commit
This commit is contained in:
201
docs/money/payment/payment-provider-stripe/src/stripe/stripe.md
Normal file
201
docs/money/payment/payment-provider-stripe/src/stripe/stripe.md
Normal file
@@ -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