Skip to content

Initializing Payments

This guide covers everything you need to know about initializing payments, verifying transactions, and handling payment responses with Voltax.

All Voltax payment providers follow the same general flow:

  1. Initiate Payment: Call initiatePayment() with payment details
  2. Redirect User: Redirect the customer to the authorizationUrl
  3. Customer Completes Payment: Customer enters payment details on the provider’s page
  4. Callback: Provider redirects customer back to your callbackUrl
  5. Verify Transaction: Call verifyTransaction() to confirm payment status

Each provider has its own typed payment DTO that extends a base schema. The base fields are shared across all providers:

import { Currency } from "@noelzappy/voltax";
// Base fields (shared by all providers)
interface BasePaymentDTO {
amount: number; // Amount in major currency units (e.g., 100.50)
email: string; // Customer's email address
currency: Currency; // Currency code (NGN, GHS, USD, KES, ZAR)
// Optional fields
reference?: string; // Your unique transaction reference
mobileNumber?: string; // Customer's mobile number (10-15 digits)
description?: string; // Transaction description (max 255 chars)
callbackUrl?: string; // URL to redirect after payment (Used as webhook for Hubtel)
metadata?: Record<string, any>; // Custom data to attach
}
// Provider-specific options are added at the top level of each DTO
// e.g., PaystackPaymentDTO adds: channels, subaccount, splitCode, etc.
// e.g., HubtelPaymentDTO adds: returnUrl (required), cancellationUrl, etc.
FieldTypeDescription
amountnumberPayment amount in major units (e.g., 100 for 100 NGN)
emailstringValid email address of the customer
currencyCurrencyOne of: Currency.NGN, Currency.GHS, Currency.USD, Currency.KES, Currency.ZAR
FieldTypeDescription
referencestringUnique identifier for the transaction. Required by some providers
mobileNumberstringCustomer phone number (10-15 characters)
descriptionstringBrief description of the payment (max 255 chars)
callbackUrlstringURL to redirect after payment completion
metadataRecord<string, any>Custom key-value data attached to the transaction
import Voltax, { Currency } from "@noelzappy/voltax";
const paystack = Voltax("paystack", {
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
const payment = await paystack.initiatePayment({
amount: 5000,
email: "customer@example.com",
currency: Currency.NGN,
reference: `order-${Date.now()}`,
callbackUrl: "https://yoursite.com/payment/callback",
});
console.log(payment);
// {
// status: 'PENDING',
// reference: 'order-1234567890',
// authorizationUrl: 'https://checkout.paystack.com/xxx',
// externalReference: 'order-1234567890',
// raw: { ... }
// }

Attach custom data to track orders, users, or any relevant information:

const payment = await paystack.initiatePayment({
amount: 2500,
email: "customer@example.com",
currency: Currency.NGN,
reference: "order-123",
metadata: {
orderId: "ORD-12345",
userId: "USR-67890",
productName: "Premium Subscription",
quantity: 1,
},
});

Each provider has its own payment DTO with specific options at the top level:

import { Voltax, PaystackChannel, Currency } from '@noelzappy/voltax';
const paystack = Voltax('paystack', {
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
const payment = await paystack.initiatePayment({
amount: 5000,
email: 'customer@example.com',
currency: Currency.NGN,
// Paystack-specific options at top level
channels: [PaystackChannel.CARD, PaystackChannel.BANK_TRANSFER],
subaccount: 'ACCT_xxxxxxxx',
transactionCharge: 100,
plan: 'PLN_xxxxxxxx',
});

All payment operations return a standardized response:

interface VoltaxPaymentResponse {
status: PaymentStatus; // SUCCESS, PENDING, or FAILED
reference: string; // Your transaction reference
authorizationUrl?: string; // URL to redirect customer (for initialization)
externalReference?: string; // Provider's internal reference
raw?: any; // Original provider response
}
enum PaymentStatus {
SUCCESS = "SUCCESS", // Payment completed successfully
PENDING = "PENDING", // Payment is processing or awaiting action
FAILED = "FAILED", // Payment failed or was cancelled
}

After the customer completes payment, verify the transaction status:

import { PaymentStatus } from "@noelzappy/voltax";
const result = await paystack.verifyTransaction("order-123");
switch (result.status) {
case PaymentStatus.SUCCESS:
// Payment successful - fulfill the order
console.log("Payment completed!");
console.log("Provider reference:", result.externalReference);
break;
case PaymentStatus.PENDING:
// Payment still processing
console.log("Payment is still pending...");
break;
case PaymentStatus.FAILED:
// Payment failed
console.log("Payment failed");
break;
}

For a quick status check without full transaction details:

const status = await paystack.getPaymentStatus("order-123");
if (status === PaymentStatus.SUCCESS) {
console.log("Order is paid!");
}

Use VoltaxAdapter to manage multiple providers in the same application:

import { VoltaxAdapter, Currency } from "@noelzappy/voltax";
const voltax = new VoltaxAdapter({
paystack: { secretKey: process.env.PAYSTACK_SECRET_KEY! },
flutterwave: { secretKey: process.env.FLUTTERWAVE_SECRET_KEY! },
hubtel: {
clientId: process.env.HUBTEL_CLIENT_ID!,
clientSecret: process.env.HUBTEL_CLIENT_SECRET!,
merchantAccountNumber: process.env.HUBTEL_MERCHANT_ACCOUNT!,
},
});
// Use Paystack for Nigerian customers
const ngPayment = await voltax.paystack.initiatePayment({
amount: 5000,
email: "customer@ng.example.com",
currency: Currency.NGN,
});
// Use Hubtel for Ghanaian customers
const ghPayment = await voltax.hubtel.initiatePayment({
amount: 100,
email: "customer@gh.example.com",
currency: Currency.GHS,
reference: "gh-order-123",
callbackUrl: "https://yoursite.com/webhook",
returnUrl: "https://yoursite.com/success",
});
import {
Voltax,
Currency,
PaymentStatus,
VoltaxValidationError,
VoltaxGatewayError,
} from "@noelzappy/voltax";
import { randomUUID } from "crypto";
async function processPayment(orderDetails: {
amount: number;
email: string;
currency: Currency;
}) {
const paystack = Voltax("paystack", {
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
const reference = `order-${randomUUID()}`;
try {
// Step 1: Initiate payment
const payment = await paystack.initiatePayment({
...orderDetails,
reference,
callbackUrl: `https://yoursite.com/callback?ref=${reference}`,
});
// Step 2: Store reference in your database
await savePaymentReference(reference, orderDetails);
// Step 3: Return authorization URL to client
return {
success: true,
checkoutUrl: payment.authorizationUrl,
reference,
};
} catch (error) {
if (error instanceof VoltaxValidationError) {
return { success: false, error: "Invalid payment details" };
}
if (error instanceof VoltaxGatewayError) {
return { success: false, error: "Payment provider error" };
}
throw error;
}
}
async function handleCallback(reference: string) {
const paystack = Voltax("paystack", {
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
// Always verify server-side
const result = await paystack.verifyTransaction(reference);
if (result.status === PaymentStatus.SUCCESS) {
await fulfillOrder(reference);
return { success: true };
}
return { success: false, status: result.status };
}