Xtopay Docs
Payments

Hosted Checkout

Let Xtopay host the payment page so you don't have to build one.

Overview

Xtopay Checkout is a hosted payment page that handles:

  • Payment method selection
  • Card number entry and 3DS authentication
  • Mobile money prompts (push notification to customer's phone)
  • Bank transfer instructions with real-time confirmation
  • USSD dial string display
  • Automatic redirect back to your app on success or failure

You don't write any payment UI. You create a payment server-side, redirect to checkout_url, and Xtopay handles the rest.

Basic flow

// 1. Create the payment (server-side)
const payment = await xtopay.payments.create({
  amount: 10000,
  currency: "GHS",
  customer: { email: "ama@example.com", name: "Ama Owusu" },
  redirect_url: "https://yourapp.com/checkout/success",
  cancel_url: "https://yourapp.com/checkout/cancel",
});

// 2. Redirect customer to checkout
return redirect(payment.checkout_url);
// Hosted at: https://checkout.xtopay.co/pay/{token}

Customising checkout

Pass a checkout object when creating the payment:

const payment = await xtopay.payments.create({
  amount: 10000,
  currency: "GHS",
  customer: { email: "ama@example.com" },
  checkout: {
    // Your brand
    logo_url: "https://yourapp.com/logo.png",
    accent_color: "#412f7d",

    // Pre-select a payment method
    preferred_method: "mobile_money",

    // Lock to specific methods only
    allowed_methods: ["card", "mobile_money"],

    // Description shown on the page
    description: "Pro Plan — monthly",

    // Language
    locale: "en",
  },
  redirect_url: "https://yourapp.com/checkout/success",
});

Verifying the result

Don't trust the redirect URL alone — a user could manually visit the success page. Always verify server-side:

// On your redirect page (server-side)
export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const paymentId = searchParams.get("payment_id");

  const payment = await xtopay.payments.retrieve(paymentId!);

  if (payment.status !== "succeeded") {
    return redirect("/checkout/failed");
  }

  await fulfillOrder(payment.metadata.order_id);
  return redirect("/checkout/complete");
}

Xtopay appends ?payment_id={id}&status={status} to your redirect_url for convenience, but always retrieve and check the authoritative status server-side.

Checkout expiry

Checkout URLs expire after 30 minutes. If the customer hasn't completed payment by then, the payment moves to expired status and a new payment must be created.

Embedding checkout (Inline)

For a seamless in-app experience without a full page redirect, use the Xtopay.js drop-in:

<script src="https://js.xtopay.co/v1/checkout.js"></script>
const checkout = XtopayCheckout.init({
  checkoutUrl: payment.checkout_url, // from your server
  container: "#checkout-container",
  onSuccess: (payment) => console.log("paid!", payment),
  onCancel: () => console.log("cancelled"),
});
checkout.mount();

On this page