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

popfab

v0.1.2

Published

Official Node.js SDK for the Popfab API.

Readme

popfab

The official Node.js and TypeScript SDK for the Popfab API.

Use popfab to accept payments, verify bank accounts, and initiate or track transfers without manually constructing HTTP requests or handling Popfab API authentication.

The SDK is server-side only. Do not expose a Popfab secret key in a browser, mobile app, or other untrusted client.

Requirements

  • Node.js 20 or later
  • A Popfab API key

Install

npm install popfab

Configure

import Popfab from 'popfab';

const popfab = new Popfab({
  apiKey: process.env.POPFAB_SECRET_KEY!,
  timeout: 15_000,      // optional; default is 15 seconds
  maxReadRetries: 2,    // optional; applies to GET requests only
});

The key determines the environment:

| Key prefix | Environment | | --- | --- | | sk_test_ | Sandbox | | sk_live_ | Live |

The SDK rejects keys with an invalid prefix. It does not allow an application to independently override the environment selected by its key.

Payments

Initiate a payment

Payment initiation requires an idempotency key. Use a stable business value—usually your order ID—and save it before the first request.

const payment = await popfab.payments.initiate({
  amount: 25_000, // integer minor unit; ₦250.00 for NGN
  currency: 'NGN',
  reference: 'order_394',
  paymentMethod: 'card',
  customer: {
    email: '[email protected]',
    name: 'Ada Okafor',
  },
  callbackUrl: 'https://merchant.example/payments/callback',
}, {
  idempotencyKey: 'order_394',
});

console.log(payment.id, payment.status, payment.checkoutUrl);

Supported payment methods are card, bank_transfer, ussd, mobile_money, and qr.

Retrieve, list, and sync payments

const payment = await popfab.payments.get('ppfb_pay_...');

const page = await popfab.payments.list({
  status: 'pending',
  limit: 25,
  cursor: 'optional_cursor',
});

for (const item of page.data) {
  console.log(item.reference, item.status);
}

// Ask Popfab to synchronise a payment with its provider.
const updated = await popfab.payments.sync('ppfb_pay_...', { force: true });

Refund a payment

const refund = await popfab.payments.refund('ppfb_pay_...', {
  amount: 25_000, // omit for a provider-supported full refund
  reason: 'Customer request',
});

Refunds are never retried automatically. Keep your own durable business record before retrying an uncertain refund request.

Transfers

List banks and verify a recipient

Always verify a recipient before creating a transfer.

const { banks } = await popfab.transfers.listBanks('NGN');

const recipient = await popfab.transfers.verifyAccount({
  accountNumber: '0123456789',
  bankCode: '058',
});

console.log(recipient.accountName);

Initiate a transfer

Transfers require an idempotency key. Use the same key if your application must retry after a timeout or network failure.

const transfer = await popfab.transfers.initiate({
  amount: 5_000,
  currency: 'NGN',
  reference: 'vendor_payout_394',
  recipient: {
    accountNumber: '0123456789',
    bankCode: '058',
    name: 'Ada Okafor', // use the verified account name
  },
  reason: 'Vendor payout',
}, {
  idempotencyKey: 'vendor_payout_394',
});

if (transfer.pendingConfirmation) {
  // Popfab is reconciling an uncertain provider outcome.
  // Do not submit a second transfer with a new reference.
}

Bulk transfers

const batch = await popfab.transfers.initiateBulk({
  currency: 'NGN',
  transfers: [
    {
      amount: 5_000,
      reference: 'payout_001',
      recipient: { accountNumber: '0123456789', bankCode: '058', name: 'Ada Okafor' },
    },
    {
      amount: 7_500,
      reference: 'payout_002',
      recipient: { accountNumber: '0123456788', bankCode: '058', name: 'Tolu Ade' },
    },
  ],
}, { idempotencyKey: 'payroll_january_2026' });

Retrieve and list transfers

const transfer = await popfab.transfers.get('ppfb_txfr_...');

const page = await popfab.transfers.list({
  status: 'pending',
  limit: 25,
});

