npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mzawadi-mini-app/mini-app-pay

v0.3.2

Published

M-Pesa PayBill payments for Alipay mini-app webviews. ES5-safe, mandatory paybill (v2).

Readme

Mini-App Pay

A tiny JavaScript library for processing M-Pesa payments — PayBill, Till (Buy Goods), and Bonga PayBill (pay a paybill with Bonga points) — inside Alipay mini-app webviews. It is generic and not tied to any single business: the caller supplies the paybill or till number, so anyone who needs to take payments from inside a mini app can use it. It wraps Alipay's AlipayJSBridge payment API behind simple, promise-based functions and returns a structured result for both success and failure.

Two builds: legacy vs v2

The library ships in two flavours that expose the same MiniAppPay.makePayment global but differ in one safety rule:

| Build | File(s) | paybillNo | Use it for | |-------|---------|-------------|------------| | Legacy | payment-v1.js, mini-app-pay.iife.js | Optional, built-in default | Existing embeds already on the CDN — behavior is frozen. | | v2 (strict) | mini-app-pay.v2.js | Mandatory | All new integrations, npm, and the future CDN package. |

⚠️ Never load a legacy build and v2 on the same page — they both define the MiniAppPay global and whichever loads last wins.

Why v2 requires paybillNo: for a globally-reused library, a hardcoded default paybill risks routing real payments to the wrong recipient. In v2 a missing/blank paybillNo fails immediately and never calls AlipayJSBridge.

Features

  • 🔒 Native Alipay payment integration (M-Pesa PayBill, Till / Buy Goods & Bonga, KES)
  • 🎯 Built-in consent dialog before every payment
  • 🛡️ Structured error handling with clear messages
  • 📊 Transaction reference echoed back on every response
  • 🧱 ES5-safe v2 build for old Alipay webview JS engines

Installation

npm (bundlers / Node)

npm install @mzawadi-mini-app/mini-app-pay
// ESM
import { makePayment } from "@mzawadi-mini-app/mini-app-pay";

// CommonJS
const { makePayment } = require("@mzawadi-mini-app/mini-app-pay");

TypeScript declarations are bundled — no @types package needed.

CDN / <script> (Alipay webview)

Load the Alipay Web View SDK first, then the payment library.

<!-- 1. Alipay Web View SDK (provides window.AlipayJSBridge) -->
<script type="text/javascript" src="https://appx/web-view.min.js"></script>

<!-- 2. Load the v2 (strict) library from a public npm CDN -->

<!-- 2a. Recommended: unpkg -->
<script src="https://unpkg.com/@mzawadi-mini-app/mini-app-pay"></script>

<!-- 2b. Or jsDelivr -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@mzawadi-mini-app/mini-app-pay"></script> -->

<!-- Tip: pin a version for production, e.g. .../[email protected] -->

The cdn.cheche.co.ke host serves the legacy v1 build only. New integrations must load v2 from unpkg or jsDelivr as shown above.

The <script> build attaches the global MiniAppPay.makePayment.

Quick Start (v2)

const PAYBILL_NO = "YOUR_PAYBILL_NUMBER"; // the business/paybill number that receives the money

async function handlePayment() {
  const result = await MiniAppPay.makePayment({
    amount: 100,                               // replace with the actual amount (KES)
    transactionReference: "TXN_" + Date.now(), // replace with your own unique reference
    reason: "Game purchase",                   // replace with what the payment is for
    paybillNo: PAYBILL_NO,                      // replace with your paybill number (MANDATORY in v2)
  });

  if (result.success) {
    console.log("Payment successful!", result.data.transactionId);

    // Optionally forward the result to your own backend for reconciliation.
    await fetch("/your/endpoint", {            // replace with your server endpoint
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        transactionId: result.data.transactionId, // Alipay transaction id
        transactionReference: result.transactionReference,
        amount: 100,                           // the amount you charged
      }),
    });
  } else if (
    result.message === "Payment cancelled by user!" || // consent dialog (step 1)
    (result.data && result.data.error === 201)         // Alipay sheet (step 2)
  ) {
    console.log("User cancelled the payment."); // not a hard error
  } else {
    console.error("Payment failed:", result.message);
  }
}

Quick Start — Till / Buy Goods (v2)

Same flow as PayBill, but routes to an M-Pesa Till (Buy Goods) number via MiniAppPay.makeTillPayment(...). A Till payment is identified only by the till number — there is no billReference.

const TILL_NUMBER = "YOUR_TILL_NUMBER"; // the Till (Buy Goods) number that receives the money

async function handleTillPayment() {
  const result = await MiniAppPay.makeTillPayment({
    amount: 25,                                // replace with the actual amount (KES)
    tillNumber: TILL_NUMBER,                   // replace with your till number (MANDATORY)
    reason: "Groceries",                       // replace with what the payment is for
    transactionReference: "TXN_" + Date.now(), // optional: echoed back for your own tracking
  });

  if (result.success) {
    console.log("Payment successful!", result.data.transactionId);
  } else if (
    result.message === "Payment cancelled by user!" || // consent dialog (step 1)
    (result.data && result.data.error === 201)         // Alipay sheet (step 2)
  ) {
    console.log("User cancelled the payment."); // not a hard error
  } else {
    console.error("Payment failed:", result.message);
  }
}

