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

zerofee

v3.0.2

Published

Official Node.js SDK for ZeroFee - Unified Payment Orchestration for Africa

Readme

zerofee

Official Node.js / TypeScript SDK for ZeroFee — unified payment orchestration for Africa and global payments.

npm version license types

One API for cards (Stripe, PayPal), mobile money (M-Pesa, MTN, Orange, Wave, Airtel via PawaPay), and West-African rails (Hub2, PaiementPro). Create a hosted checkout with three fields, or drive a direct mobile-money charge — the SDK routes to the best available provider for the customer.

Installation

npm install zerofee
# or
pnpm add zerofee
# or
yarn add zerofee

Requires Node.js >= 18 (uses the built-in global fetch and crypto). Ships CommonJS, ESM, and TypeScript declarations.

Quick start

import { ZeroFee } from 'zerofee'

const zf = new ZeroFee('sk_live_xxx') // sk_sand_xxx for sandbox

// Create a hosted checkout session — only 3 fields required
const payment = await zf.payments.create({
  amount: 10000,
  sourceCurrency: 'USD',
  paymentReference: 'ORDER-123',
  successUrl: 'https://yoursite.com/success',
  cancelUrl: 'https://yoursite.com/cancel',
})

// Redirect the customer to complete payment
console.log(payment.checkoutUrl)

Amounts are in major currency units (e.g. 10000 = 10,000 XOF, 85.43 = $85.43) — never cents.

Direct mobile-money charge

Pass a paymentMethod to charge directly instead of creating a hosted session. Some methods (e.g. Orange Money CI) require an OTP step:

const payment = await zf.payments.create({
  amount: 5000,
  sourceCurrency: 'XOF',
  paymentReference: 'Premium plan',
  paymentMethod: 'PAYIN_ORANGE_CI',
  customer: { phone: '+2250700000000' },
})

if (payment.paymentFlow?.type === 'otp') {
  // Customer enters the OTP they received by SMS
  const confirmed = await zf.payments.authenticate(payment.id, { otp: '123456' })
  console.log(confirmed.status) // 'completed' | 'failed'
}

Checking status

// Authenticated retrieve
const payment = await zf.payments.retrieve('txn_abc123')

// Public status endpoint (safe to poll from a client)
const status = await zf.payments.getStatus('txn_abc123')
console.log(status.status) // 'pending' | 'processing' | 'completed' | 'failed' | ...

// Checkout session status (public)
const session = await zf.checkout.getSessionStatus('cs_abc123')

Verifying webhooks

ZeroFee signs every webhook with HMAC-SHA256 in the X-ZeroFee-Signature header (t={timestamp},v1={signature}, signed over `${timestamp}.${rawBody}`). Always verify against the raw request body — not a re-serialized object.

import express from 'express'
import { ZeroFee, WebhookSignatureVerificationError } from 'zerofee'

const zf = new ZeroFee('sk_live_xxx')
const app = express()

app.post(
  '/webhooks/zerofee',
  express.raw({ type: 'application/json' }), // raw body, not express.json()
  (req, res) => {
    try {
      const event = zf.webhooks.constructEvent(
        req.body.toString('utf8'),
        req.headers['x-zerofee-signature'] as string,
        process.env.ZEROFEE_WEBHOOK_SECRET!, // whsec_xxx
      )

      switch (event.type) {
        case 'payment.completed':
          // event.data is the Payment
          console.log('Paid:', event.data.id)
          break
        case 'payment.failed':
          break
      }

      res.json({ received: true })
    } catch (err) {
      if (err instanceof WebhookSignatureVerificationError) {
        return res.status(400).send('Invalid signature')
      }
      throw err
    }
  },
)

The signed timestamp is checked against a 5-minute tolerance by default (replay protection). Override it with constructEvent(body, sig, secret, { toleranceSeconds }).

Error handling

Every failure throws a typed subclass of ZeroFeeError:

import {
  ZeroFee,
  AuthenticationError,
  ValidationError,
  NotFoundError,
  RateLimitError,
  APIError,
} from 'zerofee'

try {
  await zf.payments.create({ amount: 5000, sourceCurrency: 'USD', paymentReference: 'x' })
} catch (err) {
  if (err instanceof ValidationError) {
    console.error('Bad request:', err.message)
  } else if (err instanceof AuthenticationError) {
    console.error('Check your API key')
  } else if (err instanceof RateLimitError) {
    console.error('Slow down')
  } else if (err instanceof APIError) {
    console.error(err.statusCode, err.code, err.requestId)
  }
}

Idempotency

Pass an idempotency key as the second argument to payments.create to safely retry without creating duplicate charges:

await zf.payments.create(params, 'order-123-attempt-1')

API surface

| Resource | Methods | |----------|---------| | zf.payments | create, retrieve, getStatus, list, authenticate, cancel | | zf.checkout | getPaymentMethods, retrieveSession, getSessionStatus | | zf.webhooks | constructEvent, verifySignature | | zf.customers | list, retrieve | | zf.invoices | list, retrieve, getByTransaction | | zf.countries | list, retrieve | | zf.currencies | list, convert, getRates | | zf.discovery | getPayinMethod, listFlowTypes, listWebhookEvents |

Configuration

const zf = new ZeroFee('sk_live_xxx', {
  baseUrl: 'https://api.0fee.dev/v1', // default
  timeout: 30000,                     // ms, default 30s
})

Links

  • Documentation: https://0fee.dev/docs
  • API reference: https://0fee.dev/docs
  • Issues: https://github.com/skiil-ai/0fee.dev/issues

License

MIT © ZeroFee