Skip to content

Provider Interface

Every provider package (e.g. @sanwohq/paystack, sanwo_flutterwave) exports a SanwoProviderDefinition object. This page documents that shape and the underlying template/bridge system.

Field Type Required Description
id string Yes Unique identifier (e.g. "paystack", "flutterwave")
name string Yes Internal name used in logs and events
displayName string Yes Human-readable name (e.g. "Paystack", "Flutterwave")
template string Yes HTML template with {{sanwoBridge}} and {{params}} placeholders
website string? No Provider’s website URL
documentation string? No Provider’s documentation URL
supportedCurrencies string[]? No ISO 4217 currency codes the provider supports
supportedCountries string[]? No ISO 3166 country codes the provider supports
paymentMethods string[]? No Payment methods the provider supports (e.g. "card", "bank_transfer")
amountInMinorUnit boolean? No Whether the provider natively expects minor units. If false, Sanwo auto-converts from minor to major units before passing to the template.
interface SanwoProviderDefinition {
id: string;
name: string;
displayName: string;
template: string;
website?: string;
documentation?: string;
supportedCurrencies?: string[];
supportedCountries?: string[];
paymentMethods?: string[];
amountInMinorUnit?: boolean;
}
class SanwoProviderDefinition {
final String id;
final String name;
final String displayName;
final String template;
final String? website;
final String? documentation;
final List<String>? supportedCurrencies;
final List<String>? supportedCountries;
final List<String>? paymentMethods;
final bool? amountInMinorUnit;
SanwoProviderDefinition({
required this.id,
required this.name,
required this.displayName,
required this.template,
this.website,
this.documentation,
this.supportedCurrencies,
this.supportedCountries,
this.paymentMethods,
this.amountInMinorUnit,
});
}
struct SanwoProviderDefinition {
let id: String
let name: String
let displayName: String
let template: String
var website: String? = nil
var documentation: String? = nil
var supportedCurrencies: [String]? = nil
var supportedCountries: [String]? = nil
var paymentMethods: [String]? = nil
var amountInMinorUnit: Bool? = nil
}
data class SanwoProviderDefinition(
val id: String,
val name: String,
val displayName: String,
val template: String,
val website: String? = null,
val documentation: String? = null,
val supportedCurrencies: List<String>? = null,
val supportedCountries: List<String>? = null,
val paymentMethods: List<String>? = null,
val amountInMinorUnit: Boolean? = null,
)

The template field contains an HTML string that loads the provider’s checkout SDK and wires it up to Sanwo’s bridge. Two placeholders are injected at render time:

Replaced with a JSON object containing the checkout parameters:

{
"amount": 500000,
"currency": "NGN",
"publicKey": "pk_test_xxxxxxxxxxxx",
"reference": "sanwo_1719849600_a1b2c3",
"customer": {
"email": "customer@example.com"
}
}

If amountInMinorUnit is false on the provider definition, the amount value is auto-converted to major units before injection (e.g. 500000 kobo becomes 5000).

Replaced with a platform-specific JavaScript snippet that defines a sanwoBridge function. This function sends messages from the checkout iframe/webview back to the host application.

<!DOCTYPE html>
<html>
<head>
<script src="https://js.paystack.co/v2/inline.js"></script>
</head>
<body>
<script>
{{sanwoBridge}}
const params = {{params}};
const handler = PaystackPop.setup({
key: params.publicKey,
email: params.customer.email,
amount: params.amount,
currency: params.currency,
ref: params.reference,
onSuccess: (response) => {
sanwoBridge({ type: "sanwo", event: "success", data: response });
},
onCancel: () => {
sanwoBridge({ type: "sanwo", event: "cancelled", data: {} });
},
onLoad: () => {
sanwoBridge({ type: "sanwo", event: "loaded", data: {} });
},
});
handler.openIframe();
</script>
</body>
</html>

Each platform injects a different sanwoBridge implementation via the {{sanwoBridge}} placeholder:

Platform Bridge mechanism Injected code
Web Parent window postMessage window.parent.postMessage(...)
React Native WebView postMessage window.ReactNativeWebView.postMessage(...)
Flutter JavaScript channel messageHandler.postMessage(...)
iOS WKWebView message handler window.webkit.messageHandlers.sanwo.postMessage(...)
Android JavaScript interface JSBridge.showMessageInNative(...)

All bridges send messages with the same shape:

interface SanwoBridgeMessage {
type: "sanwo";
event: "success" | "cancelled" | "closed" | "error" | "loaded";
data: Record<string, unknown>;
}
Event When Typical data contents
success Payment completed { reference, transactionId, ... } (provider-specific)
cancelled User dismissed checkout {}
closed Checkout UI torn down {}
error Provider error occurred { message, code?, ... }
loaded Provider SDK finished loading {}

The host SDK parses these messages and translates them into the platform-specific CheckoutResult.

When amountInMinorUnit is true (or omitted), the amount is passed through unchanged. When false, Sanwo divides by 100 before injecting into {{params}}:

amountInMinorUnit You pass Template receives Example provider
true (default) 500000 500000 Paystack
false 500000 5000 Flutterwave, Monnify

This ensures you always work with minor units in your application code, regardless of what the provider SDK expects.