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
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:
await payments.coupons.create({
code: "NAIRA1K",
type: "fixed",
value: 100000, // ₦1,000 in kobo
currency: "NGN",
});codestringrequiredThe code customers enter. Case-insensitive.
type"percent" | "fixed"requiredPercentage discount, or a fixed amount off.
valuenumberrequiredFor percent, the percentage (20 = 20%). For fixed, integer minor units.
currencystringRequired for fixed coupons; the discount only applies to charges in this
currency.
maxRedemptionsnumberTotal times the code can be redeemed across all customers.
expiresAtDateWhen 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.
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.00Redeem 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.
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
Webhooks
One signature-verified handler for every provider's events. payments.webhooks.handle() verifies and normalizes any provider's payload into a single event schema.
Providers
All 24 payment providers — global, African, and crypto. Credentials, capabilities, and the quirks each one has (all absorbed by Radon so your code never sees them).