Skip to content

Error Handling

Sanwo surfaces errors through the CheckoutResult when a checkout completes, and through the SanwoErrorInfo object that gives you structured details about what went wrong and whether you can retry.

Every checkout returns a result with a status field. The "failed" status is the primary error path:

type CheckoutResult =
| { status: "successful"; reference: string; providerResponse: unknown }
| { status: "cancelled" }
| { status: "failed"; error: SanwoErrorInfo }
| { status: "pending"; reference: string };

When status is "failed", the error field contains a SanwoErrorInfo object with everything you need to diagnose and respond to the failure.

interface SanwoErrorInfo {
code: SanwoErrorCode; // Machine-readable error code
message: string; // Human-readable description
provider?: string; // Which provider reported the error (if applicable)
recoverable: boolean; // Can you retry the checkout?
cause?: unknown; // The underlying error (provider SDK error, network error, etc.)
}

The code field is the key to programmatic error handling. The message is suitable for logging but may be too technical for end users – write your own user-facing messages based on the code.

Sanwo defines 12 error codes. Each one tells you what went wrong and what to do about it.

These indicate a problem with how Sanwo was set up. They are not recoverable – fix the configuration and redeploy.

Code Description What to do
INVALID_CONFIGURATION The Sanwo instance was created with invalid config Check that provider and publicKey are correct. Make sure you imported the right provider package.
INVALID_CHECKOUT_OPTIONS The checkout options are invalid Verify amount is a positive number, currency is a valid ISO 4217 code, and customer.email is present.
UNSUPPORTED_CURRENCY The active provider does not support the requested currency Switch to a provider that supports this currency, or use a different currency. See Choosing a Provider.
UNSUPPORTED_CAPABILITY A requested feature is not available for this provider The provider does not support a feature you are trying to use. Remove the unsupported option or switch providers.

These can happen during a checkout. Some are recoverable.

Code Description What to do
PROVIDER_LOAD_FAILED The provider SDK could not be loaded Usually a network or CDN issue. Check connectivity and retry.
PROVIDER_NOT_READY The provider has not finished initializing Wait for initialization to complete before calling checkout. This can happen if you call checkout immediately after creating the instance.
SCRIPT_LOAD_FAILED The provider’s JavaScript SDK failed to load The <script> tag for the provider’s checkout JS could not load. Check for ad blockers, CSP issues, or network problems.
CHECKOUT_ALREADY_ACTIVE Another checkout is already in progress Wait for the current checkout to complete before starting a new one.
CHECKOUT_FAILED The payment was declined or failed at the provider level The provider rejected the payment. The message field usually contains the reason (e.g., insufficient funds, card declined). Show the user an appropriate message.
CHECKOUT_CANCELLED The user closed the checkout This is also surfaced via result.status === "cancelled". Prefer checking the status field directly.
NETWORK_ERROR A connectivity issue occurred during checkout Check the user’s internet connection and retry.
TIMEOUT The checkout exceeded the configured timeout The default timeout is 120 seconds. If your checkout flow needs more time, increase the timeout in your configuration.
const result = await sanwo({
amount: 500000,
currency: "NGN",
customer: { email: "customer@example.com" },
});
switch (result.status) {
case "successful":
console.log("Paid!", result.reference);
break;
case "failed":
console.error(result.error.code, result.error.message);
if (result.error.recoverable) {
// Safe to retry
showRetryButton();
} else {
// Fix configuration and redeploy
showSupportContact();
}
break;
case "cancelled":
console.log("User closed checkout");
break;
case "pending":
console.log("Verify server-side:", result.reference);
break;
}

Every SanwoErrorInfo includes a recoverable boolean that tells you whether retrying the checkout is likely to help.

Recoverable errors (recoverable: true):

  • PROVIDER_LOAD_FAILED – network issue loading the SDK, try again
  • NETWORK_ERROR – connectivity problem, may resolve
  • CHECKOUT_FAILED – payment declined, user can try a different card
  • TIMEOUT – checkout took too long, user can try again
  • SCRIPT_LOAD_FAILED – script blocked or network issue, may work on retry

Non-recoverable errors (recoverable: false):

  • INVALID_CONFIGURATION – code change required
  • INVALID_CHECKOUT_OPTIONS – fix the options being passed
  • UNSUPPORTED_CURRENCY – wrong provider for this currency
  • UNSUPPORTED_CAPABILITY – feature not available
  • PROVIDER_NOT_READY – initialization timing issue
  • CHECKOUT_ALREADY_ACTIVE – wait for the current checkout

Use this flag to decide whether to show a “Try again” button or a “Contact support” message.

The default checkout timeout is 120 seconds (2 minutes). If a checkout is not completed within this window, it fails with the TIMEOUT error code.

You can configure a longer timeout when creating the Sanwo instance:

const sanwo = createSanwo({
provider: paystackProvider,
publicKey: "pk_test_xxxxxxxxxxxx",
timeout: 180000, // 3 minutes in milliseconds
});

Consider increasing the timeout for:

  • Markets where bank transfer confirmation takes longer
  • Checkout flows that require OTP or 3D Secure verification
  • Slower network environments

The CheckoutResult captures the final outcome of a checkout. But sometimes non-fatal errors happen during a checkout – for example, a provider script that needs retrying internally.

Use the onError callback in your checkout options to observe these transient errors:

const result = await sanwo({
amount: 500000,
currency: "NGN",
customer: { email: "customer@example.com" },
onError: (error) => {
// Non-fatal error during checkout
console.warn("Checkout warning:", error.code, error.message);
analytics.track("checkout_warning", { code: error.code });
},
});

The onError callback fires for issues that Sanwo handles internally (like retrying a script load). The checkout may still succeed. Use it for logging and monitoring, not for control flow.

Enable debug mode to log all internal Sanwo events to the console. This is invaluable when diagnosing checkout issues during development.

const sanwo = createSanwo({
provider: paystackProvider,
publicKey: "pk_test_xxxxxxxxxxxx",
debug: true,
});

Debug mode logs:

  • Provider initialization steps
  • Checkout lifecycle events (started, opened, loaded, closed)
  • Provider SDK requests and responses
  • Error details including the underlying cause

Always disable debug mode in production.

Map error codes to messages your users will understand:

function getUserMessage(code: SanwoErrorCode): string {
switch (code) {
case "CHECKOUT_FAILED":
return "Your payment could not be processed. Please try a different payment method.";
case "NETWORK_ERROR":
return "Please check your internet connection and try again.";
case "TIMEOUT":
return "The payment took too long. Please try again.";
case "CHECKOUT_CANCELLED":
return "Payment was cancelled.";
default:
return "Something went wrong. Please try again or contact support.";
}
}

For recoverable errors in automated flows:

async function checkoutWithRetry(options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await sanwo(options);
if (result.status !== "failed" || !result.error.recoverable) {
return result;
}
if (attempt < maxRetries) {
await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
return { status: "failed", error: { code: "TIMEOUT", message: "Max retries exceeded", recoverable: false } };
}

Track errors to catch provider issues early:

const result = await sanwo(options);
if (result.status === "failed") {
errorTracker.capture("payment_failed", {
code: result.error.code,
message: result.error.message,
provider: result.error.provider,
recoverable: result.error.recoverable,
});
}