Files
WRNexusJS/packages/payment/README.md
T

85 lines
4.0 KiB
Markdown

# @wrnexus/payment
Gateway-neutral, webhook-authoritative payments for WrNexus. Amounts are integer minor units. Every initialize and refund operation requires an idempotency key. The package never accepts or stores card numbers, CVVs, or expiry input; its components navigate to provider-hosted checkout.
Built-in adapters: offline `sandbox`, Stripe, Razorpay, and PayPal. Applications may add adapters with `defineGateway`. Capabilities are explicit, unsupported operations are refused, signed webhooks are mandatory in production, and package-owned migrations provision the append-only event ledger.
Webhook events are deduplicated by the provider's event ID and reduced in provider occurrence/sequence order. Captured and refund states are monotonic, so a delayed retry of an older event cannot regress a terminal payment.
Configure any number of named gateway accounts in `wrnexus.config.ts`; keep credentials in environment variables:
```ts
const payment = {
default: "razorpayIndia",
gateways: {
razorpayIndia: {
provider: "razorpay",
keyId: process.env.RAZORPAY_KEY_ID!,
keySecret: process.env.RAZORPAY_KEY_SECRET!,
webhookSecret: process.env.RAZORPAY_WEBHOOK_SECRET ?? "",
},
stripeGlobal: {
provider: "stripe",
secretKey: process.env.STRIPE_SECRET_KEY!,
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
},
},
} satisfies PaymentConfig;
```
At runtime, call `configurePaymentFromConfig(config.payment, databasePaymentStore())` once. Pass a configured gateway name to `initializePayment` to override the default for a specific checkout.
## Package APIs
When a modern `gateways` configuration is present, the plugin contributes these routes:
- `GET /api/payments/providers` — client-safe names, capabilities, currencies, and countries
- `GET|POST /api/payments/intents` — list the authenticated subject's payments or initialize one
- `GET /api/payments/intents/[id]` — status and client-safe checkout configuration
- `POST /api/payments/intents/[id]/sync`
- `POST /api/payments/intents/[id]/capture`
- `POST /api/payments/intents/[id]/cancel`
- `POST /api/payments/intents/[id]/refund`
- `GET /api/payments/methods`
- `POST /api/payments/reconcile`
- `POST /api/payments/webhook/:gateway`
Provider secrets are held only by server adapters and are never returned by provider, intent, or status APIs. Request-body subject values are ignored. Configure authentication and authorization in the same payment block:
```ts
payment: {
default: "razorpayIndia",
gateways: { /* named providers */ },
api: {
subject: (ctx) => ctx.session?.userId,
authorize: async (ctx, action, payment) => {
if (["capture", "refund", "reconcile"].includes(action))
return await isBillingAdmin(ctx)
return !payment || payment.subject === ctx.session?.userId
},
// Recommended for catalog purchases: derive price and metadata server-side.
initialize: async (ctx, body, subject) => resolveCatalogPurchase(body, subject),
},
}
```
Administrative actions deny by default. Set `api: false` to expose only the signed webhook endpoint. Use CSRF protection and rate limiting in the application's normal middleware.
## Pages, themes, and languages
The package exposes `PaymentOverviewPage`, `PaymentDetailsPage`, `PaymentCheckoutPage`, and `PaymentMethodsPage`, plus the smaller payment components. These use framework theme variables, inherit light/dark mode and accent colors, and collapse to a single-column mobile layout.
All visible copy is supplied through component props. Applications can pass their normal i18n messages without the package forcing a locale or duplicating the application's language switcher:
```wrn
<PaymentMethodsPage
methods={payments.methods}
title={t.paymentMethodsTitle}
description={t.paymentMethodsDescription}
emptyLabel={t.noPaymentMethods}
/>
```
Provider-hosted card fields remain on the provider's origin. Applications should never collect or pass raw card numbers, CVVs, or expiry values through these APIs.