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

@gpmpay/sdk

v0.4.0

Published

Official Node.js SDK for GPM Pay — VietQR codes, transaction webhooks, and payment reconciliation.

Readme

@gpmpay/sdk

SDK Node.js chính thức cho GPM Pay — dựng QR VietQR, nhận webhook giao dịch, tự đối soát thanh toán.

Zero dependency · Node >= 18.17 · TypeScript sẵn có · ESM + CommonJS · MIT

🇬🇧 English · 📚 Tài liệu trực tuyến

Tài liệu chuyên sâu nằm ngay trong package. Đã cài rồi thì mở node_modules/@gpmpay/sdk/docs/vi/ (5 guide) · AGENTS.md (chỉ dẫn cho AI agent) · examples/ (ví dụ chạy được). Chưa cài thì đọc thẳng trên web: duyệt toàn bộ file của package — hoặc xem bảng "Tài liệu" ở cuối trang.

⚠️ Chỉ dùng phía server. API token là secret. Đưa nó ra trình duyệt hoặc app mobile đồng nghĩa trao quyền truy cập tài khoản GPM Pay của bạn cho bất kỳ ai xem được mã nguồn.


Cài đặt

pnpm add @gpmpay/sdk      # hoặc: npm i @gpmpay/sdk / yarn add @gpmpay/sdk

GPM Pay làm gì

Không có cổng thanh toán nào giữ tiền. Khách chuyển khoản bình thường vào tài khoản của bạn; GPM Pay theo dõi biến động số dư và bắn webhook cho mọi giao dịch tiền vào, kèm số tiền và nội dung chuyển khoản.

Việc đối soát là của bạn. Bạn tự sinh mã đơn, nhét vào nội dung chuyển khoản, rồi khi webhook về thì dò lại mã đó trong payload.content và so số tiền. GPM Pay không sinh mã, không giữ đơn, không khớp lệnh thay bạn — nó là đường ống báo giao dịch.

Toàn bộ mô hình, kèm các bẫy đối soát thực tế: docs/vi/02-payments.md.

Bắt đầu trong 60 giây

1. Tạo API token tại https://app.gpmpay.com/api-tokens với scope webhooks:manage và bank-accounts:read.

2. Cài đặt và kiểm tra token:

export GPMPAY_API_TOKEN=gpm_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy
pnpm add @gpmpay/sdk
npx gpmpay ping
✓ Connected to https://api.gpmpay.com/api/v1  (182 ms)
  Token    gpm_a1b2c3d4••••••••
  Scopes   bank-accounts:read, webhooks:manage
  Missing  transactions:read

3. Dựng QR với mã của bạn:

import { GpmPay } from '@gpmpay/sdk';
import { buildPaymentInstructions } from '@gpmpay/sdk/vietqr';

const client = new GpmPay({ apiToken: process.env.GPMPAY_API_TOKEN! });
const account = (await client.bankAccounts.list({ status: 'ACTIVE' })).data[0]!;

const code = `DH${localOrder.id}`;   // mã của bạn — khách phải ghi chuỗi này

const { qrImageUrl, transferContent } = buildPaymentInstructions({
  bankAccount: account,
  amount: Math.round(localOrder.total),   // VND, số nguyên
  transferContent: code,
});

4. Đối chiếu khi webhook về:

onEvent: async (event) => {
  const code = /DH(\d+)/.exec(event.payload.content)?.[0];
  const order = code && await db.orders.findByCode(code);
  if (order && order.total === event.payload.transferAmount) {
    await giaoHang(order);
  }
}

Cách dựng webhook đầy đủ ở mục dưới.


Cấu hình

const client = new GpmPay({
  apiToken: process.env.GPMPAY_API_TOKEN!,  // BẮT BUỘC
  sandbox: false,                            // true → môi trường thử nghiệm
  timeoutMs: 30_000,
  maxRetries: 2,
  userAgent: 'my-shop/2.1',
  defaultHeaders: { 'X-Trace-Id': traceId }, // không ghi đè được Authorization
  onRequest: (e) => logger.debug(e),         // không bao giờ nhận token
  onResponse: (e) => metrics.timing(e.durationMs),
});

const client = GpmPay.fromEnv();  // đọc GPMPAY_API_TOKEN

SDK không khởi tạo được nếu thiếu token — sai cấu hình lộ ra lúc deploy, không phải lúc khách đầu tiên bấm thanh toán:

new GpmPay({ apiToken: 'sk_live_x' });  // ném GpmPayConfigError — 'invalid_api_token_format'

SDK không bao giờ in token ra. client.toString() và console.log(client) chỉ hiện prefix công khai (gpm_a1b2c3d4••••••••), an toàn để log và dán vào ticket hỗ trợ.

Bảng scope

Backend chỉ còn ba scope:

