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

pymnt-tools

v1.1.1

Published

Payments format parsers and webhook signature verifiers — ISO 8583, EMV BER-TLV, EMVCo QR, UPI links, ISO 9564 PIN blocks and key check values, and Razorpay/Stripe/Cashfree/PayU signatures. No dependencies, runs in Node, the browser and on the edge.

Readme

pymnt-tools

Payments format parsers and webhook signature verifiers, with no dependencies.

ISO 8583, EMV BER-TLV, EMVCo/Bharat QR, UPI links, ISO 9564 PIN blocks, card number inspection, IFSC validation, and webhook signatures for Razorpay, PayU, Cashfree and Stripe.

These are the libraries behind tools.moveahead.tech, extracted rather than reimplemented — the same functions, compiled from the same source, so the package and the website cannot disagree about what a message says.

npm install pymnt-tools

Node 18+, ESM. Every module is pure and isomorphic: no node: imports, no DOM, crypto through Web Crypto. They run unchanged in Node, in a browser bundle and on edge runtimes.

Nothing here has a hard dependency. The one optional extra is qrcode, needed only if you call renderQrSvg to draw a UPI QR — parsing and building UPI links does not touch it.

Modules

Import the one you need — there is no barrel, so you pay only for what you use.

Every module has a browser front end that runs the identical function, which is usually the fastest way to check what a message contains before you write the code that reads it.

| Import | What it does | Try it first | | --- | --- | --- | | pymnt-tools/iso8583 | Parse and build ISO 8583, with the bitmap computed from the fields set | ISO 8583 parser & builder | | pymnt-tools/emv-tlv | EMV BER-TLV, including ISO 8583 field 55, with TVR/AIP/TSI bit decoding | EMV tag decoder | | pymnt-tools/emvco | EMVCo merchant QR (Bharat QR), with CRC validation | Bharat QR / EMVCo decoder | | pymnt-tools/upi | Build, parse and validate upi://pay links; render QR as SVG | UPI link & QR generator | | pymnt-tools/pin-block | ISO 9564 PIN blocks, formats 0–3, key component XOR, and DES/3DES/AES check values | PIN block toolkit | | pymnt-tools/card | Luhn, network detection from IIN ranges, PAN formatting | Card test toolkit | | pymnt-tools/webhook-signature | Verify and sign gateway webhook signatures | Webhook signature verifier | | pymnt-tools/ifsc | Validate and normalise Indian IFSC codes | IFSC lookup |

Those pages paste in nothing and send nothing — the marked tools compute in the browser — so a payload you cannot share is still safe to inspect there.

Webhook signatures

The reason this exists. Every other library covers GitHub, Slack and Shopify; this one covers the Indian gateways, and tells you which string was signed.

import { providersById } from "pymnt-tools/webhook-signature";

const result = await providersById.razorpay.verify({
  payload: rawRequestBody,          // the exact bytes, never JSON.parse'd
  secret: process.env.RAZORPAY_WEBHOOK_SECRET,
  signature: request.headers["x-razorpay-signature"],
});

if (result.status !== "valid") {
  // steps[] shows what was signed and what was compared, which is
  // almost always where the answer is.
  console.log(result.summary, result.steps);
}

Providers: razorpay, razorpay-payment (checkout/order/subscription/link), stripe, cashfree, payu. Comparisons use a constant-time equality, and PayU's hash string masks the salt before it appears in any output.

The raw body matters. A signature is computed over exact bytes. If your framework has already parsed and re-serialised the body, the signature cannot verify and the mismatch is not your secret's fault. In Express use express.raw(); in Next.js read await request.arrayBuffer().

ISO 8583, and field 55

import { parseIso8583 } from "pymnt-tools/iso8583";
import { parseIso8583Field55 } from "pymnt-tools/emv-tlv";

const message = parseIso8583(hexDump, { layout: "packed" });
const de55 = message.fields.find((field) => field.id === 55);

const emv = parseIso8583Field55(de55.value);
for (const node of emv.nodes) {
  console.log(node.tag, node.name, node.description ?? node.hex);
}

The parser reports where alignment broke rather than returning garbage — the tag or field it was reading and the byte offset — because on a malformed message that is the only useful output. Packed BCD and EBCDIC input are both supported.

The builder computes the bitmap from the fields you set. Length problems are errors and character-set problems are warnings, deliberately: a wrong-length value shifts every field after it, while a value of the right length in the wrong character set still assembles and is the switch's business to reject.

PIN blocks

import { buildPinBlock, decodePinBlock } from "pymnt-tools/pin-block";

const { block, steps } = buildPinBlock({
  format: "0",
  pin: "1234",
  pan: "4111111111111111",
});
// block === "041225EEEEEEEEEE"
// steps shows the PIN field and the PAN field separately, then the XOR

This is the clear block — the formatting layer either side of encryption. A live PIN block is encrypted from the pad to the HSM, and nothing here decrypts one.

Key check values come with it, and report every reading the length admits:

import { keyCheckValues } from "pymnt-tools/pin-block";

// key = 000102030405060708090A0B0C0D0E0F
await keyCheckValues(key);
// 16 bytes is double-length 3DES *or* AES-128, and nothing in the bytes says
// which, so both are returned rather than one being guessed at:
// [ { algorithm: "3DES", kcv: "DDADA1", reading: "double-length 3DES" },
//   { algorithm: "AES",  kcv: "C6A13B", reading: "AES-128" } ]

DES is implemented here because Web Crypto does not offer it and payment key management still runs on 3DES. It is a single-block cipher for computing check values — no mode, no IV, no padding — and it is pinned against the published FIPS vectors. Do not use it to encrypt anything.

Note that decoding a format 0 block with the wrong PAN does not fail: the XOR has no integrity check, so you get a plausible wrong PIN. Check issues — a padding warning is the only signal.

Ambiguity is reported, not hidden

IIN ranges genuinely overlap between networks, so inspectCard returns every match rather than picking one:

import { inspectCard } from "pymnt-tools/card";

const card = inspectCard("4111 1111 1111 1111");
card.luhn.valid;                                  // true
card.networks.map((match) => match.network.name); // every network whose ranges cover it

Presenting one confident answer where the data does not support one is the failure mode that costs the most credibility, so none of these modules do it.

Test data only

The card and PIN block modules are built for test data. Use test PANs and test PINs; nothing here should meet a real card number or a real PIN.

Changes

1.1.1 — Stripe verification no longer reports "the timestamp is inside the replay window" when t= is not a Unix timestamp. The HMAC covers t as a string, so a hand-assembled header can carry a matching digest with an unparseable timestamp; the age comparison was then skipped and the success path claimed a window that had never been checked. It now returns invalid with reason: "replay_window" and says the signature itself is correct. Stripe does not send such a header — this only affects hand-built ones.

1.1.0 — DES, 3DES and AES key check values (keyCheckValues).

Related

Licence

MIT