Building payment systems in Africa means dealing with multiple payment gateways, each with its own API, documentation, and quirks. Voltax solves this by providing:
- 🔌 One Interface, Multiple Gateways - Write once, accept payments from Paystack, Flutterwave, Hubtel, and more
- 🛡️ Type-Safe - Built with TypeScript and Zod for runtime validation
- 🔄 Easy Provider Switching - Change payment providers without rewriting your code
- ⚡ Lightweight - Tree-shakeable, ESM & CJS support
- 🧪 Well Tested - Comprehensive test coverage
| Gateway | Countries | Status |
|---|---|---|
| Paystack | Nigeria, Ghana, South Africa, Kenya | ✅ Ready |
| Flutterwave | Nigeria, Ghana, Kenya, South Africa + | ✅ Ready |
| Hubtel | Ghana | ✅ Ready |
| More coming... | — | Contribute! |
npm install @noelzappy/voltaxpnpm add @noelzappy/voltaxyarn add @noelzappy/voltaximport { Voltax, Currency, PaymentStatus } from '@noelzappy/voltax';
// Initialize a single provider
const paystack = Voltax('paystack', {
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
// Initiate a payment
const payment = await paystack.initiatePayment({
amount: 5000,
email: 'customer@example.com',
currency: Currency.NGN,
reference: `order-${Date.now()}`,
callbackUrl: 'https://yoursite.com/callback',
});
console.log(payment.authorizationUrl);
// Redirect customer to complete payment
// Verify the payment
const result = await paystack.verifyTransaction(payment.reference);
if (result.status === PaymentStatus.SUCCESS) {
console.log('Payment successful!');
}import { VoltaxAdapter, Currency } from '@noelzappy/voltax';
// Initialize multiple providers at once
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 any configured provider
await voltax.paystack.initiatePayment({ ... });
await voltax.hubtel.initiatePayment({ ... });All providers implement the same interface:
interface VoltaxProvider<TPaymentDTO> {
initiatePayment(payload: TPaymentDTO): Promise<VoltaxPaymentResponse>;
verifyTransaction(reference: string): Promise<VoltaxPaymentResponse>;
getPaymentStatus(reference: string): Promise<PaymentStatus>;
}Each provider has its own typed payment DTO that extends the base schema. Common fields include:
// Base fields shared by all providers
{
amount: number; // Amount in major units (e.g., 100 for 100 NGN)
email: string; // Customer email
currency: Currency; // NGN, GHS, USD, KES, ZAR
reference?: string; // Your unique transaction reference
mobileNumber?: string; // Customer phone number
description?: string; // Transaction description
callbackUrl?: string; // Redirect URL after payment
metadata?: object; // Custom data
}
// Provider-specific fields are added at the top level
// e.g., Paystack adds: channels, subaccount, splitCode, etc.
// e.g., Hubtel adds: returnUrl, cancellationUrl (required fields){
status: PaymentStatus; // SUCCESS, PENDING, or FAILED
reference: string; // Your transaction reference
authorizationUrl?: string; // Checkout URL
externalReference?: string; // Provider's reference
raw?: any; // Original provider response
}Voltax provides structured error classes:
import {
VoltaxValidationError,
VoltaxGatewayError,
VoltaxNetworkError,
} from '@noelzappy/voltax';
try {
await voltax.paystack.initializePayment(payload);
} catch (error) {
if (error instanceof VoltaxValidationError) {
// Invalid payload - check error.errors for details
console.error('Validation failed:', error.errors);
} else if (error instanceof VoltaxGatewayError) {
// Payment provider returned an error
console.error('Gateway error:', error.message, error.statusCode);
} else if (error instanceof VoltaxNetworkError) {
// Network connectivity issue - safe to retry
console.error('Network error:', error.message);
}
}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_xxxxx',
transactionCharge: 100,
});import { Voltax, Currency } from '@noelzappy/voltax';
const flutterwave = Voltax('flutterwave', {
secretKey: process.env.FLUTTERWAVE_SECRET_KEY!,
});
const payment = await flutterwave.initiatePayment({
amount: 5000,
email: 'customer@example.com',
currency: Currency.NGN,
reference: 'order-123', // Required for Flutterwave
// Flutterwave-specific options at top level
customerName: 'John Doe',
pageTitle: 'My Store',
logoUrl: 'https://yoursite.com/logo.png',
});import { Voltax, Currency } 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!,
});
const payment = await hubtel.initiatePayment({
amount: 100,
email: 'customer@example.com',
currency: Currency.GHS,
reference: 'order-123', // Required
callbackUrl: 'https://yoursite.com/webhook', // Required
// Hubtel-specific options at top level
returnUrl: 'https://yoursite.com/success', // Required
});For complete documentation, visit voltax.noelzappy.dev
We welcome contributions! Voltax aims to support all major African payment gateways, and we need your help.
Gateways we'd love to add:
- M-Pesa (Kenya)
- OPay (Nigeria)
- Chipper Cash (Pan-African)
- MTN MoMo (Ghana, Uganda)
- Yoco (South Africa)
- And many more!
See CONTRIBUTING.md for a complete guide on adding new payment gateways.
# Clone the repo
git clone https://github.com/noelzappy/voltax.git
# Install dependencies
cd voltax && npm install
# Run tests
cd packages/node && npm testvoltax/
├── packages/
│ ├── node/ # Node.js SDK (@noelzappy/voltax)
│ ├── go/ # Go SDK (coming soon)
│ └── php/ # PHP SDK (coming soon)
└── docs/ # Documentation site
MIT © noelzappy
Built with ❤️ for African developers