Bora PesaBora Pesa

Providers

Available payment providers — Selcom, ClickPesa, AzamPay, Snippe — and their capabilities.

Added in v0.1.0

Providers are the bridge between the SDK and payment gateways. Pick one that matches your business needs. Swap providers with a one-line config change — no application code changes needed.

Comparison

A ✅ means the feature is implemented in our provider adapter. A does not guarantee the upstream provider API lacks the feature — it may simply not be wired up yet. Open an issue if you need it.

SelcomClickPesaAzamPaySnippe
MNO USSD push
MNO redirect checkout
Card payment
Bank checkout (OTP)
Hosted payment page
Disburse (wallet)
Disburse (bank)
Webhook signatureHMAC-SHA256HMAC-SHA256RSA-PKCS#1HMAC-SHA256
Order cancellation
Order listing
Balance query
Name lookup
Credential validation
Auth methodHMAC per-requestBearer tokenBearer tokenBearer token
Payout fee preview

Configuration

Each provider has a different config surface — fields match what each provider's dashboard gives you. Every config accepts an optional baseUrl override (or equivalent) for sandbox/proxy use.

ClickPesa

import { ClickPesaProvider } from '@borapesa/clickpesa'
FieldRequiredDescription
clientIdClient ID from ClickPesa dashboard
apiKeyAPI key from ClickPesa dashboard
checksumKeyHMAC-SHA256 signing key for webhook verification. Generate in the ClickPesa dashboard. When set, all POST/PUT/PATCH bodies are signed and incoming webhooks are verified. Without it, webhooks are accepted unverified.
sandboxtruehttps://api-sandbox.clickpesa.com. false (default) → https://api.clickpesa.com.
baseUrlExplicit base URL override — takes precedence over sandbox.
redirectUrlDefault redirect URL for hosted checkout. Overridable per-payment via payload.redirectUrl.
const pesa = createPesa({
  provider: new ClickPesaProvider({
    clientId:    process.env.CLICKPESA_CLIENT_ID!,
    apiKey:      process.env.CLICKPESA_API_KEY!,
    sandbox:     false,
    redirectUrl: 'https://mysite.com/success',
  }),
})

Selcom

import { SelcomPaymentProvider } from '@borapesa/selcom'
FieldRequiredDescription
apiKeyAPI key from Selcom
apiSecretAPI secret for HMAC-SHA256 signing
vendorFloat account / vendor identifier
pinFloat account PIN — required for disbursement and balance queries
baseUrlDefaults to https://apigw.selcommobile.com. Set to the sandbox URL for testing.
senderAccountSource account number for Qwiksend bank transfers. Defaults to vendor.
senderNameAccount holder display name for bank transfers. Defaults to vendor.
senderPhoneSender mobile number for bank transfers.
redirectUrlDefault redirect URL for checkout orders. Overridable per-payment via payload.redirectUrl.
cancelUrlCancel URL for checkout orders. Customer is sent here if they abandon payment.
webhookUrlWebhook callback URL for payment status. Typically pesa.mountWebhook endpoint.
const pesa = createPesa({
  provider: new SelcomPaymentProvider({
    apiKey:      process.env.SELCOM_API_KEY!,
    apiSecret:   process.env.SELCOM_API_SECRET!,
    vendor:      process.env.SELCOM_VENDOR!,
    pin:         process.env.SELCOM_PIN!,
    redirectUrl: 'https://mysite.com/return',
  }),
})

AzamPay

import { AzamPayPaymentProvider } from '@borapesa/azampay'
FieldRequiredDescription
appNameApp name from AzamPay dashboard
clientIdClient ID from AzamPay dashboard
clientSecretClient secret from AzamPay dashboard
apiKeyAPI key from AzamPay dashboard
senderNameMerchant display name for disbursement transfers
senderBankMerchant bank name for disbursement. Defaults to "AzamPay".
sandboxTarget sandbox (default: true).
authBaseUrlOverride auth base URL.
checkoutBaseUrlOverride checkout base URL.
disbursementBaseUrlOverride disbursement base URL. AzamPay uses separate hosts for checkout and disbursement — this defaults automatically per sandbox.
const pesa = createPesa({
  provider: new AzamPayPaymentProvider({
    appName: process.env.AZAMPAY_APP_NAME!,
    clientId: process.env.AZAMPAY_CLIENT_ID!,
    clientSecret: process.env.AZAMPAY_CLIENT_SECRET!,
    apiKey: process.env.AZAMPAY_API_KEY!,
    senderName: process.env.AZAMPAY_SENDER_NAME!,
    sandbox: false,
  }),
})