Transfer statuses are pending, queued, processing, success, failed, and reversed.

Idempotency and retries

payments.initiate, transfers.initiate, and transfers.initiateBulk require a caller-supplied idempotencyKey.

  • Persist the key before the first request.
  • Reuse the original key for a retry of the same business operation.
  • Never reuse a key with different amount, recipient, or request details.
  • The SDK retries safe GET requests only. It never automatically retries a payment, transfer, bulk transfer, or refund.

For an uncertain money-moving request, query the transaction/transfer by its Popfab ID or wait for a Popfab webhook. Do not create a new request simply because the network response was lost.

Pagination

List methods return a page with data, hasMore, nextCursor, and sometimes total.

let cursor: string | undefined;
do {
  const page = await popfab.payments.list({ limit: 100, cursor });
  // Process page.data.
  cursor = page.nextCursor ?? undefined;
} while (cursor);

Errors

All errors extend PopfabError and include a safe message, type, API code, statusCode, and Popfab requestId where available.

import { RateLimitError, ValidationError } from 'popfab';

try {
  await popfab.payments.initiate(paymentInput, { idempotencyKey: orderId });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof ValidationError) {
    console.log(error.details);
  }
  throw error;
}

Available error classes include AuthenticationError, ValidationError, IdempotencyError, RateLimitError, ApiError, NetworkError, and ConfigurationError.

Webhooks

Popfab signs each delivery with HMAC-SHA256 over the exact UTF-8 value:

<X-POPFAB-Timestamp>.<raw request body>

The SDK verifies X-POPFAB-Signature, validates X-POPFAB-Timestamp against a five-minute tolerance by default, and returns a typed event. Always provide the unmodified raw body—parsing and serializing JSON before verification changes the signed payload.

import express from 'express';

app.post('/webhooks/popfab', express.raw({ type: 'application/json' }), (req, res) => {
  const event = popfab.webhooks.constructEvent({
    payload: req.body,
    signature: req.header('X-POPFAB-Signature') ?? undefined,
    timestamp: req.header('X-POPFAB-Timestamp') ?? undefined,
    webhookSecret: process.env.POPFAB_WEBHOOK_SECRET!,
  });

  // Store/queue the event and make this operation idempotent by event.id.
  console.log(event.id, event.type);
  res.sendStatus(200);
});

The dispatcher also sends X-POPFAB-Event, X-POPFAB-Delivery, and X-POPFAB-Environment headers. Do not treat a webhook as a replacement for idempotency: webhook handlers must remain safe to run more than once.

Virtual accounts

const account = await popfab.virtualAccounts.create({
  customerEmail: '[email protected]',
  customerName: 'Ada Okafor',
  customerPhone: '+2348000000000',
  preferredBank: 'wema', // optional
});

const stored = await popfab.virtualAccounts.get(account.id);
const accounts = await popfab.virtualAccounts.list({ limit: 25 });
await popfab.virtualAccounts.deactivate(account.id);

Customers

Customers are created or updated by successful payment activity. The SDK currently supports retrieving them:

const customer = await popfab.customers.get('ppfb_cus_...');
const customers = await popfab.customers.list({ limit: 25 });

API surface

| Resource | Methods | | --- | --- | | popfab.payments | initiate, get, list, sync, refund | | popfab.transfers | listBanks, verifyAccount, initiate, initiateBulk, get, list | | popfab.virtualAccounts | create, get, list, deactivate | | popfab.customers | get, list | | popfab.webhooks | constructEvent |

Development

npm install
npm run check
npm test
npm run build

The package is tested with Node.js built-in test runner and TypeScript. A dry run before publishing is recommended:

npm pack --dry-run

Security

  • Store POPFAB_SECRET_KEY in a secret manager or server-side environment variable.
  • Never commit keys or expose them in browser/mobile code.
  • Use test keys in sandbox and live keys only in production.
  • Verify recipient accounts before payouts.
  • Log Popfab requestId values for support and reconciliation.

Status

This is an active pre-release SDK. The public API will follow semantic versioning once the first stable version is released.

Additional resources

License

This project is licensed under the MIT License.