First off, thank you for considering contributing to Voltax! 🎉
Voltax aims to be the most comprehensive payment SDK for Africa, and we need your help to make that happen. Whether you're fixing a bug, adding a new payment gateway, or improving documentation, your contribution is valuable.
- Code of Conduct
- Getting Started
- Development Setup
- Adding a New Payment Gateway
- Coding Guidelines
- Testing
- Submitting a Pull Request
- Reporting Issues
This project adheres to a Code of Conduct that all contributors are expected to follow. Please be respectful and inclusive in all interactions.
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/voltax.git cd voltax - Add the upstream remote:
git remote add upstream https://github.com/noelzappy/voltax.git
- Node.js 18 or later
- npm 9 or later
# Install root dependencies
npm install
# Install package dependencies
cd packages/node
npm install
# Run tests to verify setup
npm test
# Build the package
npm run buildvoltax/
├── packages/
│ └── node/ # Node.js SDK
│ ├── src/
│ │ ├── core/ # Core classes and interfaces
│ │ │ ├── enums.ts # Currency, PaymentStatus enums
│ │ │ ├── errors.ts # Error classes
│ │ │ ├── interfaces.ts # VoltaxProvider interface
│ │ │ ├── schemas.ts # Base Zod validation schema
│ │ │ ├── voltax.ts # Voltax factory & VoltaxAdapter
│ │ │ └── provider-schemas/ # Provider-specific schemas
│ │ │ ├── paystack.schema.ts
│ │ │ ├── flutterwave.schema.ts
│ │ │ ├── hubtel.schema.ts
│ │ │ └── moolre.schema.ts
│ │ ├── providers/ # Payment gateway adapters
│ │ │ ├── paystack/
│ │ │ ├── flutterwave/
│ │ │ ├── hubtel/
│ │ │ └── moolre/
│ │ └── index.ts # Public exports
│ └── package.json
└── docs/ # Documentation site
We especially encourage contributions that add support for new African payment gateways! Here's how to add one:
mkdir -p packages/node/src/providers/YOUR_GATEWAYCreate types.ts with your gateway-specific types:
// packages/node/src/providers/YOUR_GATEWAY/types.ts
// Configuration for initialization
export interface YourGatewayConfig {
secretKey: string;
// Add other required credentials
}
// API response types (from the gateway's API)
export interface YourGatewayInitResponse {
// Map to the actual API response structure
}
export interface YourGatewayVerifyResponse {
// Map to the actual API response structure
}Create a schema file that extends the base payment schema:
// packages/node/src/core/provider-schemas/your_gateway.schema.ts
import { z } from 'zod';
import { BasePaymentSchema } from '../schemas.js';
/**
* YourGateway-specific payment options
*/
export const YourGatewayOptionsSchema = z.object({
customField: z.string().optional(),
// Add gateway-specific options here
});
/**
* Complete YourGateway payment schema (base + gateway-specific options)
*/
export const YourGatewayPaymentSchema = BasePaymentSchema.extend(YourGatewayOptionsSchema.shape);
export type YourGatewayPaymentDTO = z.infer<typeof YourGatewayPaymentSchema>;
export type YourGatewayOptions = z.infer<typeof YourGatewayOptionsSchema>;Create the adapter that implements VoltaxProvider:
// packages/node/src/providers/YOUR_GATEWAY/your_gateway.adapter.ts
import axios, { AxiosInstance } from "axios";
import { PaymentStatus } from "../../core/enums.js";
import { VoltaxGatewayError, VoltaxNetworkError } from "../../core/errors.js";
import { VoltaxPaymentResponse, VoltaxProvider } from "../../core/interfaces.js";
import {
YourGatewayPaymentSchema,
YourGatewayPaymentDTO,
} from "../../core/provider-schemas/your_gateway.schema.js";
import { handleGatewayError } from "../../core/errors.js";
import { YourGatewayConfig } from "./types.js";
export class YourGatewayAdapter implements VoltaxProvider<YourGatewayPaymentDTO> {
private axiosClient: AxiosInstance;
constructor(private config: YourGatewayConfig) {
this.axiosClient = axios.create({
baseURL: "https://api.yourgateway.com",
headers: {
Authorization: `Bearer ${config.secretKey}`,
"Content-Type": "application/json",
},
});
}
async initiatePayment(payload: YourGatewayPaymentDTO): Promise<VoltaxPaymentResponse> {
try {
// Validate payload with Zod schema
const validated = YourGatewayPaymentSchema.parse(payload);
// Transform payload to gateway format
const gatewayPayload = {
amount: validated.amount * 100, // Convert to minor units if needed
email: validated.email,
currency: validated.currency,
reference: validated.reference,
// Map other fields...
};
const response = await this.axiosClient.post("/transaction/initialize", gatewayPayload);
// Transform response to VoltaxPaymentResponse
return {
status: PaymentStatus.PENDING,
reference: validated.reference || response.data.reference,
authorizationUrl: response.data.authorization_url,
externalReference: response.data.reference,
raw: response.data,
};
} catch (error) {
throw handleGatewayError(error);
}
}
async verifyTransaction(reference: string): Promise<VoltaxPaymentResponse> {
try {
const response = await this.axiosClient.get(`/transaction/verify/${reference}`);
return {
status: this.mapStatus(response.data.status),
reference,
externalReference: response.data.id,
raw: response.data,
};
} catch (error) {
throw handleGatewayError(error);
}
}
async getPaymentStatus(reference: string): Promise<PaymentStatus> {
const result = await this.verifyTransaction(reference);
return result.status;
}
private mapStatus(gatewayStatus: string): PaymentStatus {
// Map gateway-specific statuses to Voltax statuses
const statusMap: Record<string, PaymentStatus> = {
success: PaymentStatus.SUCCESS,
completed: PaymentStatus.SUCCESS,
failed: PaymentStatus.FAILED,
cancelled: PaymentStatus.FAILED,
pending: PaymentStatus.PENDING,
};
return statusMap[gatewayStatus.toLowerCase()] || PaymentStatus.PENDING;
}
}Create comprehensive tests:
// packages/node/src/providers/YOUR_GATEWAY/your_gateway.adapter.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { YourGatewayAdapter } from "./your_gateway.adapter.js";
import { Currency, PaymentStatus } from "../../core/enums.js";
describe("YourGatewayAdapter", () => {
let adapter: YourGatewayAdapter;
beforeEach(() => {
adapter = new YourGatewayAdapter({
secretKey: "test_secret_key",
});
});
describe("initializePayment", () => {
it("should initialize payment successfully", async () => {
// Mock axios and test the flow
});
it("should handle validation errors", async () => {
// Test error handling
});
});
describe("verifyTransaction", () => {
it("should verify successful transaction", async () => {
// Test verification
});
});
});Add exports to packages/node/src/index.ts:
// Add to exports
export { YourGatewayAdapter } from "./providers/YOUR_GATEWAY/your_gateway.adapter.js";
export type { YourGatewayOptions, YourGatewayPaymentDTO } from "./core/provider-schemas/your_gateway.schema.js";Update packages/node/src/core/voltax.ts to include your gateway:
// Add to VoltaxProviders type
export type VoltaxProviders = 'paystack' | 'hubtel' | 'flutterwave' | 'moolre' | 'yourgateway';
// Add to VoltaxConfigMap interface
export interface VoltaxConfigMap {
// ... existing providers
yourgateway: YourGatewayConfig;
}
// Add to VoltaxAdapterMap interface
export interface VoltaxAdapterMap {
// ... existing providers
yourgateway: YourGatewayAdapter;
}
// Add to VoltaxMultiConfig interface
export interface VoltaxMultiConfig {
// ... existing providers
yourgateway?: YourGatewayConfig;
}
// Add case in Voltax factory function
case 'yourgateway':
return new YourGatewayAdapter(config as YourGatewayConfig);
// Add to VoltaxAdapter class
public yourgateway?: YourGatewayAdapter;
// In VoltaxAdapter constructor
if (config.yourgateway) {
this.yourgateway = new YourGatewayAdapter(config.yourgateway);
}Create a guide in docs/src/content/docs/guides/your-gateway.mdx following the pattern of existing guides.
- Use strict TypeScript - no
anytypes unless absolutely necessary - Export interfaces for all public types
- Use Zod for runtime validation of external data
- Run
npm run lintbefore committing - Run
npm run formatto auto-format code - Use meaningful variable and function names
- Add JSDoc comments for public APIs
- Use kebab-case for files:
your-gateway.adapter.ts - Use PascalCase for classes:
YourGatewayAdapter - Use camelCase for functions and variables
- Use the existing error classes:
VoltaxValidationError,VoltaxGatewayError,VoltaxNetworkError - Always catch and transform errors appropriately
- Include helpful error messages
# Run all tests
npm test
# Run tests in watch mode
npm run test -- --watch
# Run tests for a specific file
npm test -- your_gateway.adapter.test.ts
# Run with coverage
npm test -- --coverage- Write tests for all public methods
- Mock external API calls
- Test both success and error paths
- Aim for >80% code coverage on new code
-
Create a feature branch:
git checkout -b feature/add-gateway-name
-
Make your changes following the guidelines above
-
Write/update tests for your changes
-
Run the test suite:
npm test -
Lint and format:
npm run lint npm run format
-
Commit your changes with a descriptive message:
git commit -m "feat: add support for GatewayName" -
Push to your fork:
git push origin feature/add-gateway-name
-
Open a Pull Request on GitHub
Use conventional commits format:
feat: add support for NewGatewayfix: correct amount conversion for Paystackdocs: update Hubtel integration guidechore: update dependencies
When reporting issues, please include:
- Description of the issue
- Steps to reproduce
- Expected behavior
- Actual behavior
- Environment (Node.js version, OS, etc.)
- Code sample if applicable
Here are some African payment gateways we'd love to add support for:
- Chipper Cash - Pan-African
- OPay - Nigeria
- Kuda - Nigeria
- Moniepoint - Nigeria
- Squad - Nigeria
- Seerbit - Nigeria
- Interswitch - Nigeria
- MTN MoMo API - Ghana, Uganda, etc.
- M-Pesa (Safaricom) - Kenya
- Cellulant - Pan-African
- DPO Group - Pan-African
- Pesapal - Kenya, Uganda, Tanzania
- IntaSend - Kenya
- Kopokopo - Kenya
- Yoco - South Africa
- Peach Payments - South Africa
- PayFast - South Africa
- iKhokha - South Africa
- Ozow - South Africa
- Fawry - Egypt
- Paymob - Egypt
- Kashier - Egypt
- CMI - Morocco
Pick one and submit a PR! We'll help you through the process.
Feel free to open an issue or start a discussion if you have any questions. We're here to help!
Thank you for contributing to Voltax! 🚀