npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@radonsdk/payments

v0.1.0

Published

One unified, provider-agnostic API for 24 payment providers — global, African, and crypto. Write payments.charge() once, swap providers via config. Webhook normalization, a provider-side coupon engine, composable hooks, and bring-your-own-provider. Own yo

Readme

@radonsdk/payments

One unified, provider-agnostic API for 24 payment providers — global, African, and crypto. Write payments.charge() once and swap providers with a config change, never a code change.

Radon Payments gives you a single, typed interface (charge, refund, createSubscription, parseWebhook, …) that every provider implements. Provider-specific quirks — auth schemes, minor vs. major currency units, wildly different webhook payloads — are absorbed inside each adapter and never leak to your code. Own your data, no vendor lock-in.

  • 24 providers, one API — Stripe, PayPal, Adyen, Paystack, Flutterwave, Coinbase Commerce, Mercado Pago, and more.
  • Normalized webhooks — every provider's events collapse into one schema; one handler, payments.webhooks.handle(req).
  • Provider-agnostic coupon engine, composable hooks/middleware, and a bring-your-own-provider interface.
  • Lazy-loaded adapters — a Stripe + Paystack app never bundles Alipay's code. The core is ~39 KB.
  • Strict TypeScript, ESM + CJS, Node ≥ 18, zero required dependencies.

Install

npm install @radonsdk/payments
# or: pnpm add @radonsdk/payments

Radon never stores your secrets. Each adapter reads credentials from RADON_<PROVIDER>_* environment variables (or from the providers config block, which takes precedence). See .env.example for every provider's variable names.


Quickstart (< 5 minutes)

import { RadonPayments, money } from "@radonsdk/payments";

const payments = new RadonPayments({
  mode: "test",                       // sandbox keys everywhere; flip to "live" to go live
  providers: { stripe: {} },          // credentials come from RADON_STRIPE_SECRET_KEY
  defaultProvider: "stripe",
});

// Amounts are ALWAYS integer minor units + an ISO-4217 currency. $19.99:
const charge = await payments.charge({
  amount: money(1999, "USD"),
  customer: { email: "[email protected]" },
});

console.log(charge.status);       // "succeeded" | "requires_action" | ...
console.log(charge.redirectUrl);  // present when the customer must finish on a hosted page

Switch providers without touching your charge code — it's a config change:

const payments = new RadonPayments({
  mode: "test",
  providers: { stripe: {}, paystack: {} },
});

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

money(1999, "USD") is $19.99; money(500000, "NGN") is ₦5,000.00. You always pass minor units — the adapter converts to whatever the provider's API actually wants (kobo, decimal strings, etc.).


Free vs. Pro

Five providers are free. The rest require a Radon Pro license key, verified once in await payments.init() and cached for the process lifetime.

| Tier | Providers | | --- | --- | | Free | stripe, paystack, paypal, opay, bachs | | Pro (license) | everything else (see catalog below) + any bring-your-own provider |

const payments = new RadonPayments({
  providers: { adyen: {} },            // a Pro provider
  licenseKey: process.env.RADON_LICENSE_KEY,   // or config.license: { key, verifyUrl, ... }
});

await payments.init();                 // verifies the license (throws if invalid); unlocks Pro
await payments.charge({ amount: money(1999, "EUR") }, { provider: "adyen" });
  • Free providers work with no license and no init().
  • Using a Pro provider without a valid license throws LicenseRequiredError; a bad/unreachable key throws LicenseInvalidError (fail-closed).
  • Get a license at https://radonsdk.xyz/pricing. Introspect tiers with the exported FREE_PROVIDERS set and isProProvider(slug).

The unified API

// Charge (one-time). Returns a normalized ChargeResult.
const c = await payments.charge({ amount: money(1999, "USD"), customer: { email } });

// Confirm an async / redirect charge later.
const latest = await payments.retrieveCharge(c.id, { provider: "paystack" });

