Radondocs

Coupons

The provider-agnostic coupon engine — create, validate, apply, and redeem discounts that work identically across every payment provider.

Radon ships a provider-agnostic coupon engine. Discounts are computed in Radon (not in each provider's dashboard), so the same coupon works whether you charge with Stripe, Paystack, or Coinbase Commerce.

Create a coupon

A 20%-off code, capped at 100 uses
await payments.coupons.create({
  code: "LAUNCH20",
  type: "percent",     // "percent" | "fixed"
  value: 20,           // 20% off
  maxRedemptions: 100,
  expiresAt: new Date("2026-12-31"),
});

For a fixed-amount coupon, value is integer minor units and currency is required:

₦1,000 off
await payments.coupons.create({
  code: "NAIRA1K",
  type: "fixed",
  value: 100000,       // ₦1,000 in kobo
  currency: "NGN",
});
codestringrequired

The code customers enter. Case-insensitive.

type"percent" | "fixed"required

Percentage discount, or a fixed amount off.

valuenumberrequired

For percent, the percentage (20 = 20%). For fixed, integer minor units.

currencystring

Required for fixed coupons; the discount only applies to charges in this currency.

maxRedemptionsnumber

Total times the code can be redeemed across all customers.

expiresAtDate

When the code stops working.

Validate and apply

Use validate() to check a code without changing anything (great for a "apply coupon" button), and apply() to compute the discounted amount before charging.

Show the discounted total
import { money } from "@radonsdk/payments";

const amount = money(1999, "USD");

// Read-only: throws a typed error if the code is invalid/expired/used up
await payments.coupons.validate("LAUNCH20", amount);

// Compute the discount
const { finalAmount, discount } = await payments.coupons.apply("LAUNCH20", amount);
// finalAmount = $15.99, discount = $4.00

Redeem only after a successful charge

apply() computes the discount; a redemption is recorded only when the charge succeeds. A declined charge never consumes a coupon use.

The safe order of operations
const base = money(1999, "USD");
const { finalAmount } = await payments.coupons.apply("LAUNCH20", base);

const charge = await payments.charge({
  amount: finalAmount,
  customer: { email: "ada@example.com" },
});

if (charge.status === "succeeded") {
  await payments.coupons.redeem("LAUNCH20", base.amount - finalAmount.amount);
}

Fixed coupons are currency-tied

A fixed coupon carries a currency. Applying it to a charge in a different currency throws CurrencyMismatchError. Percentage coupons are currency-independent.

Persisting coupons

By default coupons live in an in-memory store (fine for a single process). For production, plug in a database-backed couponStore in your config so codes and redemption counts survive restarts and scale across instances.

const payments = new RadonPayments({
  providers: { stripe: {} },
  couponStore: myDatabaseCouponStore, // implements the CouponStore interface
});

Next steps

On this page