first commit
This commit is contained in:
160
docs/api-stack/api/api-plugin-payment/README.md
Normal file
160
docs/api-stack/api/api-plugin-payment/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# @suaveplan/api-plugin-payment - Payment-lifecycle plugin for @suaveplan/api. Wraps @suaveplan/payment + caller-injected PaymentProvider (Stripe/Adyen/PayPal/Square/Mollie/custom) as a @suaveplan/api-plugin definePlugin descriptor. Mounts 6 owner-scoped OpenAPI routes (intent create/list/get/confirm/capture/cancel + refund), a HMAC-verified webhook receiver, an Idempotency-Key middleware, a typed paymentService slot in the host ServiceRegistry, and OTel instruments (5 counters + 1 histogram). Server-tier — runs in Bun and Node.
|
||||
|
||||
Payment-lifecycle plugin for [`@suaveplan/api`](../api). Wraps `@suaveplan/payment` + a caller-injected `PaymentProvider` (Stripe / Adyen / PayPal / Square / Mollie / custom) as a single `definePlugin` consumer that ships:
|
||||
|
||||
- **7 owner-scoped OpenAPI routes** (create / list / get / confirm / capture / cancel / refund) gated behind `requireAuth()` from `@suaveplan/api-plugin-auth`, with cross-user 404 anti-enumeration baked in at the service layer.
|
||||
- **`POST /payments/webhook`** — HMAC-verified webhook receiver that reads the raw body and checks `X-Signature` + `X-Timestamp` BEFORE deserialising the payload.
|
||||
- **`Idempotency-Key` middleware** on `POST /intents` and `POST /intents/:id/refund` so retries never double-charge.
|
||||
- **`paymentService` ServiceRegistry slot** exposing the full lifecycle to downstream plugins with full TypeScript inference.
|
||||
- **OpenTelemetry instrument bundle** — 5 counters + 1 histogram covering intent creation, captures, refunds, webhook delivery, signature failures, and intent-creation latency.
|
||||
- **15 stable `PAYMENT_PLUGIN_*` error codes** with typed `PaymentPluginError` class for programmatic dispatch.
|
||||
|
||||
Server-tier — runs in Bun and Node.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-plugin-payment`. Every failure path in the plugin raises a `PaymentPlugi…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-plugin-payment`. Every failure path in the plugin raises a `PaymentPlugi…
|
||||
- [context-augmentation](./src/middleware/context-augmentation.md) — TypeScript module augmentation that surfaces the Hono `ContextVariableMap` slot `user: Principal | null` to every consu…
|
||||
- [idempotency-middleware](./src/middleware/idempotency-middleware.md) — Hono middleware enforcing the `Idempotency-Key` header on payment-mutation routes (`POST /payments/intents`, `POST /pay…
|
||||
- [otel](./src/otel/instruments.md) — OpenTelemetry instrument bundle for `@suaveplan/api-plugin-payment`. Exposes 5 counters and 1 histogram so operators ca…
|
||||
- [instruments](./src/otel/instruments.md) — OpenTelemetry instrument bundle for `@suaveplan/api-plugin-payment`. Exposes 5 counters and 1 histogram so operators ca…
|
||||
- [plugin](./src/plugin/plugin.md) — Factory + descriptor for the `@suaveplan/api-plugin-payment` Hono plugin. Wraps a caller-injected `PaymentProvider` (St…
|
||||
- [plugin](./src/plugin/plugin.md) — Factory + descriptor for the `@suaveplan/api-plugin-payment` Hono plugin. Wraps a caller-injected `PaymentProvider` (St…
|
||||
- [routes](./src/routes/build-routes.md) — Builds the 7 OpenAPI route entries the plugin mounts for the payment lifecycle (create / list / get / confirm / capture…
|
||||
- [build-routes](./src/routes/build-routes.md) — Builds the 7 OpenAPI route entries the plugin mounts for the payment lifecycle (create / list / get / confirm / capture…
|
||||
- [capture-intent](./src/schemas/routes/capture-intent.md) — Body schema for `POST /payments/intents/:id/capture`. Supports both full capture (empty body) and partial capture (body…
|
||||
- [create-intent](./src/schemas/routes/create-intent.md) — Body and response schemas for `POST /payments/intents`. The request body carries the amount (typed `MoneySchema`), opti…
|
||||
- [empty-body](./src/schemas/routes/empty-body.md) — Strict empty-object body schema shared by `POST /payments/intents/:id/confirm` and `POST /payments/intents/:id/cancel`.…
|
||||
- [intent-id-param](./src/schemas/routes/intent-id-param.md) — Schema for the `:id` path parameter shared by every per-intent route (`GET /intents/:id`, `POST /intents/:id/confirm`,…
|
||||
- [list-intents](./src/schemas/routes/list-intents.md) — Query and response schemas for `GET /payments/intents`. The route lists intents owned by the authenticated principal, o…
|
||||
- [payment-intent](./src/schemas/routes/payment-intent.md) — Wire-shape Zod schemas for the serialised `PaymentIntent`, `Refund`, and `PaymentStatus` types exposed by every route i…
|
||||
- [refund](./src/schemas/routes/refund.md) — Body + response schemas for `POST /payments/intents/:id/refund`. Supports full or partial refunds with optional `reason…
|
||||
- [webhook](./src/schemas/routes/webhook.md) — Response schema for `POST /payments/webhook`. The webhook receiver MUST read the raw request body and verify the provid…
|
||||
- [money](./src/schemas/money.md) — Boundary Zod schema for `MoneyLike` — `{ amount: bigint, currency: string }`. Payment APIs work in minor units (cents,…
|
||||
- [options](./src/schemas/options.md) — Zod schema validating `PaymentPluginOptions` at the `createPaymentPlugin` factory boundary. The schema runs once at con…
|
||||
- [payment-service](./src/services/payment-service.md) — Owner-scoped lifecycle service that wraps the caller-injected `PaymentProvider` with an ownership map (`PaymentIntentSt…
|
||||
- [registry-augmentation](./src/services/registry-augmentation.md) — TypeScript module augmentation that adds the `paymentService` slot to `@suaveplan/api-plugin/service-registry`'s `Servi…
|
||||
- [memory-store](./src/store/memory-store.md) — In-process default implementation of the `PaymentIntentStore` contract. Stores owner records in a `Map<string, OwnedInt…
|
||||
- [payment-intent-store](./src/store/payment-intent-store.md) — Persistence contract for the intent-id → owner map every payment plugin needs to enforce per-user scoping on lookups, l…
|
||||
- [types](./src/types/types.md) — Public type surface for `@suaveplan/api-plugin-payment`. Declares the two top-level interfaces every consumer sees: `Pa…
|
||||
- [types](./src/types/types.md) — Public type surface for `@suaveplan/api-plugin-payment`. Declares the two top-level interfaces every consumer sees: `Pa…
|
||||
- [webhook](./src/webhook/verify-webhook.md) — Wrapper around `@suaveplan/payment`'s `WebhookVerifier` tailored for the plugin's webhook route. Reads the request's `X…
|
||||
- [verify-webhook](./src/webhook/verify-webhook.md) — Wrapper around `@suaveplan/payment`'s `WebhookVerifier` tailored for the plugin's webhook route. Reads the request's `X…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/api-plugin-payment
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { defineApp } from "@suaveplan/api";
|
||||
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
|
||||
import { createPaymentPlugin } from "@suaveplan/api-plugin-payment";
|
||||
import { StripeProvider } from "@suaveplan/payment-provider-stripe";
|
||||
|
||||
const app = defineApp()
|
||||
.use(createAuthPlugin({ betterAuth: { /* ... */ } }))
|
||||
.use(
|
||||
createPaymentPlugin({
|
||||
provider: new StripeProvider({ apiKey: process.env.STRIPE_KEY! }),
|
||||
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
The plugin mounts under `/payments` by default. Override with `routePrefix`.
|
||||
|
||||
## The 3-way plug-and-play contract
|
||||
|
||||
`@suaveplan/api-plugin-payment` is the canonical **payment** exemplar of the contract every `@suaveplan/api-plugin-*` package follows:
|
||||
|
||||
1. **Boundary Zod schemas** — every request body, query, and path parameter validates through a schema exported at `./schemas/*` so consumers and the plugin share one source of truth.
|
||||
2. **OpenAPI route registration** — every route is built with `RouteBuilder` from `@suaveplan/api-openapi` and registered via `definePluginRoutes`, so the host's `/openapi.json` describes the payment surface with the same Zod-derived types the code accepts.
|
||||
3. **ServiceRegistry slot** — the `paymentService` slot exposes the lifecycle to downstream plugins without forcing them to re-implement provider plumbing.
|
||||
|
||||
## Idempotency
|
||||
|
||||
`POST /payments/intents` and `POST /payments/intents/:id/refund` honour the `Idempotency-Key` header. Retrying the same request with the same key and body returns the cached response without invoking the provider; retrying with a different body raises HTTP 409.
|
||||
|
||||
```ts
|
||||
const res = await fetch("/payments/intents", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"Idempotency-Key": `order-${orderId}`,
|
||||
},
|
||||
body: JSON.stringify({ amount: { amount: "2500", currency: "USD" } }),
|
||||
});
|
||||
```
|
||||
|
||||
## Webhook verification
|
||||
|
||||
The receiver expects:
|
||||
|
||||
- `X-Signature` — hex-encoded HMAC of `${timestamp}.${body}` using the configured `webhookSecret`.
|
||||
- `X-Timestamp` — Unix ms integer.
|
||||
|
||||
Tolerance window defaults to 5 minutes (`webhookToleranceMs`). Algorithm defaults to `SHA-256` (`webhookAlgorithm: "SHA-256" | "SHA-384" | "SHA-512"`).
|
||||
|
||||
```ts
|
||||
import { signPaymentWebhook } from "@suaveplan/api-plugin-payment";
|
||||
|
||||
const body = JSON.stringify(event);
|
||||
const ts = Date.now();
|
||||
const sig = signPaymentWebhook({
|
||||
body,
|
||||
timestamp: ts,
|
||||
secret: process.env.STRIPE_WEBHOOK_SECRET!,
|
||||
algorithm: "SHA-256",
|
||||
});
|
||||
await fetch("/payments/webhook", {
|
||||
method: "POST",
|
||||
headers: { "X-Signature": sig, "X-Timestamp": String(ts) },
|
||||
body,
|
||||
});
|
||||
```
|
||||
|
||||
## Pluggable storage
|
||||
|
||||
The default in-process `MemoryPaymentIntentStore` records the intent-id → owner map. Inject a SurrealDB / Postgres / Redis adapter for production:
|
||||
|
||||
```ts
|
||||
import { createPaymentPlugin } from "@suaveplan/api-plugin-payment";
|
||||
|
||||
const plugin = createPaymentPlugin({
|
||||
provider,
|
||||
webhookSecret,
|
||||
store: new MySurrealPaymentIntentStore({ db }),
|
||||
});
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [`./src/plugin/plugin.md`](./src/plugin/plugin.md) — factory + 6-step `onInit`.
|
||||
- [`./src/services/payment-service.md`](./src/services/payment-service.md) — owner-scoped lifecycle.
|
||||
- [`./src/services/registry-augmentation.md`](./src/services/registry-augmentation.md) — `ServiceRegistry["paymentService"]` slot.
|
||||
- [`./src/middleware/idempotency-middleware.md`](./src/middleware/idempotency-middleware.md) — Idempotency-Key middleware.
|
||||
- [`./src/webhook/verify-webhook.md`](./src/webhook/verify-webhook.md) — HMAC verifier + signer.
|
||||
- [`./src/routes/build-routes.md`](./src/routes/build-routes.md) — 7 owner-scoped routes.
|
||||
- [`./src/store/payment-intent-store.md`](./src/store/payment-intent-store.md) — store contract.
|
||||
- [`./src/store/memory-store.md`](./src/store/memory-store.md) — in-process default.
|
||||
- [`./src/schemas/money.md`](./src/schemas/money.md) — `Money` boundary schema.
|
||||
- [`./src/schemas/options.md`](./src/schemas/options.md) — `PaymentPluginOptionsSchema`.
|
||||
- [`./src/schemas/routes/payment-intent.md`](./src/schemas/routes/payment-intent.md) — intent/refund wire shape.
|
||||
- [`./src/schemas/routes/create-intent.md`](./src/schemas/routes/create-intent.md) — `POST /intents` body.
|
||||
- [`./src/schemas/routes/intent-id-param.md`](./src/schemas/routes/intent-id-param.md) — `:id` path param.
|
||||
- [`./src/schemas/routes/list-intents.md`](./src/schemas/routes/list-intents.md) — `GET /intents` query.
|
||||
- [`./src/schemas/routes/capture-intent.md`](./src/schemas/routes/capture-intent.md) — capture body.
|
||||
- [`./src/schemas/routes/refund.md`](./src/schemas/routes/refund.md) — refund body + response.
|
||||
- [`./src/schemas/routes/empty-body.md`](./src/schemas/routes/empty-body.md) — empty-body schema.
|
||||
- [`./src/schemas/routes/webhook.md`](./src/schemas/routes/webhook.md) — webhook response.
|
||||
- [`./src/otel/instruments.md`](./src/otel/instruments.md) — 5 counters + 1 histogram.
|
||||
- [`./src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — 15 `PAYMENT_PLUGIN_*` codes.
|
||||
- [`./src/types/types.md`](./src/types/types.md) — `PaymentPluginOptions` / `PaymentPluginConfig`.
|
||||
|
||||
Reference in New Issue
Block a user