// Refund (full or partial).
await payments.refund({ chargeId: c.id, amount: money(500, "USD") });

// Subscriptions (providers that support them).
const sub = await payments.subscriptions.create({
  customer: { email },
  plan: { amount: money(1500, "USD"), interval: "month" },
});
await payments.subscriptions.cancel(sub.id, { atPeriodEnd: true });

Operations not supported by a provider throw a typed UnsupportedOperationError — never a silent failure — and provider.capabilities tells you up front what's available.


Webhooks — one handler for every provider

Each provider signs webhooks differently (HMAC-SHA256/512, MD5, RSA, timestamped schemes, sorted-key digests…). Radon verifies the signature inside the adapter and hands you one normalized event:

// Express / Fastify / Next — pass the RAW body (signatures are computed over exact bytes).
app.post("/webhooks", async (req, res) => {
  try {
    const event = await payments.webhooks.handle(
      { body: req.rawBody, headers: req.headers },
      { provider: "stripe" }, // optional when only one provider is configured
    );
    // event.type is one of the normalized types below
    res.sendStatus(200);
  } catch {
    res.sendStatus(400); // signature failed — reject
  }
});

Normalized event types:

payment.succeeded   payment.failed
subscription.created subscription.updated subscription.cancelled
refund.issued        dispute.opened        invoice.paid
unknown

Register handlers as hooks (below), or use the return value of handle().


Coupons (provider-agnostic)

Coupons live in Radon, not the provider — so one coupon works identically across all 32. The discount is applied before the provider is charged; a redemption is recorded only after the charge succeeds.

await payments.coupons.create({ code: "LAUNCH20", type: "percentage", value: 20, maxRedemptions: 100 });

const result = await payments.charge({ amount: money(5000, "USD"), coupon: "LAUNCH20" });
result.coupon; // { code: "LAUNCH20", amountDiscounted: 1000, originalAmount: 5000 }

Plug in your own persistence with the couponStore config option (defaults to in-memory).


Hooks, middleware & plugins

// Lifecycle hooks
payments.on("onPaymentSucceeded", (charge) => analytics.track(charge));
payments.on("onWebhookEvent", (event) => queue.enqueue(event));

// Around-style charge middleware (onion order)
payments.useMiddleware(async (input, next) => {
  const result = await next(input);
  await audit.log(input, result);
  return result;
});

// Bundle hooks + middleware as a reusable plugin
payments.use({ name: "my-plugin", onRefundIssued: (r) => {/* … */} });

Bring your own provider

Implement the PaymentProvider interface (or extend BaseProvider) and register it. Custom providers are Pro-gated.

import { BaseProvider, registerProvider } from "@radonsdk/payments";

class MyPspProvider extends BaseProvider {
  readonly name = "my-psp";
  readonly capabilities = { charge: true, refund: true, webhooks: true, subscriptions: false, redirect: false, crypto: false };
  async charge(input) { /* call your API via this.http(...) */ }
  async refund(input) { /* … */ }
  parseWebhook(req, secret) { /* verify + normalize */ }
}

registerProvider("my-psp", async () => MyPspProvider);

Provider catalog

24 providers, fully implemented with real API calls and signature-verified webhooks. Import any adapter directly for an explicit dependency: import { StripeProvider } from "@radonsdk/payments/providers/stripe".

Global

| Slug | Provider | Tier | Notes | | --- | --- | --- | --- | | stripe | Stripe | Free | cards, subscriptions, refunds, HMAC webhooks | | paypal | PayPal | Free | Orders v2, OAuth2, async webhook verify | | braintree | Braintree | Pro | GraphQL charge/refund, SHA1-HMAC webhook | | adyen | Adyen | Pro | minor units, HMAC-SHA256 notification | | checkout | Checkout.com | Pro | Bearer + HMAC webhook | | square | Square | Pro | amount_money, notification-URL HMAC | | authorizenet | Authorize.Net | Pro | opaque-data nonce, X-ANET-Signature | | razorpay | Razorpay | Pro | order-based, subscriptions, HMAC webhook | | mollie | Mollie | Pro | decimal-string amounts, fetch-to-verify | | gocardless | GoCardless | Pro | mandate-based direct debit, versioned | | klarna | Klarna | Pro | HPP session → redirect (no signed webhook) | | skrill | Skrill | Pro | Quick Checkout, MD5 status signature |

