Files
WRNexusJS/packages/payment
ClintchizandClaude Opus 5 2d9797856d Release the thirteen changed packages
Published to the private registry: auth 0.8.32, cli 0.8.70, compiler
0.8.29, core 0.8.15, csr 0.8.40, dev-server 0.8.65, payment 0.8.8, pwa
0.8.11, ssr 0.8.11, styles 0.8.22, test 0.8.15, ui 0.8.80 and viewer
0.8.12.

This records the version bumps and the lockfile. The package sources are
staged from the working tree, so the tarballs also carry source changes
that are still uncommitted here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:34:46 +05:30
..

@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:

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:

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:

<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.