Bora PesaBora Pesa

Getting Started

Install Bora Pesa and process your first payment in under 5 minutes.

Added in v0.1.0

Installation

pnpm add @borapesa/pesa

The core package includes a BogusProvider for local development — no credentials, no network, no sandbox registration. When you're ready for production, install a provider adapter:

pnpm add @borapesa/clickpesa

Your first payment

import { createPesa } from '@borapesa/pesa';
import { BogusPaymentProvider } from '@borapesa/pesa/testing';

// 1. Create an instance — that's it, no config needed for dev
const pesa = createPesa({
  provider: new BogusPaymentProvider({ defaultBehavior: 'success' }),
});

// 2. Initiate a payment
const order = await pesa.createOrder({
  amount:    15000,             // TZS 15,000 — whole integers only
  currency:  'TZS',
  reference: 'order_abc123',   // Your internal order ID — must be unique
  customer:  {
    name:  'Juma Ali',
    phone: '255712345678',     // MSISDN format: 255XXXXXXXXX
  },
});

console.log(order.status);       // 'SUCCESS'
console.log(order.orderId);      // Provider-assigned transaction ID

React to events

Bora Pesa emits typed events after every verified webhook:

pesa.on('PAYMENT_SUCCESS', async (event) => {
  // event.reference — your order ID
  // event.amount    — TZS amount
  // event.provider  — which provider processed it
  await db.orders.update({
    where: { id: event.reference },
    data:  { status: 'paid' },
  });
});

Going to production

Swap the BogusProvider for a real provider and add a persistent event store:

pnpm add @borapesa/clickpesa @borapesa/sqlite
import { retryPlugin, idempotencyPlugin, loggingPlugin } from '@borapesa/pesa/plugins';
import { ClickPesaProvider } from '@borapesa/clickpesa';
import { SQLiteAdapter } from '@borapesa/sqlite';

const pesa = createPesa({
  provider: new ClickPesaProvider({
    clientId:    process.env.CLICKPESA_CLIENT_ID!,
    apiKey:      process.env.CLICKPESA_API_KEY!,
    checksumKey: process.env.CLICKPESA_CHECKSUM_KEY, // enables webhook verification
  }),
  db: new SQLiteAdapter('./pesa.db'),
  plugins: [
    idempotencyPlugin(),
    retryPlugin({ maxAttempts: 3 }),
    loggingPlugin({ level: 'info' }),
  ],
});

Any class implementing PesaDatabaseAdapter works as an event store — use @borapesa/sqlite, or implement the four-method interface for your own database. See Adapters for details.

Mount on your server

Every pesa instance exposes a webhook handler. Mount it publicly — providers POST callbacks here:

Bun.serve({ fetch: pesa.mountWebhook });

The handler serves one endpoint (customizable via basePath in config):

  • POST {basePath}/webhook — receive provider callbacks

For order creation and status queries, use pesa.createOrder() and pesa.getPaymentStatus() in your own routes behind your own auth middleware. basePath defaults to /pesa.

Next steps

On this page