The Critical Path: Payments
Payment integration is one of the most critical parts of any commercial application. Get it wrong, and you lose revenue and customer trust. Get it right, and you enable seamless transactions that drive business growth.
Understanding Payment Flows
Card Payments
The standard flow for card payments:
- Customer initiates payment on your platform
- Payment data sent to payment gateway (never stored on your server)
- Gateway processes with card networks
- Webhook notification confirms success/failure
- Order fulfillment based on payment status
Mobile Money (M-PESA)
M-PESA integration follows a different pattern:
- Initiate STK Push to customer's phone
- Customer authorizes on their phone
- Callback received with payment status
- Verify transaction via API
- Update order status
M-PESA/Daraja API Integration
Setup & Authentication
M-PESA requires careful setup:
- Consumer Key & Secret: From Safaricom Developer Portal
- Passkey: For STK Push authentication
- Shortcode: Your business number
- Callback URLs: For payment notifications
STK Push Implementation
// Initiate STK Push
const stkPush = async (phoneNumber, amount, accountReference) => {
const timestamp = new Date().toISOString().replace(/[^0-9]/g, '').slice(0, -3);
const password = Buffer.from(shortcode + passkey + timestamp).toString('base64');
const response = await axios.post(
'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
{
BusinessShortCode: shortcode,
Password: password,
Timestamp: timestamp,
TransactionType: 'CustomerPayBillOnline',
Amount: amount,
PartyA: phoneNumber,
PartyB: shortcode,
PhoneNumber: phoneNumber,
CallBackURL: callbackUrl,
AccountReference: accountReference,
TransactionDesc: 'Payment for services'
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
};
Callback Handling
Always verify callbacks:
- Validate callback signature (if provided)
- Check transaction status via API
- Idempotency checks: Prevent duplicate processing
- Update database atomically
- Send confirmation to customer
Paystack Integration
Setup
Paystack is popular in Nigeria and other African countries:
- Public Key: For client-side integration
- Secret Key: For server-side operations
- Webhook Secret: For verifying webhook authenticity
Payment Initialization
// Initialize payment
const initializePayment = async (email, amount, reference) => {
const response = await axios.post(
'https://api.paystack.co/transaction/initialize',
{
email,
amount: amount * 100, // Convert to kobo
reference,
callback_url: 'https://yoursite.com/payment/callback'
},
{
headers: {
Authorization: `Bearer ${secretKey}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
};
Webhook Verification
Always verify webhook signatures:
const crypto = require('crypto');
const verifyPaystackWebhook = (payload, signature) => {
const hash = crypto
.createHmac('sha512', webhookSecret)
.update(JSON.stringify(payload))
.digest('hex');
return hash === signature;
};
Stripe Integration
Setup
Stripe is global and feature-rich:
- Publishable Key: Client-side
- Secret Key: Server-side
- Webhook Secret: For webhook verification
Payment Intent
const stripe = require('stripe')(secretKey);
// Create payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100, // Convert to cents
currency: 'usd',
metadata: {
order_id: orderId,
user_id: userId
}
});
// Client confirms payment
// Webhook handles completion
Webhook Handling
Stripe webhooks require careful handling:
const event = stripe.webhooks.constructEvent(
payload,
signature,
webhookSecret
);
switch (event.type) {
case 'payment_intent.succeeded':
await handleSuccessfulPayment(event.data.object);
break;
case 'payment_intent.payment_failed':
await handleFailedPayment(event.data.object);
break;
// Handle other event types
}
Security Best Practices
Never Store Card Data
- PCI Compliance: Use payment gateways to avoid PCI scope
- Tokenization: Use tokens instead of raw card data
- 3D Secure: Enable for additional security
Webhook Security
- Verify signatures: Always verify webhook authenticity
- Idempotency: Handle duplicate webhooks gracefully
- HTTPS only: Never accept webhooks over HTTP
- Rate limiting: Protect webhook endpoints
Error Handling
- Graceful failures: Don't expose internal errors to customers
- Retry logic: Implement retries for transient failures
- Logging: Log all payment attempts (securely)
- Monitoring: Alert on payment failures
Subscription Management
Recurring Payments
For subscriptions, I implement:
- Subscription objects: Track subscription state
- Webhook handlers: Process subscription events
- Dunning management: Handle failed payments
- Proration: Handle plan changes fairly
Payment Retry Logic
Automated retry for failed payments:
- First retry: 1 day after failure
- Second retry: 3 days after first retry
- Third retry: 7 days after second retry
- Cancel subscription: After final failure
Testing
Test Cards & Accounts
Each gateway provides test credentials:
- Stripe: Test card numbers for various scenarios
- Paystack: Test mode with test cards
- M-PESA: Sandbox environment with test numbers
Webhook Testing
Use tools like:
- Stripe CLI: Test webhooks locally
- ngrok: Expose local server for testing
- Gateway dashboards: Trigger test webhooks
Real-World Implementation
For a SaaS platform, I integrated:
- Stripe: For international customers (cards)
- Paystack: For Nigerian customers
- M-PESA: For Kenyan customers
The implementation included:
- Unified payment interface
- Automatic gateway selection by region
- Webhook processing with idempotency
- Subscription management
- Payment retry logic
- Comprehensive error handling
Results:
- 99.5% payment success rate
- Zero payment-related security incidents
- Seamless multi-gateway experience
- Automated subscription renewals
Key Takeaways
- Security first: Never store card data, always verify webhooks
- Handle failures gracefully: Payment failures are normal
- Test thoroughly: Use test cards and sandbox environments
- Monitor everything: Payment issues need immediate attention
- Document flows: Payment logic is complex—document it well
- Plan for edge cases: What happens if webhook arrives before redirect?
Payment integration is complex, but following best practices and using established gateways makes it manageable. The key is understanding each gateway's specific requirements and implementing robust error handling and security measures.