Quick Start — Bonga PayBill (v2)

Pay a PayBill with Bonga points via MiniAppPay.makeBongaPayment(...). Same signature and flow as makePayment — only the underlying M-Pesa rail differs, so paybillNo is still mandatory.

const PAYBILL_NO = "YOUR_PAYBILL_NUMBER"; // the business/paybill number that receives the money

async function handleBongaPayment() {
  const result = await MiniAppPay.makeBongaPayment({
    amount: 30,                                // replace with the actual amount (KES)
    transactionReference: "TXN_" + Date.now(), // replace with your own unique reference
    reason: "Electricity bill",                // replace with what the payment is for
    paybillNo: PAYBILL_NO,                      // replace with your paybill number (MANDATORY)
  });

  if (result.success) {
    // Bonga success data has NO transactionId: { startTime, message, statusCode }.
    console.log("Payment successful!", result.data.statusCode, result.data.message);
  } else if (
    result.message === "Payment cancelled by user!" || // consent dialog (step 1)
    (result.data && result.data.errorCode === 201)     // Alipay sheet (step 2, Bonga uses errorCode)
  ) {
    console.log("User cancelled the payment."); // not a hard error
  } else {
    console.error("Payment failed:", result.message);
  }
}

What your user sees: the two-step flow

One makePayment(...) call shows two modals in sequence, and the user can stop at either — each gives a different response:

  1. Consent dialog (this library's "Confirm Payment" modal). Tapping Cancel charges nothing and returns { success: false, message: "Payment cancelled by user!" }.
  2. Alipay's native PayBill sheet. Tapping Confirm in step 1 hands off to Alipay. Authorizing returns { success: true, message: "Payment of KES <amount> successful", data: { transactionId, startTime } } (Bonga's success data instead carries { startTime, message, statusCode } — no transactionId). Backing out here is a different cancel — Alipay's own "Canceled by user" with data.error === 201 (Bonga uses data.errorCode === 201).
// ✅ Success — user authorized in step 2
{
  "success": true,
  "message": "Payment of KES 1 successful",
  "data": { "startTime": 308088201, "transactionId": "UJE3JBDHVA" },
  "transactionReference": "TXN_1791962083799"
}

// 🚫 Cancelled the consent dialog (step 1)
{
  "success": false,
  "message": "Payment cancelled by user!",
  "data": { "message": "Payment cancelled by user!" },
  "transactionReference": "TXN_1791962083799"
}

Always branch on result.success first. Only read result.message / result.data.error when you need to tell why it failed. See makePayment-documentation.md for every response shape.

API Reference

makePayment(...) — v2

Accepts either an options object (preferred) or legacy positional arguments with paybillNo as the mandatory 4th argument.

// Options object
MiniAppPay.makePayment({ amount, transactionReference, reason, paybillNo });

// Positional
MiniAppPay.makePayment(amount, transactionReference, reason, paybillNo);

Parameters

  • amount (number | string): payment amount in KES.
  • transactionReference (string): unique id; sent as the PayBill billReference.
  • reason (string, optional): description shown in the consent dialog.
  • paybillNo (string, required in v2): business/paybill number to credit.

Returns: Promise<PayResponse<PayBillSuccessData>>

makeTillPayment(...) — v2

Same shape as makePayment, but targets an M-Pesa Till (Buy Goods) number. Accepts either an options object (preferred) or positional arguments with tillNumber as the mandatory 4th argument.

// Options object
MiniAppPay.makeTillPayment({ amount, tillNumber, reason, transactionReference });

// Positional
MiniAppPay.makeTillPayment(amount, transactionReference, reason, tillNumber);

Parameters

  • amount (number | string): payment amount in KES.
  • tillNumber (string, required): Till (Buy Goods) number to credit.
  • reason (string, optional): description shown in the consent dialog.
  • transactionReference (string, optional): your own reference, echoed back on the response. Unlike PayBill, Buy Goods has no billReference, so this is never sent to the bridge — it is for your bookkeeping only.

Returns: Promise<PayResponse<TillSuccessData>>

makeBongaPayment(...) — v2

Pays a PayBill using Bonga points. Same signature, flow, and PayResponse envelope as makePayment; it just routes through Alipay's lipaNaBongaPaybill method and returns different payload contents (see the note below). paybillNo is mandatory.

// Options object
MiniAppPay.makeBongaPayment({ amount, transactionReference, reason, paybillNo });

// Positional
MiniAppPay.makeBongaPayment(amount, transactionReference, reason, paybillNo);

Parameters

  • amount (number | string): payment amount in KES.
  • transactionReference (string): unique id; sent as the PayBill billReference.
  • reason (string, optional): description shown in the consent dialog.
  • paybillNo (string, required): business/paybill number to credit.

Returns: Promise<PayResponse<BongaSuccessData>>

