Radondocs

Charges

Take one-time payments with payments.charge() — handle direct and redirect-based providers, idempotency, currencies, and confirmation.

A charge is a single, one-time payment. payments.charge() returns the same normalized ChargeResult whatever the provider — you branch on status, not on which processor you used.

A basic charge

Charge a card
import { money } from "@radonsdk/payments";
import { payments } from "@/lib/payments";

const charge = await payments.charge({
  amount: money(1999, "USD"),                 // $19.99
  customer: { email: "ada@example.com" },
});

if (charge.status === "succeeded") {
  // ✅ money captured — fulfill the order
}

charge() runs any middleware you've registered, applies a coupon if one is attached, then fires your onPaymentSucceeded / onPaymentFailed hooks.

The input

amountMoneyrequired

Integer minor units + ISO-4217 currency, built with money() or majorMoney(). See money.

customer{ email?: string; id?: string; name?: string }

The payer. Some providers (Paystack and most African processors) require email and will throw without it.

idempotencyKeystring

A unique key that makes retrying the same charge safe — the provider returns the original result instead of double-charging. Forwarded natively where the provider supports it (e.g. Stripe's Idempotency-Key header).

metadataRecord<string, string>

Arbitrary key/values stored on the charge and echoed back in webhooks — great for your order id.

Amounts are minor units

money(1999, "USD") is $19.99, not $1,999. Passing a float like money(19.99, "USD") throws — use majorMoney(19.99, "USD"). This is the single most common payments bug; Radon makes it loud instead of silent.

Redirect flows

Many providers don't settle in one call — the customer must approve the payment on a hosted page (bank 3-D Secure, PayPal, Paystack, Klarna). For these, charge() returns status: "requires_action" and a redirectUrl.

Handle both direct and redirect providers
const charge = await payments.charge({
  amount: money(500000, "NGN"),               // ₦5,000
  customer: { email: "ada@example.com" },
}, { provider: "paystack" });

if (charge.status === "requires_action" && charge.redirectUrl) {
  // Send the customer to the provider's hosted page…
  return Response.redirect(charge.redirectUrl);
}

When the customer returns to your success_url, confirm the real outcome server-side — never trust the redirect alone:

On return — confirm before fulfilling
const confirmed = await payments.retrieveCharge(charge.id, { provider: "paystack" });

if (confirmed.status === "succeeded") {
  await fulfillOrder(confirmed.metadata.orderId);
}

Prefer webhooks for fulfillment

A customer can close the tab before redirecting back. The reliable signal that a payment completed is the provider's webhook — treat the redirect as a UX nicety and fulfill on charge.succeeded.

Choosing the provider per charge

With multiple providers configured, pass { provider } to route a single call; otherwise defaultProvider is used.

await payments.charge({ amount: money(1999, "USD") }, { provider: "stripe" });
await payments.charge({ amount: money(500000, "NGN") }, { provider: "paystack" });

Reading provider-only fields

Radon normalizes the common fields. For anything provider-specific, read charge.raw — the untouched API response.

const charge = await payments.charge({ amount: money(1999, "USD") });
const stripePaymentMethod = (charge.raw as any).payment_method_details; // Stripe-only

Next steps

On this page