BoomPay/partners

M-Pesa

Offer BoomPay alongside M-Pesa's STK Push for merchants in mobile-money markets: M-Pesa prompts the customer's phone, BoomPay redirects to a hosted wallet page — both presented as options at checkout.

Mobile MoneySafaricom Daraja APIParallel checkout option

M-Pesa’s Daraja API initiates payments via STK Push — a mobile prompt sent directly to the customer’s phone. This is a different UX model from BoomPay’s browser redirect, and both can coexist on the same checkout page. Merchants in mobile-money markets (Kenya, Tanzania, Ghana, and others where M-Pesa operates) can offer M-Pesa for local mobile money users and Boomcoin as a crypto alternative, with your platform handling both outcomes through independent return paths.

Flow

01

Checkout loads

Server initiates an M-Pesa STK Push (customer's phone gets a prompt) and creates a BoomPay intent link simultaneously.

02

Two parallel paths

Customer can complete the M-Pesa prompt on their phone, or click the Boomcoin button to pay on BoomPay's hosted page.

03

M-Pesa path

Daraja's callback URL receives the M-Pesa result asynchronously.

04

BoomPay path

BoomPay redirects to your return URL with a signed paymentIntentId.

05

First to complete wins

Your platform marks the order paid via whichever path completed, cancels the other, and notifies the merchant.

Code

mpesa/prepare-checkout.js
const BoomPay = require('boom-pay-sdk');

// For merchants accepting M-Pesa STK Push, offer BoomPay as a parallel
// crypto alternative. M-Pesa's Daraja API initiates the mobile money flow;
// BoomPay's redirect initiates the Boomcoin flow.
async function prepareCheckout(merchant, order, customerPhone) {
  const boomPay = new BoomPay({
    apiKey: merchant.boompayApiKey,
    sandbox: process.env.NODE_ENV !== 'production',
  });

  // M-Pesa STK Push -- unchanged. Prompts customer's phone directly.
  const stkPushResult = await initiateStkPush(merchant, order, customerPhone);

  // BoomPay intent for the Boomcoin alternative.
  const intent = await boomPay.payments.createIntent({
    amount: toBmc(order.total, order.currency),
    successUrl: `${process.env.PSP_BASE}/boompay/return?status=success&order=${order.id}&mid=${merchant.id}`,
    failureUrl: `${process.env.PSP_BASE}/boompay/return?status=failure&order=${order.id}&mid=${merchant.id}`,
    label: `${merchant.name} Order ${order.reference}`,
    metadata: { orderId: order.id, merchantId: merchant.id },
  });

  return {
    stkPushCheckoutRequestId: stkPushResult.CheckoutRequestID,
    boompayLink: intent.link,
  };
}

// M-Pesa amounts are in KES (Kenyan Shillings, whole numbers).
function toBmc(amount, currency) {
  return amount; // placeholder -- implement real KES-to-BMC rate
}

async function initiateStkPush(merchant, order, phone) {
  // Your existing Daraja API STK Push call -- unchanged.
}
mpesa/boompay-return.js
const crypto = require('crypto');

// BoomPay return handler for the Boomcoin path.
// M-Pesa's result comes via Daraja's own callback URL (separate route).
async function handleBoompayReturn(req, res) {
  const { order: orderId, mid, status } = req.query;
  const intentId = req.query.paymentIntentId;
  const signature = (req.query['X-Boom-Signature'] || '').replace(/ /g, '+');
  const merchant = await db.getMerchant(mid);
  const expected = crypto.createHmac('sha1', merchant.boompayApiKey).update(intentId).digest('base64');
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) return res.status(403).send('Invalid signature');
  const boomPay = new BoomPay({ apiKey: merchant.boompayApiKey, sandbox: process.env.NODE_ENV !== 'production' });
  const payment = await boomPay.payments.getPayment(intentId);
  if (status === 'success' && payment.paidAt) {
    await db.markOrderPaid(orderId, { provider: 'boompay', reference: intentId });
    await notifyMerchant(merchant, 'PAYMENT_COMPLETE', orderId);
    return res.redirect(merchant.successUrl);
  }
  await db.markOrderFailed(orderId);
  res.redirect(merchant.failureUrl);
}
!

M-Pesa STK Push is asynchronous — the Daraja callback may arrive seconds to minutes after the customer sees the prompt. BoomPay’s return is synchronous (browser redirect). Your platform must handle both timings correctly and prevent both paths from fulfilling the same order if the customer somehow completes both.

Go-live checklist

  • Implement a real KES→BMC (or local currency→BMC) conversion in toBmc().
  • Implement idempotency for the M-Pesa callback and the BoomPay return — both can theoretically arrive for the same order.
  • Confirm BoomPay’s hosted page is accessible on mobile networks in your target M-Pesa markets.