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

paysawa

v0.1.0

Published

Node.js SDK for Paysawa mobile money payments: collections, withdrawals, payment links and verified webhooks.

Readme

paysawa

Node.js SDK for Paysawa's apps API (MTN Mobile Money and Orange Money, XAF). Collect payments, withdraw funds, create hosted payment links, poll transactions and verify app webhooks. Requires Node.js 22 or newer. Supports CommonJS, ES modules and TypeScript with no runtime dependencies or build step. Use this SDK in your server application; keep app passwords out of browser code and source control.

Installation

Once this package is published to npm:

npm install paysawa

Before publishing, install directly from this directory into your application:

npm install "C:\Users\GLC\Desktop\PAYSAWA\SDK\paysawa-node"

You can also run npm pack here, then install the resulting paysawa-0.1.0.tgz in another project using npm install /path/to/paysawa-0.1.0.tgz.

Credentials and API URL

Create a test or live app in the Paysawa dashboard. Use its app username, app password and webhook key. Test apps use sandbox funds; live apps move real money.

Set these environment variables (see .env.example):

PAYSAWA_APP_USERNAME=pa_your_app_username
PAYSAWA_APP_PASSWORD=your_app_password
PAYSAWA_WEBHOOK_KEY=whk_your_webhook_key
PAYSAWA_BASE_URL=https://your-paysawa-host.example/api/

Use the dashboard's api_base_url, including /api/. The SDK adds endpoint paths to it. The development default is http://localhost:8000/api/. Environment files are not loaded automatically; configure your deployment environment or use Node's --env-file=.env option.

Quick start

import { Client, PaysawaError, WaitTimeoutError } from 'paysawa';

const client = Client.fromEnv();
// Or: new Client({ appUsername: 'pa_...', appPassword: '...',
//   webhookKey: 'whk_...', baseUrl: 'https://your-host.example/api/' });

try {
  const tx = await client.collect({
    amount: 5000,
    phone: '670000001',
    description: 'Order 1001',
    externalReference: 'order-1001',
    wait: true,
    timeout: 120000,
    interval: 3000,
  });
  console.log(tx.reference, tx.status, tx.isSuccessful);
} catch (error) {
  if (error instanceof WaitTimeoutError) console.log('Still pending:', error.transaction?.reference);
  else if (error instanceof PaysawaError) console.error(error.code, error.message);
  else throw error;
}

console.log((await client.getBalance()).total_balance);
const payout = await client.withdraw({ amount: 1000, phone: '690000001', description: 'Refund' });
const link = await client.getPaymentLink({
  amount: '2000', description: 'Order 1002', externalReference: 'order-1002',
  redirectUrl: 'https://shop.example.com/thanks',
});
console.log(payout.reference, link.link, link.reference);

CommonJS:

const { Client } = require('paysawa');
const client = Client.fromEnv();
// Call async methods from an async function.

Inputs use JavaScript camelCase. Response fields retain the backend's names (external_reference, ussd_code, total_balance, etc.). Amounts accept positive whole XAF as a safe integer, decimal integer string (also "5000.00"), or bigint. Use strings or bigint for amounts beyond JavaScript's safe integer range. HTTP JSON responses retain native JSON number behavior. Only XAF is supported by the API.

Methods and options

| Method | Result | | --- | --- | | collect({ amount, phone, ... }) | Promise<Transaction>; asks the customer to approve a payment | | withdraw({ amount, phone, ... }) | Promise<Transaction>; sends funds from the merchant balance | | getTransaction(reference) | Latest collection, payout or payment-link state | | waitForTransaction(reference, options) | Polls until successful or failed | | getBalance() | Balance with total_balance, held_balance, currency | | getPaymentLink({ amount, ... }) | { link, reference } | | verifyWebhook(params) | Verified WebhookEvent; synchronous |

Payment options include description, externalReference, currency (default XAF) and wait (default false). With wait: true, timeout, interval and onUpdate are passed to polling. Link options also include redirectUrl.

Transactions have status (PENDING, SUCCESSFUL, FAILED) and convenience getters isPending, isSuccessful, isFailed, isFinal. Status constants and the Transaction class are exported.

