Skip to main content

Payment Gateway Integration: M-PESA, Paystack, and Stripe

00:04:09:60

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:

  1. Customer initiates payment on your platform
  2. Payment data sent to payment gateway (never stored on your server)
  3. Gateway processes with card networks
  4. Webhook notification confirms success/failure
  5. Order fulfillment based on payment status

Mobile Money (M-PESA)

M-PESA integration follows a different pattern:

  1. Initiate STK Push to customer's phone
  2. Customer authorizes on their phone
  3. Callback received with payment status
  4. Verify transaction via API
  5. 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

javascript
// 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:

  1. Validate callback signature (if provided)
  2. Check transaction status via API
  3. Idempotency checks: Prevent duplicate processing
  4. Update database atomically
  5. 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

javascript
// 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:

javascript
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

javascript
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:

javascript
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:

  1. First retry: 1 day after failure
  2. Second retry: 3 days after first retry
  3. Third retry: 7 days after second retry
  4. 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

  1. Security first: Never store card data, always verify webhooks
  2. Handle failures gracefully: Payment failures are normal
  3. Test thoroughly: Use test cards and sandbox environments
  4. Monitor everything: Payment issues need immediate attention
  5. Document flows: Payment logic is complex—document it well
  6. 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.