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();