Migrate from another provider
Step-by-step guide to replace your existing payment integration with Wajub, regardless of your current provider.
This guide covers migration from any payment system: aggregator (Paystack, Stripe, CinetPay…), direct operator integration (MTN MoMo API, Orange Money API…) or homegrown solution.
Phase 1: Map the existing setup
Before coding, inventory your current integration:
| Element | Your current setup | Wajub equivalent |
|---|---|---|
| Payment initialization | Proprietary endpoint / SDK | POST /payments |
| Payment page | Embedded or redirect | Wajub hosted page |
| Confirmation | Callback / polling | Webhooks + verification |
| Refund | Back-office or API | POST /refunds |
| Outgoing transfers | Varies by provider | POST /transfers |
Phase 2: Adapt your abstraction layer
Ideally, encapsulate payment logic behind an interface. This makes dual write and roll-out easier:
// payment-gateway.js — single interface
class PaymentGateway {
async initializeCheckout(order) { throw new Error('Not implemented'); }
async verifyTransaction(reference) { throw new Error('Not implemented'); }
async refundTransaction(reference, amount) { throw new Error('Not implemented'); }
processWebhook(payload, signature) { throw new Error('Not implemented'); }
}// wajub-adapter.js — no official SDK yet, calling the REST API directly
const BASE_URL = 'https://api.wajub.com';
class WajubAdapter extends PaymentGateway {
constructor() {
super();
this.publicKey = process.env.WAJUB_PUBLIC_KEY;
this.secretKey = process.env.WAJUB_SECRET_KEY;
this.webhookSecret = process.env.WAJUB_WEBHOOK_SECRET;
}
async initializeCheckout(order) {
const res = await fetch(`${BASE_URL}/payments`, {
method: 'POST',
headers: { Authorization: this.publicKey, 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: order.amount,
currency: order.currency,
email: order.email,
phone: order.phone,
reference: `ORDER-${order.id}`,
callback: `${process.env.BASE_URL}/payment/return`,
}),
}).then(r => r.json());
return { id: res.transaction.id, redirectUrl: res.authorization_url };
}
async verifyTransaction(id) {
const res = await fetch(`${BASE_URL}/payments/${id}`, {
headers: { Authorization: this.publicKey },
}).then(r => r.json());
return { status: res.transaction.status, amount: res.transaction.amount };
}
async refundTransaction(paymentId, amount) {
const res = await fetch(`${BASE_URL}/refunds`, {
method: 'POST',
headers: { Authorization: this.secretKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ payment: paymentId, amount, reason: 'requested_by_customer' }),
}).then(r => r.json());
return { id: res.refund.id, status: res.refund.status };
}
// See /tools/webhooks/signature-verification for the full HMAC implementation
processWebhook(rawBody, timestamp, signatureHeader) {
return verifyWajubSignature(rawBody, timestamp, signatureHeader, this.webhookSecret);
}
}Phase 3: Dual write
During the transition, send transactions to both systems:
const legacyGateway = new LegacyProviderAdapter();
const wajubGateway = new WajubAdapter();
async function initializeCheckout(order) {
// Always send to the old provider
const legacy = await legacyGateway.initializeCheckout(order);
// Dual write to Wajub (sandbox)
if (process.env.WAJUB_DUAL_WRITE === 'true') {
try {
const wajub = await wajubGateway.initializeCheckout(order);
console.log('Wajub (sandbox):', {
legacyRef: legacy.reference,
wajubRef: wajub.reference,
});
} catch (err) {
console.error('Wajub dual write skipped:', err.message);
}
}
// If toggle is on, use Wajub
if (process.env.PAYMENT_PROVIDER === 'wajub') {
const wajub = await wajubGateway.initializeCheckout(order);
return wajub.redirectUrl;
}
return legacy.redirectUrl;
}Phase 4: Progressive roll-out
Once dual write is validated, gradually shift traffic:
function shouldUseWajub(orderId) {
const percentage = parseInt(process.env.WAJUB_ROLLOUT_PERCENT, 10) || 0;
// Deterministic hash based on order ID
const hash = crypto
.createHash('md5')
.update(orderId)
.digest('hex');
const bucket = parseInt(hash.slice(0, 8), 16) % 100;
return bucket < percentage;
}
// Usage
const gateway = shouldUseWajub(order.id)
? wajubGateway
: legacyGateway;
const { redirectUrl } = await gateway.initializeCheckout(order);Common differences to anticipate
Amount format
Some providers use subunits (kobos, centimes). Wajub uses the major unit (CFA francs, nairas, cedis…). Check your conversions.
Transaction statuses
| Old status (example) | Wajub status |
|---|---|
success / successful | succeeded |
pending | pending |
failed / declined | failed |
abandoned / timeout | cancelled / expired |
refunded | refunded |
Webhooks
If your old provider didn't use webhooks (or without signature), adding signature verification is a requirement with Wajub. Plan for it from the start.
Test reconciliation
Before cutting off the old provider, reconcile your transactions over at least 72 hours of real traffic: compare amounts, statuses, and fees between the two systems.