DEV Community

Cover image for Accept Payments in Minutes with Afriex Checkout Sessions
Victory Lucky for Afriex

Posted on

Accept Payments in Minutes with Afriex Checkout Sessions

Most payment integrations follow the same painful path: you build a checkout UI, set up bank account display logic, handle Mobile Money prompts, manage session timers, poll for confirmation, and somehow do all of this correctly across every edge case. It takes days, sometimes weeks — and that's before you think about testing.

Afriex Checkout Sessions changes that. With a single API call, you get a fully hosted payment page that handles everything: payment method selection, bank transfer details, Mobile Money authorization, session expiration, and redirect on completion. You stay focused on your product; Afriex handles the payment flow.

This guide walks you through a complete integration from scratch — using either the official TypeScript SDK or the REST API directly.


How It Works

The flow is four steps:

  1. Your server creates a checkout session via the SDK or REST API
  2. Afriex returns a checkoutUrl
  3. You redirect the customer to that URL
  4. After payment, Afriex redirects the customer back to your app and fires a webhook to your server

That's the entire integration surface. No payment UI to build. No polling loop. No bank account display to manage.


Prerequisites

Before you start, you'll need:

  • An Afriex Business account with API access
  • Your API key from the Afriex dashboard (Developer tab)
  • Node.js 20+ if you're using the SDK, or any server-side runtime for the REST API
  • An HTTPS redirect URL where customers will land after payment

Sandbox first. Checkout Sessions is currently available for end-to-end testing in Afriex's sandbox environment. Production rollout is happening soon — use sandbox to validate your integration now so you're ready to flip the switch.


Setup

Using the SDK (Node.js / TypeScript)

Install the official Afriex SDK:

npm install @afriex/sdk
# or
pnpm add @afriex/sdk
# or
yarn add @afriex/sdk
Enter fullscreen mode Exit fullscreen mode

Then initialize it once — typically in a shared module or service file:

import { AfriexSDK } from "@afriex/sdk";

export const afriex = new AfriexSDK({
  apiKey: process.env.AFRIEX_API_KEY,
  environment: "staging", // or "production"
});
Enter fullscreen mode Exit fullscreen mode

The SDK maps staging to https://sandbox.api.afriex.com and production to https://api.afriex.com — no need to manage base URLs yourself.

Enable retries for production resilience:

export const afriex = new AfriexSDK({
  apiKey: process.env.AFRIEX_API_KEY,
  environment: "production",
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000, // milliseconds
    retryableStatusCodes: [408, 429, 500, 502, 503, 504],
  },
});
Enter fullscreen mode Exit fullscreen mode

Using the REST API directly

Set your base URL depending on your environment:

Sandbox:    https://sandbox.api.afriex.com
Production: https://api.afriex.com
Enter fullscreen mode Exit fullscreen mode

Pass your API key on every request via the x-api-key header.


Step 1: Create a Checkout Session

SDK (Node.js / TypeScript)

import { afriex } from "./afriex-client"; // your initialized SDK instance

async function createCheckoutSession(order: Order) {
  const session = await afriex.checkout.createSession({
    amount: order.amountInKobo,          // e.g. 500000 for ₦5,000
    currency: "NGN",
    merchantReference: order.id,          // must be unique per session
    redirectUrl: "https://yourapp.com/checkout/return",
    channels: ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
    customer: {
      name: order.customer.name,
      email: order.customer.email,
      phone: order.customer.phone,        // E.164 format: +2348100000000
      countryCode: "NG",
    },
    metadata: {
      orderId: order.id,
      plan: order.plan,
    },
  });

  return session.checkoutUrl;
}
Enter fullscreen mode Exit fullscreen mode

The SDK returns { checkoutUrl: string } — redirect your customer there immediately.


REST API

cURL

curl -X POST https://sandbox.api.afriex.com/api/v1/checkout-session \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "amount": 500000,
    "currency": "NGN",
    "merchantReference": "order-2026-05-30-001",
    "redirectUrl": "https://yourapp.com/checkout/return",
    "channels": ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
    "customer": {
      "name": "Amarachi Okafor",
      "email": "amara@example.com",
      "phone": "+2348100000000",
      "countryCode": "NG"
    },
    "metadata": {
      "orderId": "ORD-9912",
      "plan": "pro"
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Node.js (fetch)

async function createCheckoutSession(order) {
  const response = await fetch(
    "https://sandbox.api.afriex.com/api/v1/checkout-session",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.AFRIEX_API_KEY,
      },
      body: JSON.stringify({
        amount: order.amountInKobo,
        currency: "NGN",
        merchantReference: order.id,
        redirectUrl: "https://yourapp.com/checkout/return",
        channels: ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
        customer: {
          name: order.customer.name,
          email: order.customer.email,
          phone: order.customer.phone,
          countryCode: "NG",
        },
        metadata: {
          orderId: order.id,
        },
      }),
    }
  );

  const { data } = await response.json();
  return data.checkoutUrl;
}
Enter fullscreen mode Exit fullscreen mode

