Payvault
Unified payment SDK for African markets. One API for Paystack + Flutterwave -- backend TypeScript SDK + embeddable checkout widget.
Install / Use
/learn @T9ner/PayvaultREADME
PayVault
One API. Every African payment provider.
Stop rewriting payment code when you switch providers. PayVault gives you a single, type-safe API that works with Paystack, Flutterwave, and any provider you add.
Quick Start • Architecture • SDK Usage • API • Dashboard • Contributing
</div>Why PayVault?
| Problem | PayVault Solution |
|---|---|
| Every provider has a different API shape | One unified interface for all providers |
| Switching providers means rewriting code | Change one line to switch providers |
| 15+ transaction statuses across providers | 4 unified statuses: success, failed, pending, abandoned |
| Different amount formats (kobo vs naira) | Always use major currency units (5000 = N5,000) |
| No retry logic out of the box | Smart retry with exponential backoff + jitter |
| Webhook formats differ per provider | Unified webhook events with signature verification |
Quick Start
Prerequisites
- Node.js 18+ (for SDK and Dashboard)
- Go 1.22+ (for API)
- Docker & Docker Compose (for PostgreSQL and Redis)
1. Install the SDK
npm install payvault-sdk
2. Use PayVault in Your App
import { PayVault } from 'payvault-sdk';
// Create a PayVault instance for Paystack
const vault = PayVault.paystack('sk_test_xxxxx');
// Initialize a transaction — returns a checkout URL
const tx = await vault.initializeTransaction({
amount: 5000, // N5,000 (always in major units)
email: 'customer@example.com',
currency: 'NGN',
metadata: { orderId: 'order_123' },
});
console.log(tx.authorizationUrl);
// => "https://checkout.paystack.com/abc123"
3. Start the API (Optional)
If you want to run the hosted backend:
# Start infrastructure
docker compose up -d postgres redis
# Run the API (default port :8080)
cd apps/api
cp env.example .env # Edit with your credentials
go run ./cmd/api
# Run the API for dashboard development (Vite proxy expects :8081)
PORT=8081 go run ./cmd/api
The API runs on http://localhost:8080 by default. For dashboard development, the Vite proxy targets http://localhost:8081, so start the API with PORT=8081 go run ./cmd/api.
4. Run the Dashboard (Optional)
If you want the merchant dashboard UI:
cd apps/dashboard
npm install
npm run dev
The dashboard starts on http://localhost:3000. In development, Vite proxies /api requests to http://localhost:8081.
Architecture
PayVault is a complete payment platform with three components:
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ import { PayVault } from 'payvault-sdk' │
│ const vault = PayVault.paystack('sk_test_xxxxx') │
└─────────────────┬───────────────────────────────────────────┘
│
│ TypeScript SDK
│ (npm package)
▼
┌─────────────────────────────────────────────────────────────┐
│ Payment Providers │
│ │
│ Paystack │ Flutterwave │ Your Custom Provider │
└─────────────────────────────────────────────────────────────┘
Optional Backend Components:
┌──────────────────────┐ ┌──────────────────────┐
│ API (Go/chi) │ │ Dashboard (React) │
│ │ │ │
│ - Unified REST API │◄────────►│ - Merchant UI │
│ - Multi-provider │ │ - Analytics │
│ - PostgreSQL │ │ - Settings │
│ - Redis queue │ │ - Vite + Tailwind │
└──────────────────────┘ └──────────────────────┘
Component Roles
| Component | Purpose | Used By |
|-----------|---------|---------|
| SDK (packages/sdk/) | TypeScript client for payment providers — install via npm | Developers integrating payments |
| API (apps/api/) | Optional Go backend for hosted multi-tenant payment gateway | Companies running their own payment infrastructure |
| Dashboard (apps/dashboard/) | Optional merchant UI for managing transactions, viewing analytics | Merchants using the hosted API |
Most developers only need the SDK. The API and Dashboard are for companies building a hosted payment gateway service.
Repository Structure
payvault/
├── packages/
│ └── sdk/ # TypeScript SDK — npm package `payvault-sdk`
│ ├── src/
│ │ ├── client.ts # PayVault main client class
│ │ ├── types.ts # All TypeScript interfaces
│ │ ├── errors.ts # Structured error classes
│ │ ├── http.ts # HTTP client with retry logic
│ │ ├── utils.ts # Shared utilities
│ │ └── providers/
│ │ ├── paystack.ts # Paystack implementation
│ │ └── flutterwave.ts # Flutterwave implementation
│ ├── checkout/ # Embeddable checkout widget
│ ├── package.json
│ └── tsconfig.json
│
├── apps/
│ ├── api/ # Go backend API
│ │ ├── cmd/api/ # Main entry point
│ │ ├── internal/
│ │ │ ├── handlers/ # HTTP request handlers
│ │ │ ├── services/ # Business logic (Paystack, Flutterwave)
│ │ │ ├── models/ # Database models
│ │ │ ├── middleware/ # Auth, logging, CORS
│ │ │ └── queue/ # Redis job queue
│ │ ├── migrations/ # SQL schema migrations
│ │ ├── go.mod
│ │ ├── Dockerfile
│ │ └── env.example
│ │
│ └── dashboard/ # React merchant dashboard
│ ├── src/
│ │ ├── pages/ # Page components
│ │ ├── components/ # Reusable UI components
│ │ ├── services/ # API client
│ │ └── store/ # State management
│ ├── public/
│ ├── index.html
│ ├── vite.config.ts
│ └── package.json
│
├── docker-compose.yml # PostgreSQL + Redis
├── LICENSE
└── README.md
SDK Usage
The SDK is the core of PayVault — a TypeScript client that abstracts payment provider differences.
Initialize a Transaction
import { PayVault } from 'payvault-sdk';
const vault = PayVault.paystack('sk_test_xxxxx');
const tx = await vault.initializeTransaction({
amount: 5000,
email: 'customer@example.com',
currency: 'NGN',
metadata: { orderId: 'order_123' },
});
// Redirect customer to tx.authorizationUrl
Verify a Transaction
const result = await vault.verifyTransaction('pvt_ps_abc123');
if (result.success) {
console.log(`Paid ${result.amount} ${result.currency}`);
console.log(`Channel: ${result.channel}`); // 'card', 'bank_transfer', etc.
console.log(`Customer: ${result.customer.email}`);
// Save authorization code for recurring charges
if (result.authorization?.reusable) {
await saveAuthCode(result.customer.email, result.authorization.code);
}
}
Switch to Flutterwave
The killer feature — change one line to switch providers:
- const vault = PayVault.paystack('sk_test_xxxxx');
+ const vault = PayVault.flutterwave('FLWSECK_TEST-xxxxx');
Everything else stays the same. Same method names, same response shapes, same types.
Handle Webhooks
import express from 'express';
import { PayVault } from 'payvault-sdk';
const app = express();
const vault = PayVault.paystack('sk_live_xxxxx', {
webhookSecret: 'whsec_xxxxx',
});
// Register handlers
vault.on('charge.success', async (event) => {
console.log(`Payment received: ${event.amount} ${event.currency}`);
await fulfillOrder(event.reference);
});
vault.on('charge.failed', async (event) => {
await notifyCustomer(event.customer.email);
});
// Webhook endpoint
app.post('/webhooks/payments', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const signature =
(req.headers['x-paystack-signature'] as string) ||
(req.headers['verif-hash'] as string);
const event = await vault.handleWebhook(req.body, signature);
res.status(200).json({ received: true });
} catch (err) {
res.status(401).json({ error: 'Invalid signature' });
}
});
Payment Links
PayVault lets you create shareable payment pages without writing any frontend code. This is handled by the API, so no SDK is required.
Create a link
curl -X POST http://localhost:8081/api/v1/dashboard/links \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Plan",
"description": "One-time upgrade to premium",
"link_type": "fixed",
"amount": 5000,
"currency": "NGN"
}'
Share the link
The response includes a slug. Your checkout URL is:
http://your-api-host/api/v1/checkout/{slug}
Opening this URL shows a branded dark-theme checkout page. The customer enters their email, clicks Pay, and gets redirected to Paystack's ho
