Surface B — normalized, provider-agnostic operations with minor-unit Money, plus the capability matrix that tells you what each provider actually supports.
The unified layer (Surface B) gives a smaller set of high-traffic operations identical signatures across the providers that support them. Write your integration once against defaultProvider; pass { provider } per call to target another configured one.
const payweave = createPayweave({
paystack: { secretKey: process.env.PAYSTACK_SECRET_KEY! },
})
const checkout = await payweave.checkout.create({
amount: { value: 500_000, currency: "NGN" }, // ALWAYS minor units
customer: { email: "ada@example.com" },
reference: "order_8123",
redirectUrl: "https://app.example.com/pay/callback",
})
// → { checkoutUrl, reference, providerRef, raw }
const result = await payweave.verify({ reference: "order_8123" })
// → { status, amount: { value, currency }, customer, paidAt, channel, raw }
await payweave.refunds.create({ reference })
await payweave.banks.list({ country: "NG" })
await payweave.banks.resolveAccount({ accountNumber: "0123456789", bankCode: "058" })Every amount is { value: number, currency: string } where value is in integer minor units. The adapters convert:
// amount: { value: 500000, currency: "NGN" }
// Paystack outgoing body → amount: 500000
// Flutterwave outgoing body → amount: 5000Your reference maps to Paystack reference and Flutterwave tx_ref. Omit it and the SDK generates one (pwv_<ulid>), returned on the result.
verify() returns a single normalized status, mapped from each provider's own strings:
| Unified status | Meaning |
|---|---|
success | payment completed |
failed | payment failed / declined |
pending | not yet resolved (also the fallback for unknown statuses) |
abandoned | customer never completed |
reversed | funds returned |
Unknown statuses map to pending plus a logged warning — the SDK never throws on an unrecognized status.
Not every provider supports every unified operation, and the SDK never silently drops a call onto the wrong endpoint — it gates each one against a capability matrix and throws before making a request.
| Operation | Stripe | Paystack | Flutterwave |
|---|---|---|---|
checkout.create | ✅ | ✅ | ✅ |
verify | ✅ | ✅ | ✅ |
refunds.create | ✅ | ✅ | ✅ |
transfers.create | ❌ | ✅ | ✅ |
banks.list | ❌ | ✅ | ✅ |
banks.resolveAccount | ❌ | ✅ | ✅ |
Stripe doesn't map transfers.create or banks.* — Stripe Connect payouts are semantically different from Paystack/Flutterwave bank transfers, and bank-account lookup is Nigeria-specific.
const payweave = createPayweave({
stripe: { secretKey: process.env.STRIPE_SECRET_KEY! },
})
await payweave.transfers.create({ /* ... */ })
// throws PayweaveValidationError: "transfers are not supported on stripe" — zero HTTP calls madeIntrospect the matrix at runtime instead of hardcoding it:
payweave.capabilities("stripe")
// → { "checkout.create": { supported: true }, verify: { supported: true },
// "refunds.create": { supported: true },
// "transfers.create": { supported: false, reason: "transfers are not supported on stripe" },
// "banks.list": { supported: false, reason: "banks.list is not supported on stripe" },
// "banks.resolveAccount": { supported: false, reason: "banks.resolveAccount is not supported on stripe" } }
payweave.capabilities() // the full matrix, keyed by every configured providercheckout.create, verify, and refunds.create show supported: true for
Stripe in the matrix above, but the runtime mapping hasn't shipped yet —
calling them today on a Stripe-only client throws a plain "not implemented
yet" error, not a capability error. Use Surface A
(payweave.stripe.*) for Stripe until that lands.
raw escape hatchEvery unified response includes the untouched provider response on raw, so the abstraction never hides data from you.