Python (requests)

import requests
import os

def create_checkout_session(order):
    response = requests.post(
        "https://sandbox.api.afriex.com/api/v1/checkout-session",
        headers={
            "Content-Type": "application/json",
            "x-api-key": os.environ["AFRIEX_API_KEY"],
        },
        json={
            "amount": order["amount_in_kobo"],
            "currency": "NGN",
            "merchantReference": order["id"],
            "redirectUrl": "https://yourapp.com/checkout/return",
            "channels": ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
            "customer": {
                "name": order["customer"]["name"],
                "email": order["customer"]["email"],
                "phone": order["customer"]["phone"],
                "countryCode": "NG",
            },
        },
    )
    return response.json()["data"]["checkoutUrl"]
Enter fullscreen mode Exit fullscreen mode

Response

{
  "data": {
    "checkoutUrl": "https://sandbox.pay.afriex.com/pay/eyJhbGciOiJI..."
  }
}
Enter fullscreen mode Exit fullscreen mode

Redirect your customer to checkoutUrl immediately. The session has an expiration timer, so don't hold on to the URL for later.


Request Reference

Required Fields

Field Type Description
amount integer Amount in minor currency units (kobo for NGN, cents for USD). Minimum: 100.
currency string ISO 4217 currency code, uppercase (e.g. NGN, GHS). Must be enabled for checkout on your account.
merchantReference string Your unique identifier for this session. Used to match webhooks and reconcile transactions.
redirectUrl string HTTPS URL the customer is sent to after the checkout flow completes.
customer object Customer details. See below.

Customer Object

Field Type Description
name string Customer's full name.
email string Customer's email address.
phone string Phone number in E.164 format (e.g. +2348100000000).
countryCode string ISO 3166-1 alpha-2 country code (e.g. NG, GH). Case-insensitive.

Optional Fields

Field Type Default Description
channels array ["VIRTUAL_BANK_ACCOUNT"] Payment methods to offer. Options: VIRTUAL_BANK_ACCOUNT, MOBILE_MONEY.
metadata object Flat key/value pairs (strings only) you want attached to the session for your own reference.

Step 2: The Customer Completes Payment

Once redirected, the customer lands on the Afriex-hosted checkout page. Depending on the channels you enabled, they can:

Pay by Bank Transfer

The customer sees a one-time virtual bank account — account name, number, and bank — with the exact amount to transfer. They open their banking app, send the transfer, and return to the checkout page. Afriex detects the inbound transfer and confirms payment automatically.

⚠️ Exact amount required. The customer must transfer the precise amount shown. Sending a different amount will cause delays and may require a manual reconciliation or refund.

Pay by Mobile Money

The customer selects their network (e.g. MTN, Airtel), enters their account name and phone number, and taps Pay now. Afriex pushes an authorization prompt to their phone. Once they approve it, the checkout page updates automatically.

Both paths end in one of two outcomes: a success screen (auto-redirects to your redirectUrl) or a failure screen (with guidance to retry or contact support).


Step 3: Handle the Redirect

When the customer lands back on your redirectUrl, show them an appropriate confirmation or error screen. Use the merchantReference you generated to look up the order state in your system.

A typical return handler:

// Express.js example
app.get("/checkout/return", async (req, res) => {
  // The merchantReference you passed when creating the session
  const reference = req.query.reference;

  // Look up your order by reference — don't trust query params for payment status
  const order = await db.orders.findByReference(reference);

  if (order.status === "paid") {
    return res.redirect(`/orders/${order.id}/confirmation`);
  }

  // Not yet confirmed — webhook may still be in flight
  return res.redirect(`/orders/${order.id}/pending`);
});
Enter fullscreen mode Exit fullscreen mode

Don't use the redirect to confirm payment. The redirect tells you the customer finished the checkout flow, not that payment succeeded. Always use the webhook (see Step 4) as the authoritative signal.


Step 4: Listen for the Webhook

When Afriex confirms payment, it fires a CHECKOUT_SESSION.CREATED webhook to the callback URL configured in your Afriex dashboard. This is the source of truth for marking an order as paid.