| Scope | Method dùng được | |---|---| | webhooks:manage | webhookSettings.*, webhookHistories.* | | bank-accounts:read | bankAccounts.list, bankAccounts.retrieve, banks.list | | transactions:read | transactions.list, transactions.listAll, transactions.retrieve, simulator.createTransaction |

Token thiếu scope sẽ nhận GpmPayPermissionError với .missingScope chỉ đúng scope còn thiếu.

⚠️ ApiTokenGuard của backend là fail-closed. Endpoint nào không khai báo scope thì mọi API token đều bị chặn (403), bất kể sở hữu tài khoản. Vì vậy SDK chỉ mô hình hoá đúng những route API token gọi được — vd client.apiTokens chỉ có remove(), vì các route quản lý token còn lại là dashboard-only. Trường hợp này trả GpmPayPermissionError với .reason === 'endpoint'.


Webhook

Xác thực chữ ký

Header gửi kèm phụ thuộc vào authorizationType bạn đặt trên webhook setting — ba chế độ dùng ba header hoàn toàn khác nhau:

| authorizationType | Header GPM Pay gửi | Cách kiểm tra | |---|---|---| | HMAC (mặc định) | X-GPMPay-Signature: t=<unix>,v1=<hex> — đổi tên được qua authorizationHeaderName | constructWebhookEvent() | | API_KEY | Header bạn tự đặt, mặc định Authorization; giá trị là secret thô, không có prefix Bearer | verifyApiKeyHeader(received, expected) | | NONE | Không có header xác thực nào | Không xác thực được — chỉ dùng cho endpoint nội bộ |

Ba điểm hay bị hiểu nhầm:

  • Không tồn tại header X-GPMPay-Timestamp. Timestamp nằm trong t= bên trong giá trị chữ ký.
  • Driver HTTP không gửi X-GPMPay-Event — chỉ driver WordPress gửi. event.type là giá trị mặc định phía SDK.
  • Enum là HMAC, không phải HMAC_SHA256. Thuật toán là SHA-256, tên enum thì không.

Với HMAC: chữ ký ký trên chuỗi `${t}.${rawBody}`, cửa sổ lệch giờ ±300 giây.

import { constructWebhookEvent } from '@gpmpay/sdk/webhooks';

const event = constructWebhookEvent({
  rawBody,                                  // BYTE THÔ, không phải object đã parse
  signature: headers['x-gpmpay-signature'],
  secret: process.env.GPMPAY_WEBHOOK_SECRET!,
});

if (event.payload.transferType === 'in') {
  await doiSoat(event.payload);
}

Với API_KEY:

import { verifyApiKeyHeader } from '@gpmpay/sdk/webhooks';

if (!verifyApiKeyHeader(headers.authorization, process.env.GPMPAY_WEBHOOK_SECRET!)) {
  return res.status(401).end();
}

⚠️ Phải dùng raw body. JSON.stringify(req.body) làm đổi thứ tự key và khoảng trắng, chữ ký sẽ luôn sai. SDK phát hiện và báo lỗi rõ ràng thay vì để bạn ngồi đoán.

Express

import express from 'express';
import { gpmpayWebhook } from '@gpmpay/sdk/webhooks';

app.post(
  '/webhooks/gpmpay',
  express.raw({ type: 'application/json' }),   // ← bắt buộc
  gpmpayWebhook({
    secret: process.env.GPMPAY_WEBHOOK_SECRET!,
    onEvent: async (event) => {
      const code = /DH(\d+)/.exec(event.payload.content)?.[0];
      if (code) await doiSoat(code, event.payload.transferAmount);
    },
  }),
);

Nếu express.json() đã chạy toàn cục, bắt raw body bằng hook verify:

app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } }));

Next.js (App + Pages Router), Fastify, Hono / Cloudflare Workers / Deno, và framework bất kỳ: docs/vi/03-webhooks.md §4.

Đăng ký endpoint và lấy secret

const { setting, secret } = await client.webhookSettings.createHmacEndpoint({
  url: 'https://shop.example.com/webhooks/gpmpay',
});
console.log(secret);  // ← chỉ hiện MỘT LẦN, lưu ngay vào GPMPAY_WEBHOOK_SECRET

Retry và idempotency

GPM Pay huỷ delivery sau 5 giây và thử lại theo lịch 10s → 30s → 2m → 10m → 1h → 6h, tối đa 6 lần.

  • Trả 200 nhanh, xử lý sau (gpmpayWebhook mặc định làm vậy — respondEarly: true).
  • Endpoint của bạn phải idempotent theo payload.id — cùng một giao dịch có thể tới nhiều lần.
import { WEBHOOK_RETRY_SCHEDULE_SECONDS, WEBHOOK_MAX_ATTEMPTS } from '@gpmpay/sdk/webhooks';

Payload

