Hubtel Integration
Hubtel is a leading payment and commerce platform in Ghana, offering mobile money, card payments, and more. This guide covers how to integrate Hubtel payments using Voltax.
Prerequisites
Section titled “Prerequisites”Before you begin, you’ll need:
- A Hubtel merchant account (sign up at hubtel.com)
- Your Hubtel API credentials:
- Client ID
- Client Secret
- Merchant Account Number
Configuration
Section titled “Configuration”Initialize a Hubtel provider with your credentials:
import Voltax from "@noelzappy/voltax";
const hubtel = Voltax("hubtel", { clientId: process.env.HUBTEL_CLIENT_ID!, clientSecret: process.env.HUBTEL_CLIENT_SECRET!, merchantAccountNumber: process.env.HUBTEL_MERCHANT_ACCOUNT!,});Initiate a Payment
Section titled “Initiate a Payment”Basic Payment
Section titled “Basic Payment”import { Currency } from "@noelzappy/voltax";
const payment = await hubtel.initiatePayment({ amount: 100, // 100 GHS email: "customer@example.com", currency: Currency.GHS, reference: `hub-${Date.now()}`, // Required! callbackUrl: "https://yoursite.com/webhooks/hubtel", // Required! returnUrl: "https://yoursite.com/payment/success", // Required!});
// Redirect customer to complete paymentconsole.log(payment.authorizationUrl);// "https://checkout.hubtel.com/xxxxxxxxxx"With All Options
Section titled “With All Options”const payment = await hubtel.initiatePayment({ amount: 250, email: "customer@example.com", currency: Currency.GHS, reference: `order-${Date.now()}`, description: "Order #12345 - Premium Package", callbackUrl: "https://yoursite.com/webhooks/hubtel", returnUrl: "https://yoursite.com/payment/success", cancellationUrl: "https://yoursite.com/payment/cancelled", mobileNumber: "0241234567", // Customer's mobile money number});Hubtel-Specific Options
Section titled “Hubtel-Specific Options”All Hubtel-specific options are top-level fields in the payment DTO:
Required Options
Section titled “Required Options”interface HubtelPaymentDTO { // Base required fields amount: number; email: string; currency: Currency; reference: string; // Required for Hubtel callbackUrl: string; // Required for Hubtel
// Hubtel-specific required returnUrl: string; // Where to redirect after successful payment
// Optional fields cancellationUrl?: string; // Where to redirect if customer cancels mobileNumber?: string; // Customer's mobile money number description?: string; metadata?: Record<string, any>;}Understanding the URLs
Section titled “Understanding the URLs”| URL | Purpose |
|---|---|
callbackUrl | Server-to-server webhook URL for payment notifications |
returnUrl | Where to redirect the customer after successful payment |
cancellationUrl | Where to redirect if customer cancels (optional) |
Verify a Transaction
Section titled “Verify a Transaction”After the customer completes payment, verify the transaction:
import { PaymentStatus } from "@noelzappy/voltax";
const result = await hubtel.verifyTransaction("hub-123456");
console.log(result);// {// status: 'SUCCESS',// reference: 'hub-123456',// externalReference: 'txn_xxxxxxxxxx',// raw: { ... }// }
if (result.status === PaymentStatus.SUCCESS) { // Access detailed transaction data const { amount, charges, amountAfterCharges } = result.raw.data; console.log(`Received GHS ${amountAfterCharges} (after GHS ${charges} fees)`);}Status Mapping
Section titled “Status Mapping”Voltax maps Hubtel statuses to standardized values:
| Hubtel Status | Voltax Status |
|---|---|
Paid | SUCCESS |
Refunded | FAILED |
Unpaid | PENDING |
| Other | PENDING |
Get Payment Status
Section titled “Get Payment Status”For a quick status check:
const status = await hubtel.getPaymentStatus("hub-123456");
if (status === PaymentStatus.SUCCESS) { console.log("Payment successful!");}Complete Example
Section titled “Complete Example”Here’s a full Express.js integration example:
import express from "express";import Voltax, { Currency, PaymentStatus } from "@noelzappy/voltax";import { randomUUID } from "crypto";
const app = express();app.use(express.json());
const hubtel = Voltax("hubtel", { clientId: process.env.HUBTEL_CLIENT_ID!, clientSecret: process.env.HUBTEL_CLIENT_SECRET!, merchantAccountNumber: process.env.HUBTEL_MERCHANT_ACCOUNT!,});
// Initiate paymentapp.post("/api/payments/hubtel", async (req, res) => { try { const { amount, email, phone, orderId } = req.body;
const reference = `order-${orderId}-${randomUUID()}`;
const payment = await hubtel.initiatePayment({ amount, email, currency: Currency.GHS, reference, mobileNumber: phone, description: `Payment for Order #${orderId}`, callbackUrl: `${process.env.BASE_URL}/webhooks/hubtel`, returnUrl: `${process.env.FRONTEND_URL}/payment/success?ref=${reference}`, cancellationUrl: `${process.env.FRONTEND_URL}/payment/cancelled`, });
// Store reference in database await savePaymentReference(orderId, reference);
res.json({ success: true, checkoutUrl: payment.authorizationUrl, reference: payment.reference, }); } catch (error) { console.error("Payment initialization failed:", error); res.status(400).json({ success: false, error: error.message, }); }});
// Webhook endpoint (callbackUrl)app.post("/webhooks/hubtel", async (req, res) => { const { ClientReference, Status, TransactionId } = req.body;
console.log("Hubtel webhook received:", { reference: ClientReference, status: Status, transactionId: TransactionId, });
try { // Verify the transaction const result = await hubtel.verifyTransaction(ClientReference);
if (result.status === PaymentStatus.SUCCESS) { await fulfillOrder(ClientReference); console.log(`Order ${ClientReference} fulfilled`); }
res.status(200).json({ received: true }); } catch (error) { console.error("Webhook processing failed:", error); res.status(500).json({ error: "Processing failed" }); }});
// Return URL handlerapp.get("/payment/success", async (req, res) => { const { ref } = req.query;
try { // Always verify server-side const result = await hubtel.verifyTransaction(ref as string);
if (result.status === PaymentStatus.SUCCESS) { res.render("success", { message: "Payment successful!", reference: ref, }); } else { res.render("pending", { message: "Payment is being processed...", reference: ref, }); } } catch (error) { res.render("error", { message: "Could not verify payment status", }); }});
// Cancellation handlerapp.get("/payment/cancelled", (req, res) => { res.render("cancelled", { message: "Payment was cancelled", });});
app.listen(3000);Mobile Money Integration
Section titled “Mobile Money Integration”Hubtel excels at mobile money payments in Ghana. Include the customer’s mobile number for seamless mobile money checkout:
const payment = await hubtel.initiatePayment({ amount: 50, email: "customer@example.com", currency: Currency.GHS, reference: `momo-${Date.now()}`, mobileNumber: "0241234567", // MTN, Vodafone, or AirtelTigo number callbackUrl: "https://yoursite.com/webhooks/hubtel", returnUrl: "https://yoursite.com/success",});Supported Currency
Section titled “Supported Currency”| Currency | Code | Country |
|---|---|---|
| Ghanaian Cedi | GHS | Ghana |
Validation Requirements
Section titled “Validation Requirements”Hubtel has specific validation requirements. Here’s what Voltax validates:
| Field | Requirement |
|---|---|
amount | Must be a positive number |
reference | Required, must be provided |
callbackUrl | Required, must be a valid URL |
returnUrl | Required, must be a valid URL |
If any of these are missing or invalid, you’ll receive a VoltaxValidationError.
Next Steps
Section titled “Next Steps”- Learn about Error Handling for Hubtel errors
- Explore the API Reference for HubtelAdapter
- Check Hubtel’s documentation for advanced features like recurring payments