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

binderpay-nodejs

v1.0.0

Published

Official BinderPay SNAP API SDK for Node.js (Virtual Account & QRIS)

Readme

binderpay-nodejs

Official BinderPay SNAP API SDK for Node.js — Virtual Account & QRIS.

Implements RSA-SHA256 (SNAP Bank Indonesia) signature automatically on every request and provides callback verification helpers.

Installation

npm install binderpay-nodejs

Requires Node.js >= 18.0.0

Configuration

import { BinderPay } from 'binderpay-nodejs';
import * as fs from 'fs';

const client = new BinderPay({
  partnerId: '170041',                    // X-PARTNER-ID
  privateKey: fs.readFileSync('private.pem', 'utf8'), // RSA private key (PEM)
  channelId: 'BCA',                       // CHANNEL-ID
  isProduction: false,                    // default sandbox; true = https://api.binderpay.id
});

| Option | Type | Default | Description | | --- | --- | --- | --- | | partnerId | string | — | Registered partner ID (required) | | privateKey | string | Buffer | — | RSA private key PEM (required) | | channelId | string | — | Bank channel code, e.g. BCA (required) | | isProduction | boolean | false | false = sandbox, true = production | | baseUrl | string | — | Override base URL |

Default base URLs: Sandbox https://api-sandbox.binderpay.id, Production https://api.binderpay.id.

Virtual Account

// Create VA (Service 27)
await client.virtualAccount.create({
  customerNo: '000003212',
  virtualAccountName: 'Chus Pandi',
  trxId: 'INV-000000023212',
  totalAmount: { value: '25000.00', currency: 'IDR' },
  virtualAccountTrxType: 'C',            // C | O | R
  expiredDate: '2023-09-05T19:30:14+07:00',
  additionalInfo: { channel: 'CIMB' },
});

// Inquiry active VA (Service 30)
await client.virtualAccount.inquiry({
  trxId: 'INV-000000023212',
  additionalInfo: { contractId: 'ci302a21c9' },
});

// VA payment status (Service 26)
await client.virtualAccount.status({
  virtualAccountNo: '2269141693898987',
  trxId: 'INV-000000023212',
  additionalInfo: { contractId: 'ci302a21c9', channel: 'BCA' },
});

// Delete VA (Service 31)
await client.virtualAccount.delete({
  trxId: 'INV-000000023212',
  virtualAccountNo: '2269141693898987',
  additionalInfo: { channel: 'BCA', contractId: 'ci302a21c9' },
});

VA types: C (one-off), O (open recurring), R (close recurring).

Channels: BRI, BNI, MANDIRI, MANDIRIPC, PERMATA, BSI, MUAMALAT, BCA, CIMB, SINARMAS, BNC, MAYBANK.

QRIS

// Generate QRIS (Service 47)
await client.qris.generate({
  partnerReferenceNo: 'INV-000000023212',
  amount: { value: '45000.00', currency: 'IDR' },
  validityPeriod: '2024-01-11T17:00:00+07:00', // required if isStatic = false
  additionalInfo: { isStatic: false },
});

// Query status (Service 51)
await client.qris.query({
  originalPartnerReferenceNo: 'INV-000000023212',
  serviceCode: '47',
  additionalInfo: { contractId: 'ci302a21c9' },
});

// Cancel (Service 77)
await client.qris.cancel({
  originalPartnerReferenceNo: 'INV-000000023212',
  reason: 'cancel order',
  additionalInfo: { contractId: 'ci302a21c9' },
});

Webhook / Callback Validation

Callbacks from BinderPay are sent with the X-TIMESTAMP, X-SIGNATURE, and X-PARTNER-ID headers. Verify the signature with the BinderPay public key (download from https://binderpay.id/docs/binderpay-public.pem, not your private key).

There are three separate path concepts:

  • Merchant callback route: your application-owned route, for example /api/binderpay/callback.
  • Signed callback path: the exact path BinderPay includes in the callback string-to-sign: /v1.0/transfer-va/payment for VA or /v1.0/qr/qr-mpm-notify for QRIS.
  • Outbound API endpoint: an SDK request path such as /v1.0/transfer-va/create-va.
import fs from 'fs';
import express from 'express';
import {
  verifyCallbackSignature,
  parseCallback,
  successResponse,
} from 'binderpay-nodejs';

const app = express();

// Load BinderPay public key (PEM format) from file or environment variable
const binderpayPublicKey = fs.readFileSync('binderpay-public.pem', 'utf8');

// Use express.raw({ type: 'application/json' }) or keep rawBody
app.post('/api/binderpay/callback', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body.toString('utf8'); // exactly as received; do not re-serialize

  const valid = verifyCallbackSignature(
    req.headers,
    rawBody,
    binderpayPublicKey,
  );
  if (!valid) {
    return res.status(401).json({ message: 'Cannot verify signature' });
  }

  const callback = parseCallback(JSON.parse(rawBody)); // auto-detects VA or QRIS
  // ... process payment idempotently ...

  // Return the matching SNAP success code for the detected callback type.
  return res.json(successResponse(callback.type));
});

parseCallback() inspects the payload and validates the required fields of the detected type — a VA callback (has trxId) or a QRIS callback (has originalReferenceNo). It throws ValidationError with the detected type in the message when a required field is missing, and rejects payloads that match neither type. Use successResponse(callback.type) to acknowledge — it returns 2002500 for va and 2005200 for qris.

For a standard VA or QRIS callback, use the same verifyCallbackSignature(...) function. It automatically checks /v1.0/transfer-va/payment and /v1.0/qr/qr-mpm-notify, and handles case-insensitive headers and array values. For non-standard integrations, use verifyCallbackSignatureForPath(...) with an explicit path. You can also use validatePublicKey(publicKey) to validate public key presence and RSA format upfront.

Important:

  • rawBody must be exactly as received by the server; do not decode and re-serialize it before verification.
  • binderpayPublicKey is strictly validated; passing an empty/missing key or invalid PEM throws a ValidationError.
  • The merchant route remains application-owned; the unified helper selects the BinderPay signed callback path automatically.
  • Replay prevention and idempotent transaction handling remain the merchant application's responsibility.
  • Return HTTP 200 with the appropriate BinderPay response code after successful processing.

Error Handling

import { BinderPayError, ValidationError, SignatureError, TransportError } from 'binderpay-nodejs';

try {
  await client.virtualAccount.create({...});
} catch (err) {
  if (err instanceof ValidationError) { /* invalid input */ }
  if (err instanceof TransportError) { /* network/connection failure */ }
  if (err instanceof BinderPayError && err.responseCode === '4002701') { /* field format */ }
}

Testing

npm test        # Jest
npm run build   # tsup (CJS + ESM + d.ts)

License

This project is licensed under the MIT License - see the LICENSE file for details.