Bonga response note: the envelope is the same PayResponse, but the contents differ from PayBill — success data has no transactionId and failures carry errorCode (not error):

// ✅ Success
{
  "success": true,
  "message": "Payment of KES 30 successful",
  "data": {
    "startTime": 707937862,
    "message": "Dear customer, your request was processed successfully",
    "statusCode": "200"
  },
  "transactionReference": "TXN_..."
}

// 🚫 Cancel in the Alipay sheet (201)
{
  "success": false,
  "message": "Canceled by user",
  "data": {
    "errorMessage": "Canceled by user",
    "errorCode": 201,
    "startTime": 709531386
  },
  "transactionReference": "TXN_..."
}

Branch on result.success; detect cancellation with result.data.errorCode === 201.

Bridge readiness — isAvailable, onReady, whenReady

Use these to render/execute payment code only when the Alipay webview bridge is present. They're exposed on both the MiniAppPay global and the npm exports.

// 1. Synchronous check
if (MiniAppPay.isAvailable()) {
  // AlipayJSBridge is present right now
}

// 2. Run code once the bridge is ready (handles the AlipayJSBridgeReady event).
//    Returns an unsubscribe function.
const off = MiniAppPay.onReady(function () {
  document.querySelector(".pay-button").disabled = false;
});
// off(); // stop listening if needed

// 3. Promise-based, with an optional timeout (ms)
const ready = await MiniAppPay.whenReady(3000);
if (ready) {
  // safe to call makePayment(...)
} else {
  // not inside a supported Alipay webview (timed out)
}

| Function | Signature | Description | |----------|-----------|-------------| | isAvailable() | () => boolean | True if AlipayJSBridge exists right now. | | onReady(cb) | (cb: () => void) => () => void | Runs cb when ready (async even if already available); returns an unsubscribe function. | | whenReady(timeoutMs?) | (timeoutMs?: number) => Promise<boolean> | Resolves true when ready, or false if timeoutMs elapses first. Waits indefinitely when no timeout is given. |

// Raw Alipay payloads
interface PayBillSuccessData { transactionId: string; startTime?: number }
interface BongaSuccessData   { startTime: number; message: string; statusCode: string }
interface PayErrorData {
  error?: number;        // payBill/buyGoods code (201 = user cancelled)
  errorCode?: number;    // lipaNaBongaPaybill code (201 = user cancelled)
  errorMessage?: string;
  message?: string;      // library-side failure (e.g. missing paybillNo)
  startTime?: number;
}
type TillSuccessData = PayBillSuccessData; // buyGoods (Till) — same shape

// Discriminated union on `success` — narrow `result.data` via `result.success`
type PayResponse<TData = any> =
  | { success: true;  message: string; data: TData;       transactionReference: string }
  | { success: false; message: string; data: PayErrorData; transactionReference: string };

// makePayment / makeTillPayment → Promise<PayResponse<PayBillSuccessData>>
// makeBongaPayment             → Promise<PayResponse<BongaSuccessData>>

Payment Flow

PayBill and Till follow the same shape; only the mandatory identifier and the native bridge method differ.

  1. Validate the identifier (v2) — fail fast if missing/blank, before any modal (paybillNo for PayBill, tillNumber for Till).
  2. Check AlipayJSBridge — fail with "Unable to make payment." if absent.
  3. Consent dialog (step 1, user-facing) via AlipayJSBridge.call("confirm", ...). Cancel here → "Payment cancelled by user!".
  4. Pay (step 2, user-facing):
    • PayBill → AlipayJSBridge.call("payBill", ...) (businessID = paybillNo, billReference = transactionReference, currency = "KES").
    • Till → AlipayJSBridge.call("buyGoods", ...) (tillNumber, amount, currency = "KES", reason — no billReference).
    • Bonga PayBill → AlipayJSBridge.call("lipaNaBongaPaybill", ...) (same params as PayBill; response contents differ — see the Bonga note in the API reference). User authorizes → success; backs out → Alipay code 201.
  5. Normalize the result into PayResponse.
  6. hideLoading in finally.

Error Codes / Messages

| Scenario | message | How to detect | |----------|-----------|---------------| | Missing/blank paybillNo (v2) | paybillNo is required to make a payment. | message text | | Missing/blank tillNumber (v2) | tillNumber is required to make a payment. | message text | | Not inside an Alipay webview | Unable to make payment. | message text | | Cancel consent dialog (step 1) | Payment cancelled by user! | message text | | Cancel Alipay sheet (step 2, PayBill/Till) | Canceled by user | data.error === 201 | | Cancel Alipay sheet (step 2, Bonga) | Canceled by user | data.errorCode === 201 |

Configuration

  • Currency: KES (Kenyan Shillings) — fixed.
  • Paybill number: supplied by the caller (v2); legacy uses a built-in default.
  • Till number: supplied by the caller (v2 only; makeTillPayment).

Documentation

See makePayment-documentation.md for the full flow, signatures, and response examples.

Contributing

Building, project layout, the ES5-safe rule, and the release process live in CONTRIBUTING.md.

License

Proprietary to Cheche Digital Games.