@mopay/node-sdk
v0.1.6
Published
Official TypeScript SDK for the MoPay payment API.
Maintainers
Readme
MoPay Node SDK
TypeScript SDK for creating and verifying MoPay hosted payment sessions from Node.js.
MoPay's flow is:
- Create a payment session from your backend.
- Redirect the customer to the returned
paymentUrl. - When MoPay redirects the customer back, verify the final state with the session API.
Installation
pnpm add @mopay/node-sdkUsage
import { MoPay } from "@mopay/node-sdk";
const mopay = new MoPay({
apiKey: process.env.MOPAY_API_KEY!,
});
const session = await mopay.createPaymentSession({
amount: "100.00",
reference: "ORDER12345",
redirectUrl: "https://yourwebsite.com/payment-complete",
description: "Order #12345",
customerEmail: "[email protected]",
customerName: "John Doe",
});
console.log(session.paymentUrl);Browser Checkout
Use the secret-key SDK only on the merchant backend. The frontend should receive a checkout URL or token from the merchant backend, then call MoPayCheckout.open({ checkoutUrl }).
Merchant backend:
import { MoPay } from "@mopay/node-sdk";
const mopay = new MoPay({
apiKey: process.env.MOPAY_API_KEY!,
});
const session = await mopay.createPaymentSession({
amount: "100.00",
reference: "ORDER12345",
redirectUrl: "https://yourwebsite.com/payment-complete",
});
return {
sessionId: session.sessionId,
checkoutUrl: session.paymentUrl,
};Merchant frontend:
import { MoPayCheckout } from "@mopay/node-sdk";
const session = await fetch("/api/create-mopay-checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: "100.00", reference: "ORDER12345" }),
}).then((response) => response.json());
MoPayCheckout.open({
checkoutUrl: session.checkoutUrl,
allowedOrigins: [window.location.origin],
width: 520,
height: 760,
resizable: true,
closeOnRedirect: true,
redirectAfterClose: true,
onSuccess: () => {
// UI feedback only. Do not deliver value here.
},
onClose: async () => {
await fetch(`/api/verify-mopay-session/${session.sessionId}`);
},
});Supported modes:
dialog: opens an iframe dialog inside your UI. This is the default.popup: opens the hosted MoPay page in a centered browser popup.redirect: sends the current page topaymentUrl.
For dialog mode, width, height, minWidth, minHeight, maxWidth, and maxHeight control the SDK-owned frame around the hosted MoPay page. Set resizable: true if customers should be able to drag-resize the checkout frame.
For iframe checkout, MoPay's hosted page must allow embedding from approved merchant sites. The MoPay checkout server should set Content-Security-Policy: frame-ancestors ... dynamically from the merchant's registered allowed origins and avoid X-Frame-Options: DENY or SAMEORIGIN for embeddable checkout pages.
The SDK only accepts postMessage events from the checkout URL origin, and only from the opened iframe or popup window. Frontend callbacks are for UI feedback only; merchants must confirm payment through signed webhooks or mopay.getTransaction(sessionId) before delivering value.
If your checkout iframe can redirect back to a merchant-owned completion page, pass that exact origin in allowedOrigins. The checkout URL origin is always trusted automatically.
Closing Checkout After Redirect
The SDK cannot inspect or interrupt a cross-origin iframe while it is still on MoPay, 3DS, or a bank page. The standard pattern is to make your redirectUrl a small merchant-owned bridge page. Once the iframe or popup reaches that page, the bridge page notifies the parent SDK, then the SDK can close the dialog or popup and optionally redirect the main merchant page.
Merchant page:
MoPayCheckout.open({
checkoutUrl: session.checkoutUrl,
allowedOrigins: [window.location.origin],
closeOnRedirect: true,
redirectAfterClose: true,
});Completion bridge page:
<script type="module">
import { MoPayCheckout } from "/sdk/index.js";
MoPayCheckout.completeRedirect({
redirectTo: `/payment-success${window.location.search}`,
});
</script>If redirectAfterClose is true, the SDK uses the redirectTo value from the bridge message. You can also pass a fixed URL string or a function:
MoPayCheckout.open({
checkoutUrl,
closeOnRedirect: true,
redirectAfterClose: (message) => `/orders/${message.reference}`,
});Use closeOnRedirect: true without redirectAfterClose if you want to close the iframe or popup but keep the main merchant page where it is.
Redirect URL Handling
Your redirectUrl belongs to your merchant application, not the browser SDK. Make the route accept both redirect shapes:
GET /payment-complete?status=success&sessionId=...POST /payment-completewithContent-Type: application/x-www-form-urlencoded
Some checkout or 3DS flows return with a form POST, so a GET-only route can show a broken iframe or browser error even when the payment completed.
Express example:
import express from "express";
import { MoPay, parseRedirect } from "@mopay/node-sdk";
const app = express();
const mopay = new MoPay({ apiKey: process.env.MOPAY_API_KEY! });
app.use(express.urlencoded({ extended: false }));
app.all("/payment-complete", async (req, res) => {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(req.query)) {
if (value !== undefined) params.set(key, String(value));
}
if (req.body && typeof req.body === "object") {
for (const [key, value] of Object.entries(req.body)) {
if (value !== undefined) params.set(key, String(value));
}
}
const redirect = parseRedirect(params);
const verified = await mopay.getTransaction(redirect.sessionId);
if (mopay.isSuccessful(verified)) {
// Mark the order paid. Do not rely only on the redirect parameters.
}
res.redirect(303, `/payment-complete?${params.toString()}`);
});Next.js App Router example:
import { NextResponse } from "next/server";
import { MoPay, parseRedirect } from "@mopay/node-sdk";
const mopay = new MoPay({ apiKey: process.env.MOPAY_API_KEY! });
export async function GET(request: Request) {
return handleRedirect(request);
}
export async function POST(request: Request) {
return handleRedirect(request);
}
async function handleRedirect(request: Request) {
const url = new URL(request.url);
const params = new URLSearchParams(url.searchParams);
if (request.method === "POST") {
const body = await request.formData();
for (const [key, value] of body.entries()) {
params.set(key, String(value));
}
}
const redirect = parseRedirect(params);
const verified = await mopay.getTransaction(redirect.sessionId);
if (mopay.isSuccessful(verified)) {
// Mark the order paid. Do not deliver value from frontend callbacks alone.
}
return NextResponse.redirect(new URL(`/payment-complete?${params.toString()}`, url), 303);
}For iframe checkout, your completion page can call MoPayCheckout.completeRedirect(...) or window.parent.postMessage(...) so the checkout dialog updates the merchant UI. Treat that message as UI feedback only; fulfilment should still depend on webhooks or mopay.getTransaction(sessionId).
Verify A Redirect
Redirect parameters are useful for routing your user, but they should not be treated as the final source of truth. Always verify with MoPay from your backend.
import { MoPay, parseRedirect } from "@mopay/node-sdk";
const redirect = parseRedirect(
"https://yourwebsite.com/payment-complete?status=success&reference=ORDER12345&transactionId=TXN123&sessionId=MOP_abc123_ORDER12345&amount=100.00&paymentMethod=mpesa",
);
const mopay = new MoPay({ apiKey: process.env.MOPAY_API_KEY! });
const verified = await mopay.retrieveSession(redirect.sessionId);
if (verified.session.status === "COMPLETED") {
// Fulfil the order.
}API
new MoPay(options)
const mopay = new MoPay({
apiKey: "YOUR_API_KEY",
baseUrl: "https://mopay.co.ls",
timeoutMs: 30000,
});apiKey is required. baseUrl is optional and defaults to https://mopay.co.ls.
mopay.createPaymentSession(params)
Creates a hosted payment session with POST /api/external/payment.
Required fields:
amount: string or number, for example"100.00".reference: unique alphanumeric order reference, for example"ORDER12345".redirectUrl: absolute URL where MoPay should send the customer after payment.
Optional fields:
descriptioncustomerEmailcustomerNamepaymentFrequency:ONCE,MONTHLY, orANNUALLYproductNameproductDetailsproductImagepayWhatYouWantminimumAmount
mopay.retrieveSession(sessionId)
Retrieves session details with GET /api/external/session/v1/{sessionId}.
mopay.parseRedirect(input) or parseRedirect(input)
Parses MoPay redirect query parameters from a URL, URLSearchParams, or plain object.
MoPayCheckout.open({ checkoutUrl, ...options })
Opens the hosted checkout page in popup, dialog, or redirect mode.
Accepts checkoutUrl, checkout_url, checkoutToken, or checkout_token. checkoutUrl is the same value as the documented paymentUrl returned by createPaymentSession.
Set closeOnRedirect: true to close the dialog or popup when a trusted merchant completion page sends a mopay.checkout.redirect, mopay.checkout.close, or terminal status message. Set redirectAfterClose to true, a URL string, a URL, or a function if the main merchant page should navigate after the checkout frame closes.
MoPayCheckout.completeRedirect(options)
Browser helper for the merchant-owned completion page. It reads MoPay redirect query parameters from window.location.search, posts a mopay.checkout.redirect message to window.parent and window.opener, and returns the posted message.
mopay.checkout(params, options)
Convenience method that creates a session and opens the checkout dialog. Use this only from a trusted environment because it requires the secret API key.
mopay.getSession(sessionId) / mopay.getTransaction(sessionId)
Retrieves the latest MoPay session and transaction data for any session ID.
mopay.isSuccessful(session)
Returns true when a retrieved session is completed or has transactionStatus: "success".
mopay.isPaymentSuccessful(sessionId)
Retrieves the session by ID and returns true when the payment is successful.
mopay.waitForSession(sessionId, options)
Polls MoPay until a session reaches a terminal state such as COMPLETED, FAILED, CANCELLED, or EXPIRED.
Sandbox Mobile Money Numbers
Use these only while your MoPay project is in sandbox mode.
| Method | Number | Expected result |
| --- | --- | --- |
| M-Pesa | 52211111 | Immediate success |
| M-Pesa | 52222222 | Pending first, then success |
| M-Pesa | 52233333 | Failed |
| M-Pesa | 52244444 | Cancelled |
| M-Pesa | 52255555 | Expired |
| EcoCash | 63211111 | Immediate success |
| EcoCash | 63222222 | Pending first, then success |
| EcoCash | 63233333 | Failed |
| EcoCash | 63244444 | Cancelled |
| EcoCash | 63255555 | Expired |
