BirrJS
Concepts

Payment Providers

Integrate payment gateways with the PaymentProvider interface.

BirrJS uses a provider interface to abstract payment gateway logic. Each provider implements the same three methods.

Provider Interface

interface PaymentProvider {
  initializeTransaction(request: TransactionRequest): Promise<TransactionResponse>;
  verifyTransaction(txRef: string): Promise<VerificationResponse>;
  handleWebhook(payload: unknown, rawBody: string | Buffer, headers: Record<string, string>): Promise<WebhookEvent>;
}

TransactionRequest → TransactionResponse

type TransactionRequest = {
  amount: number;       // Minor units (e.g. 2990 = 29.90 ETB)
  currency: string;
  email: string;
  firstName?: string;
  lastName?: string;
  phoneNumber?: string;
  txRef: string;        // Unique transaction reference
  callbackUrl: string;
  returnUrl?: string;
  metadata?: Record<string, string>;
  customization?: {
    title?: string;
    description?: string;
  };
};

type TransactionResponse = {
  success: boolean;
  checkoutUrl?: string;
  txRef?: string;
  error?: string;
};

Chapa Provider

The @birrjs/chapa package provides the Chapa integration:

import { chapa } from "@birrjs/chapa";

createBirr({
  provider: chapa({
    secretKey: process.env.CHAPA_SECRET_KEY!,
    webhookSecret: process.env.CHAPA_WEBHOOK_SECRET!,
    callbackUrl: process.env.CALLBACK_URL!,
    returnUrl: process.env.RETURN_URL!,
  }),
});

Environment variables

VariableDescription
CHAPA_SECRET_KEYAPI key from developer.chapa.co
CHAPA_WEBHOOK_SECRETWebhook signing secret

Chapa-specific notes

  • Amounts are sent as decimal strings (e.g. "29.90") — BirrJS converts from minor units automatically
  • Callback URL — Chapa sends a GET request here after payment with ?trx_ref=...&status=success. BirrJS handles this on the same /handle-webhook path used by POST webhooks. Set up the webhook URL in your Chapa dashboard to point to the same endpoint for POST events.
  • Return URL — Optional. Where Chapa redirects the user's browser after payment completes. Set this to a frontend page like /account or /plans?success=true.
  • Webhook signature uses HMAC-SHA256, verified via timing-safe comparison
  • Supported events: charge.success, charge.failed, charge.cancelled, charge.reversed, charge.refunded

Chapa gaps (future)

The following Chapa features are typed but not yet wired up:

  • customization (title, description, logo)
  • meta (hide_receipt, invoices)
  • subaccounts (split payments)

Switching Providers

Your app code never references the provider directly — all calls go through the PaymentProvider interface. Swapping from Chapa to another provider means changing one import:

// Before
import { chapa } from "@birrjs/chapa";
// After
import { arifpay } from "@birrjs/arifpay";

createBirr({
  provider: arifpay({
    secretKey: process.env.ARIFPAY_SECRET_KEY!,
    callbackUrl: "https://your-app.com/api/birrjs/handle-webhook",
  }),
  // Everything else stays the same
  database: process.env.DATABASE_URL,
  plans: [free, pro],
});

Your subscribe(), check(), report(), and webhook handler all work identically regardless of which provider is configured.

Adding a New Provider

Create a new package (e.g. @birrjs/arifpay) with:

  1. A provider factory function that returns PaymentProvider
  2. Types for provider-specific request/response shapes
  3. A Zod schema for webhook payload validation

Planned providers: ArifPay, Santim Pay and more. The provider system is designed to make adding new ones straightforward.