Africa

| Slug | Provider | Tier | Notes | | --- | --- | --- | --- | | paystack | Paystack | Free | minor units (kobo), HMAC-SHA512 webhook | | opay | OPay | Free | dual-auth Cashier, HMAC-SHA3-512 callback | | bachs | Bachs | Free | decimal-string money, subscriptions, HMAC-SHA256 webhook | | flutterwave | Flutterwave | Pro | major units, verif-hash webhook | | korapay | Korapay | Pro | HMAC over data only | | fincra | Fincra | Pro | business-id header, HMAC-SHA512 |

Crypto

| Slug | Provider | Tier | Notes | | --- | --- | --- | --- | | coinbase | Coinbase Commerce | Pro | fiat + native crypto amounts, HMAC-SHA256 | | nowpayments | NOWPayments | Pro | sorted-key HMAC-SHA512 IPN | | opennode | OpenNode | Pro | Bitcoin/Lightning, id-HMAC callback |

BNPL / regional

| Slug | Provider | Tier | Notes | | --- | --- | --- | --- | | afterpay | Afterpay / Clearpay | Pro | decimal amounts, checkout→capture | | mercadopago | Mercado Pago | Pro | Checkout Pro, x-signature webhook, subscriptions | | payu | PayU (Global/CEE) | Pro | OAuth2, minor-unit orders, OpenPayU MD5 webhook |


Money & currency

Radon's canonical amount is an integer in the currency's minor unit + an ISO-4217 code — money(1999, "USD") is $19.99, money(100, "JPY") is ¥100 (zero-decimal), money(1000, "KWD") is 1.000 KWD (three-decimal). Integers avoid floating-point rounding bugs. Each adapter converts to its API's format; you never think about it.

Crypto charges quote a fiat price and return both the native asset amount and the fiat-equivalent on result.crypto ({ amount, fiatEquivalent, address }).

import { money, majorMoney, toMinorUnits } from "@radonsdk/payments";
money(1999, "USD");        // { amount: 1999, currency: "USD" }
majorMoney(19.99, "USD");  // same, from a decimal

Test vs. live

One flag flips every configured provider between sandbox and production credentials/endpoints simultaneously:

new RadonPayments({ mode: "test", providers: { /* … */ } }); // default: sandbox everywhere
new RadonPayments({ mode: "live", providers: { /* … */ } }); // production

Install size & lazy loading

The core (import { RadonPayments } from "@radonsdk/payments") contains zero provider code — only the interface, registry, and the webhook/coupon/hook engines (~39 KB). Adapters are loaded on demand via dynamic import() the first time a provider is used, and each is emitted as its own subpath entry (@radonsdk/payments/providers/<slug>). A dev who configures only Stripe + Paystack never executes — or, under a code-splitting bundler, bundles — the other 22 adapters or their optional dependencies. This is why one package can cover two dozen providers without bloating your install.


API surface

RadonPayments, createPayments, money / majorMoney / toMinorUnits / formatMinorUnits, BaseProvider, registerProvider, FREE_PROVIDERS / isProProvider, LicenseClient, the HttpClient + signature helpers (for BYO adapters), and every typed error (PaymentError, LicenseRequiredError, UnsupportedOperationError, WebhookSignatureError, …). All types are exported.

License

MIT © Radon SDK. The SDK is MIT-licensed; the Pro tier requires a commercial license key at runtime for Pro providers.