Payment Gateway Integration: A Developer's Practical Guide

Integrating a payment gateway sounds straightforward until you actually do it. Myles Ndlovu has integrated with multiple payment providers across different markets and learned that the devil is in the details — webhooks, error handling, reconciliation, and edge cases.
Choosing a Provider
Before writing a single line of code, choose the right provider for your market and use case:
Key Criteria
- Market coverage: Do they support your target countries and currencies?
- Payment methods: Cards, bank transfers, mobile money, USSD — which do your customers use?
- Pricing: Transaction fees, setup costs, monthly minimums
- API quality: Documentation, SDK availability, sandbox environment
- Settlement speed: Daily, weekly, or custom settlement to your bank account
- Support: Response time for integration issues and production incidents
African Market Options
- Paystack: Strong in Nigeria, Ghana, South Africa. Clean API, good documentation.
- Flutterwave: Broad African coverage. Supports many payment methods.
- Yoco: South African focus. Card terminals and online payments.
- DPO Group: Pan-African. Extensive payment method support.
- Ozow: South African instant EFT specialist.
The Integration Flow
Most payment gateways follow a similar flow:
1. Customer clicks "Pay"
2. Your server creates a payment session via the gateway API
3. Customer is redirected to the gateway's payment page (or uses an embedded form)
4. Customer enters payment details and authorises
5. Gateway processes the payment
6. Gateway redirects customer back to your site
7. Gateway sends a webhook to your server confirming the result
8. Your server updates the order status Server-Side Implementation
Creating a Payment Session
async function createPayment(order: Order) {
const response = await paymentGateway.createSession({
amount: order.total,
currency: order.currency,
reference: order.id, // Your unique order reference
callbackUrl: 'https://yoursite.com/api/webhooks/payment',
returnUrl: 'https://yoursite.com/order/complete',
metadata: {
orderId: order.id,
customerId: order.customerId
}
});
// Save the gateway session ID against your order
await db.orders.update(order.id, {
paymentSessionId: response.sessionId,
status: 'payment_pending'
});
return response.paymentUrl;
} Handling Webhooks
Webhooks are the most important part of the integration. They’re the reliable notification that a payment succeeded or failed.
async function handlePaymentWebhook(req: Request) {
// 1. Verify the webhook signature
const signature = req.headers['x-gateway-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return { status: 401 };
}
// 2. Parse the event
const event = JSON.parse(req.body);
// 3. Idempotency check — don't process the same event twice
const processed = await db.webhookEvents.findByEventId(event.id);
if (processed) return { status: 200 };
// 4. Process based on event type
switch (event.type) {
case 'payment.success':
await fulfillOrder(event.data.reference);
break;
case 'payment.failed':
await handleFailedPayment(event.data.reference);
break;
}
// 5. Record that we processed this event
await db.webhookEvents.create({ eventId: event.id, processedAt: new Date() });
return { status: 200 };
} Critical webhook rules:
- Always verify signatures: Never trust unverified webhooks
- Always respond with 200: Even if processing fails. Handle errors internally and retry processing yourself. If you return an error, the gateway will retry, potentially causing duplicate processing.
- Always implement idempotency: Webhooks can be delivered multiple times
- Never rely solely on redirects: The customer might close their browser before the redirect. Webhooks are the authoritative confirmation.
Testing
Sandbox Environment
Every reputable payment gateway provides a sandbox with test credentials. Use test card numbers and test mobile money numbers to simulate:
- Successful payments
- Failed payments (insufficient funds, expired card, etc.)
- Pending payments (3D Secure, mobile money approval)
- Webhook delivery
Test Scenarios Checklist
- Happy path: successful payment, webhook received, order fulfilled
- Failed payment: customer sees appropriate error, can retry
- Webhook failure: your server is down when the webhook arrives — does the gateway retry?
- Duplicate webhooks: same event delivered twice — does your system handle it?
- Timeout: customer starts payment but doesn’t complete — how does your system handle abandoned payments?
- Currency mismatch: amount differs between your records and the gateway — how do you detect this?
Error Handling
Payments fail for many reasons. Handle each gracefully:
function getCustomerMessage(errorCode: string): string {
const messages: Record<string, string> = {
'insufficient_funds': 'Your payment was declined due to insufficient funds.',
'card_expired': 'Your card has expired. Please use a different card.',
'do_not_honour': 'Your bank declined this transaction. Please contact your bank.',
'processing_error': 'We encountered an issue processing your payment. Please try again.',
};
return messages[errorCode] || 'Payment could not be processed. Please try again.';
} Never show raw error codes to customers. Map gateway errors to friendly messages.
Reconciliation
Daily reconciliation ensures your records match the gateway’s:
- Download the gateway’s settlement report
- Compare each transaction against your database
- Flag discrepancies: missing transactions, amount mismatches, status differences
- Investigate and resolve discrepancies
Automate this process. Manual reconciliation doesn’t scale.
Security
- Never log full card numbers or CVVs: PCI DSS compliance requires it
- Use the gateway’s hosted payment page: Avoids handling card data on your servers
- Rotate API keys: Change keys periodically and immediately if compromised
- Restrict webhook endpoints: Only accept requests from the gateway’s known IP addresses
- Use HTTPS: For all API calls and webhook endpoints
Going Live
Before switching from sandbox to production:
- Complete all test scenarios in sandbox
- Verify webhook handling with the gateway’s webhook testing tool
- Set up monitoring for payment success rates and processing times
- Create an incident response plan for payment failures
- Start with low transaction limits and increase gradually
Payment integration is one of those things that seems simple but rewards careful, detail-oriented implementation. Get it right, and your customers never think about it. Get it wrong, and it’s all they think about.
Myles Ndlovu builds algorithmic trading engines, crypto platforms, and payment infrastructure for emerging markets. Read more about Myles or get in touch.