// Express.js webhook handler example
app.post("/webhooks/afriex", express.raw({ type: "application/json" }), async (req, res) => {
  const event = JSON.parse(req.body);

  if (event.type === "CHECKOUT_SESSION.CREATED") {
    const { merchantReference, amount, currency } = event.data;

    // Mark order as paid using your merchantReference
    await db.orders.markPaid({
      reference: merchantReference,
      amount,
      currency,
    });
  }

  res.status(200).send("OK");
});
Enter fullscreen mode Exit fullscreen mode

Make your webhook handler idempotent — Afriex may retry delivery if your endpoint doesn't respond with a 200. Using merchantReference as your idempotency key is the right approach.


Beyond the Basic Flow: Advanced Use Cases

The checkout session API supports a few patterns beyond the standard redirect flow that are worth knowing about.

Delayed Payment Collection

You don't have to redirect customers immediately after creating a session. You can create the session on your server, store the checkoutUrl, and send it to the customer later — via email, SMS, or a payment link in an invoice. This is useful for billing flows where payment isn't expected at the point of order.

// Create the session when the invoice is generated
const session = await afriex.checkout.createSession({ ...invoiceDetails });

// Store checkoutUrl against the invoice
await db.invoices.update(invoice.id, { paymentUrl: session.checkoutUrl });

// Send it later when the invoice is dispatched
await mailer.send({
  to: invoice.customer.email,
  subject: "Your invoice is ready",
  body: `Pay here: ${invoice.paymentUrl}`,
});
Enter fullscreen mode Exit fullscreen mode

Keep session expiration in mind — if the payment link will be sent days later, build in a check for whether the session is still valid before sending.

Embedded Checkout (Iframe / Webview)

Rather than fully leaving your app, you can open the checkoutUrl inside an iframe (web) or a webview (mobile). This keeps the customer inside your product's shell while Afriex handles the payment UI.

<!-- Web: iframe embed -->
<iframe
  src="https://sandbox.pay.afriex.com/pay/YOUR_SESSION_TOKEN"
  width="100%"
  height="600"
  frameborder="0"
  allow="payment"
></iframe>
Enter fullscreen mode Exit fullscreen mode
// React Native: WebView embed
import { WebView } from "react-native-webview";

<WebView
  source={{ uri: session.checkoutUrl }}
  onNavigationStateChange={(state) => {
    // Detect redirect back to your redirectUrl
    if (state.url.startsWith("https://yourapp.com/checkout/return")) {
      navigation.navigate("OrderConfirmation");
    }
  }}
/>
Enter fullscreen mode Exit fullscreen mode

The webhook still fires regardless of how the customer accesses the checkout — embedded or full redirect.


Important Notes

Amounts are always in minor units

Afriex uses the smallest denomination of each currency — no decimals in the amount field:

Currency Major unit Minor unit Example
NGN ₦1 1 kobo ₦5,000 → 500000
GHS ₵1 1 pesewa ₵100 → 10000
USD $1 1 cent $10 → 1000

The minimum amount is 100 (equivalent to 1 major unit).

merchantReference must be unique

Each session needs a distinct merchantReference. Re-using a reference from a previous session will result in an error. Use your internal order ID, a UUID, or any other identifier you control.

Sessions expire

Each checkout session has an expiration timer visible to the customer. If the session expires before payment completes, the customer will need a new session. Build a recovery path — for example, let customers re-initiate checkout from your order page.

Phone numbers must be E.164

The customer's phone must be in E.164 format: + followed by the country code and subscriber number, no spaces or punctuation. For example, a Nigerian number would be +2348100000000, not 08100000000.


Testing in Sandbox

All of the above works end-to-end in sandbox today:

  1. Use base URL https://sandbox.api.afriex.com (or set environment: "staging" in the SDK)
  2. Use your sandbox API key from the Afriex dashboard
  3. Successful checkout sessions redirect to https://sandbox.pay.afriex.com/pay/{token}
  4. Webhooks fire to your configured callback URL in sandbox as well

Test the full round-trip — create a session, go through the hosted checkout, and verify your webhook handler receives and processes the event — before switching to production credentials.


Error Responses

Status Meaning
400 Invalid request — check required fields, amount minimum, and currency format
401 Unauthorized — verify your x-api-key header
500 Server error — retry with exponential backoff

What's Next

  • Afriex SDK Documentation — Full SDK reference covering installation, configuration, and all available modules
  • SDK Checkout Reference — SDK-specific parameters and examples for the Checkout API
  • User Flow Guide — See the full customer-facing checkout experience, screen by screen
  • Webhooks — Configure your callback URL and understand the full webhook payload
  • REST API Reference — Full OpenAPI spec for the Create Checkout Session endpoint

Afriex Checkout Sessions is currently available in sandbox. Production rollout is expected in the coming weeks — get your integration ready today.

Top comments (0)