event.payload.id             // id giao dịch — DÙNG LÀM KHOÁ IDEMPOTENCY
event.payload.content        // nội dung chuyển khoản, cắt còn 100 ký tự — MÃ CỦA BẠN Ở ĐÂY
event.payload.transferAmount // number, không phải string
event.payload.referenceCode  // mã giao dịch CỦA NGÂN HÀNG — không phải mã đơn của bạn

Đủ 11 field, kèm chỗ dễ nhầm giữa content và referenceCode: docs/vi/03-webhooks.md §2.


VietQR

Toàn bộ phần này chạy thuần client, không gọi mạng — backend không có endpoint VietQR nào.

import {
  buildPaymentInstructions,
  buildVietQrPayload,
  buildVietQrImageUrl,
} from '@gpmpay/sdk/vietqr';

const account = await client.bankAccounts.retrieve(bankAccountId);

// Cách gọn nhất: một lần gọi ra đủ thứ trang thanh toán cần.
const info = buildPaymentInstructions({
  bankAccount: account,           // phải kèm quan hệ `bank` (BIN nằm trong đó)
  amount: 250_000,
  transferContent: 'DH1042',      // MÃ CỦA BẠN — tự sinh, tự đối soát
});
// → { qrPayload, qrImageUrl, amount, transferContent, bankName, bankBin, accountNumber, accountName }

// Hoặc dựng từng phần nếu bạn đã có sẵn BIN và số tài khoản:
buildVietQrPayload({ bankBin: '970422', accountNumber: '1234567890', amount: 250_000, description: 'DH1042' });
buildVietQrImageUrl({ bankBin: '970422', accountNumber: '1234567890', amount: 250_000, description: 'DH1042' });

⚠️ Mã đối soát là của bạn. GPM Pay không sinh mã nào cả. Hãy chọn mã ngắn, không dấu, và đặt ở đầu nội dung chuyển khoản — VietQR cắt phần mô tả còn 25 ký tự, và một số ngân hàng còn chèn thêm tiền tố của riêng họ vào content. Nên dò bằng regex thay vì so bằng ===.


CLI

gpmpay ping                        Kiểm tra token + probe từng scope xem có thật không
gpmpay accounts list               Liệt kê tài khoản ngân hàng — nguồn của --account
gpmpay accounts get <id>
gpmpay transactions list           [--limit <n>] [--account <uuid>] [--type IN|OUT]
gpmpay simulate tx                 --account <uuid> --amount <vnd> --content <text>
gpmpay webhook send --url <url>    Ký payload mẫu rồi POST vào handler của bạn
gpmpay webhook listen              [--port 4444] [--secret <s>]
gpmpay webhook verify              --signature "t=..,v1=.." [--file body.json]
gpmpay webhook settings            Endpoint đã đăng ký + chế độ xác thực của từng cái
gpmpay webhook history             Lịch sử giao, mã lỗi, số lần thử
gpmpay webhook retry <id>          Đẩy lại một lần giao thất bại

--token --sandbox --json --no-color -h -v

Exit code: 0 OK · 1 lỗi chung · 2 sai cú pháp / thiếu token · 3 xác thực thất bại (401) · 4 mạng/timeout. --json tự che mọi trường secret.

Test luồng mà không cần tiền thật

npx gpmpay accounts list                    # copy một uuid ra
npx gpmpay webhook listen --port 4444 --secret $GPMPAY_WEBHOOK_SECRET

# terminal khác — bắn một event đã ký vào handler của bạn.
# Không cần API token, không gọi API GPM Pay:
npx gpmpay webhook send --url http://localhost:4444 --secret $GPMPAY_WEBHOOK_SECRET

# handler phải TỪ CHỐI hai lệnh này:
npx gpmpay webhook send --url http://localhost:4444 --secret $GPMPAY_WEBHOOK_SECRET --bad-signature
npx gpmpay webhook send --url http://localhost:4444 --secret $GPMPAY_WEBHOOK_SECRET --skew 600

Muốn chính GPM Pay bắn webhook thật (thay vì CLI giả lập) thì dùng simulate trên sandbox:

npx gpmpay simulate tx --sandbox --account <uuid> --amount 50000 --content DH1042
npx gpmpay webhook history --sandbox        # xem đã giao chưa, mã lỗi là gì

simulate từ chối chạy trên production trừ khi truyền --allow-production. Webhook chỉ bắn cho endpoint bật fireOnSimulated — kiểm tra bằng gpmpay webhook settings.


Xử lý lỗi