Snippe

import { SnippePaymentProvider } from '@borapesa/snippe'
FieldRequiredDescription
apiKeySnippe API key (snp_...) from the Snippe dashboard
webhookSecretHMAC-SHA256 signing key for webhook verification. Found in Snippe dashboard → Settings → Webhook Secret.
baseUrlDefaults to https://api.snippe.sh. Override for proxies or staging.
webhookUrlDefault webhook URL set on create/disburse calls. Provider callbacks POST here.
redirectUrlDefault redirect URL for card payments. Overridable per-payment via payload.redirectUrl.
cancelUrlCancel URL for card payments. Falls back to redirectUrl if not set.
timeoutMsRequest timeout in milliseconds (default: 30_000).
const pesa = createPesa({
  provider: new SnippePaymentProvider({
    apiKey:        process.env.SNIPPE_API_KEY!,
    webhookSecret: process.env.SNIPPE_WEBHOOK_SECRET!,
    webhookUrl:    'https://mysite.com/pesa/webhook',
    redirectUrl:   'https://mysite.com/success',
    cancelUrl:     'https://mysite.com/cancelled',
  }),
})

Provider-specific features

Some providers expose methods beyond the standard BasePaymentProvider interface. These are available on the provider instance directly (pesa.provider) but are not part of the unified PesaInstance API.

Selcom

  • Bank disbursement (Qwiksend) — send to bank accounts via senderAccount, senderName, senderPhone config.
  • Order managementcancelOrder(), listOrders() are first-class.
  • Wallet pull paymentcheckoutWalletPayment() triggers USSD from an existing checkout order for in-app payments.
  • Utility paymentspayUtility(), lookupUtility(), queryUtilityStatus() for LUKU, DSTV, GEPG, airtime, and more.
  • Selcom PesaselcomPesaCashin(), selcomPesaNameLookup() for Selcom's mobile wallet.
  • Agent cashoutagentCashout() sends funds for cash pickup at Huduma agents via *150*50#.
  • Stored cardsfetchStoredCards(), deleteStoredCard() for tokenized card management.

ClickPesa

  • BillPay — 7 methods for control number generation, customer management, and payment status.
  • Payout linksgeneratePayoutLink() for shareable disbursement URLs.
  • Exchange ratesgetExchangeRates() for currency conversion.
  • Bank listinggetBanks() for supported bank codes.
  • Account statementsgetAccountStatement() for reconciliation.

AzamPay

  • Bank checkout — OTP-based bank payment flow (CRDB, NMB).
  • Payment partnersgetPaymentPartners() to list available MNO partners.
  • RSA callback verification — callbacks are signed with RSA-SHA256; the SDK verifies them automatically.
  • Disbursement routing — separate base URL for disbursement endpoints (disbursementBaseUrl config).
  • Provider trackingraw._providerName and raw._checkoutProvider on order results for status queries.

Snippe

  • Card paymentscreateOrder with redirectUrl (config or payload) routes to Snippe's card endpoint (Visa/Mastercard). Returns checkoutUrl for the payment page.
  • Hosted checkout sessionscreateCheckoutSession() / getCheckoutSession() / listCheckoutSessions() / cancelCheckoutSession() provider-specific methods for Snippe's hosted payment page. Returns checkoutUrl + paymentLinkUrl for sharing via SMS/WhatsApp.
  • USSD push retriggerretriggerPush(reference) re-sends the PIN prompt for a pending mobile payment.
  • Payout fee previewpreviewDisburse() returns the transaction fee before committing a payout.
  • SearchlistOrders() accepts optional Snippe-specific filters: status, paymentType, and free-text q.
  • Payout failure reasonraw.failure_reason on failed disbursements for debugging.
  • HMAC webhook verification — signature verification with replay protection (5-minute window).

Writing a provider

Every provider extends BasePaymentProvider. Four methods are required, eight are optional.

import { BasePaymentProvider } from '@borapesa/pesa'

class MyProvider extends BasePaymentProvider {
  readonly name = 'my-provider'

  // Required
  async createOrder(payload) {
    /* ... */
  }
  async getPaymentStatus(orderId) {
    /* ... */
  }
  async handleWebhook(body, headers) {
    /* ... */
  }
  async disburse(payload) {
    /* ... */
  }

  // Optional — override what you support
  async getNameLookup(phoneOrAccount) {
    /* ... */
  }
}

See the Architecture page for the full provider lifecycle.

On this page