const latest = await client.getTransaction('col_your_reference');
const final = await client.waitForTransaction(latest.reference, {
  timeout: 120000,
  interval: 3000,
  onUpdate: tx => console.log(tx.status), // can also be async
});

All durations are milliseconds. Client timeout defaults to 30000 per HTTP request. Polling defaults to a 120000 budget and 3000 interval; it includes HTTP calls and retry delays. An async onUpdate callback is awaited; callback execution is controlled by your application and cannot be interrupted by the SDK. WaitTimeoutError.transaction contains the last observed transaction, or null if none was received. The polling budget starts after the initial collect/withdraw request completes. Client maxRetries defaults to 2 and applies to GET network failures and HTTP 5xx, with 500ms/1000ms backoff. You can inject a Fetch-compatible implementation with the client fetch option.

Tokens are cached and concurrent requests share token acquisition. A rejected token is refreshed once after HTTP 401. POST requests are never retried for network failures or server errors; they can be resubmitted once after an explicit HTTP 401. The apps API does not expose an idempotency key: externalReference is a correlation value, not a duplicate-payment guarantee. If a payment request has an uncertain outcome, reconcile it in the dashboard before submitting another payment.

App webhooks

Set the app's webhook URL in the dashboard. App callbacks are GET requests with query parameters and an HS256 JWT signature. This verifier targets app callbacks, not the separate /v1 JSON POST webhook format. Pass a URLSearchParams or a plain object containing scalar query values. Duplicate query parameters are rejected.

Example with Node's HTTP server (no framework dependency):

import { createServer } from 'node:http';
import { Client, WebhookVerificationError } from 'paysawa';

const client = Client.fromEnv();
createServer(async (req, res) => {
  const url = new URL(req.url, 'http://localhost');
  if (req.method !== 'GET' || url.pathname !== '/webhooks/paysawa') {
    res.writeHead(404).end();
    return;
  }
  try {
    const event = client.verifyWebhook(url.searchParams);
    if (event.isSuccessful) {
      // Load event.external_reference from your database; compare expected amount
      // and currency, and persist payment once using event.reference as a unique key.
      // await markOrderPaidOnce(event);
    }
    res.writeHead(200).end('ok');
  } catch (error) {
    res.writeHead(error instanceof WebhookVerificationError ? 401 : 500).end('error');
  }
}).listen(3000);

Express: pass req.query to client.verifyWebhook; nested or array values in signed fields are rejected. Alternatively use the standalone verifyWebhook(params, webhookKey) export. Verification checks HS256, constant-time signature comparison, timestamps with 10s clock tolerance, and that signed fields match query values. Only authenticated fields are returned; unsigned extras are discarded. The required signed fields are reference, status, amount, currency. Numeric string amounts become numbers when safe, otherwise remain strings. Webhooks can be delivered more than once: persist and deduplicate transactions before fulfillment. Return 2xx after processing succeeds; Paysawa retries unsuccessful deliveries.

Errors

Every SDK error extends PaysawaError and exposes message, status (HTTP status or null), and code (or null). Invalid local arguments throw TypeError before requests are sent.

| Error | Meaning | | --- | --- | | AuthenticationError | Invalid credentials or rejected token (401) | | PermissionDeniedError | Account/app not permitted (403) | | InvalidRequestError | Rejected request, invalid parameters or missing transaction | | InsufficientFundsError | Balance cannot cover payout plus fee | | RateLimitError | Too many requests (429) | | ServerError | HTTP 5xx | | NetworkError | Request timed out (timeout) or unreachable (unreachable) | | WaitTimeoutError | Polling budget exceeded (wait_timeout) | | WebhookVerificationError | Callback verification failed |

Development and release

No compilation or dependency installation is required to run the SDK or tests:

npm test
npm run test:coverage
npm pack --dry-run
npm pack

Tests cover API payloads, tokens, concurrency, retries, errors, polling deadlines, real local HTTP transport, webhook signatures/tampering, and CommonJS/ES module imports. They do not use live credentials or move funds. See PUBLISHING.md for independent repository setup, package verification and npm publishing.