GpmPayError
├── GpmPayConfigError            lỗi cục bộ, chưa gọi mạng
├── GpmPayConnectionError        DNS/TCP/TLS  (.syscallCode)
├── GpmPayTimeoutError           (.timeoutMs)
├── GpmPayWebhookSignatureError  (.reason)
└── GpmPayAPIError               (.status, .requestId, .rawBody)
    ├── GpmPayBadRequestError      400  (.validationMessages)
    ├── GpmPayAuthenticationError  401  (.reason)
    ├── GpmPayPermissionError      403  (.missingScope, .reason)
    ├── GpmPayNotFoundError        404  (.resource)
    ├── GpmPayRateLimitError       429  (.retryAfterSeconds)
    └── GpmPayServerError          5xx

GpmPayPermissionError.reason phân biệt ba kiểu 403:

| .reason | Nghĩa | |---|---| | 'scope' | Token hợp lệ nhưng thiếu scope — xem .missingScope | | 'endpoint' | Route này dashboard-only, không scope nào mở được | | 'ownership' | Tài nguyên tồn tại nhưng thuộc tài khoản khác |

try {
  await client.webhookSettings.create({ ... });
} catch (error) {
  if (error instanceof GpmPayPermissionError) {
    console.error('403:', error.reason, error.missingScope);
  }
}

Mọi GpmPayAPIError đều mang .requestId — dán vào ticket hỗ trợ để tra log server. Nếu lẫn bản CJS và ESM trong cùng tiến trình, instanceof có thể sai; dùng GpmPayError.isGpmPayError(error).


Kiểu dữ liệu — 3 điểm cần nhớ

| | Đọc về | Gửi đi | |---|---|---| | Tiền | string ("50000", do Prisma Decimal) | number nguyên (50000) | | Thời gian | ISO string | string \| Date | | Enum | string-literal union, không phải TS enum | |

import { toVnd, formatVnd } from '@gpmpay/sdk';

toVnd(transaction.amount);     // 50000
formatVnd(transaction.amount); // '50.000 ₫'

Phân trang: limit bị API giới hạn tối đa 50, SDK tự clamp và cảnh báo một lần. Cần duyệt hết thì dùng transactions.listAll() — nó tự đi từng trang.


Sandbox & testing

const client = new GpmPay({ apiToken, sandbox: true });

// Trả envelope, không phải Transaction trần.
const { transaction, historyIds } = await client.simulator.createTransaction({
  bankAccountId,
  amount: 50_000,
  transferContent: 'DH1042',   // đúng mã bạn sẽ đối soát
});

// historyIds rỗng = chưa endpoint nào bật fireOnSimulated, handler sẽ không được gọi.
console.log(transaction.id, historyIds.length);

simulator từ chối chạy trên production trừ khi truyền { allowOnProduction: true }. Trong unit test thì inject fetch (new GpmPay({ apiToken, fetch: myMockFetch })) thay vì gọi mạng thật — chi tiết ở docs/vi/04-errors-and-testing.md.


Chuyển từ fetch thô

| Trước | Sau | |---|---| | JSON.parse(res).data.data + .meta | client.transactions.list() → { data, meta } | | if (res.status === 403) { ... } | catch (e) { if (e instanceof GpmPayPermissionError) ... } | | Tự viết HMAC verify | constructWebhookEvent() | | Tự nối chuỗi EMVCo + CRC16 | buildVietQrPayload() | | Number(tx.amount) rải rác | toVnd(tx.amount) |


Tài liệu

Mọi file dưới đây ship kèm package (có sẵn trong node_modules/@gpmpay/sdk/) và đọc được ngay trên web mà không cần cài gì:

| Tài liệu | Nội dung | |---|---| | docs/vi/01-getting-started.md | Từ 0 đến khoản thanh toán đầu tiên | | docs/vi/02-payments.md | Mô hình tự đối soát, VietQR, các bẫy khớp mã | | docs/vi/03-webhooks.md | Đăng ký, 3 chế độ xác thực, Express/Next/Fastify/Hono, retry, debug | | docs/vi/04-errors-and-testing.md | Cây lỗi, retry, sandbox, unit test | | docs/vi/05-api-reference.md | Mọi option, method, kiểu dữ liệu | | AGENTS.md | Chỉ dẫn cho AI coding agent | | examples/ | Ví dụ chạy được | | docs/en/ | Toàn bộ nội dung trên, bản tiếng Anh |

Duyệt toàn bộ file của package: https://unpkg.com/browse/@gpmpay/sdk/ · Trang docs: https://app.gpmpay.com/docs#nodejs-sdk. Cần markdown thô (cho AI agent, curl, script) thì đổi unpkg.com/browse/ thành cdn.jsdelivr.net/npm/.

Tương thích

  • Node >= 18.17 (cần fetch, AbortSignal.timeout, node:util.parseArgs)
  • ESM và CommonJS đều dùng được
  • TypeScript: khai báo kiểu đi kèm, không cần @types/*
  • Không hỗ trợ trình duyệt — API token là secret phía server

License

MIT © GPM Softwares