UNPKG

@gpmpay/sdk

Version:

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

222 lines (159 loc) • 6.69 kB
# Getting started [Tiếng Việt](../vi/01-getting-started.md) · [Index](./README.md) This guide takes you from nothing to a real, confirmed payment. --- ## How GPM Pay works No gateway holds the money. The customer makes an ordinary bank transfer into your account. GPM Pay watches that account and POSTs a webhook for **every** incoming transaction, carrying the amount and the transfer content. That is the whole product. Your job: put your own order code in the transfer content, then match on it when the webhook arrives. > **Reconciliation is yours.** GPM Pay mints no codes and holds no order state. > Your own orders table stays the source of truth — see > [the payment model](./02-payments.md#the-model). --- ## 1. Create an API token Go to <https://app.gpmpay.com/api-tokens> → **Create token**. | Scope | Needed for | |---|---| | `webhooks:manage` | creating / updating webhooks from code | | `bank-accounts:read` | fetching `bankAccountId` and the bank BIN, `client.ping()` | | `transactions:read` | listing transactions, periodic reconciliation | Those three are the only scopes that exist. Minimum to get started: `webhooks:manage` + `bank-accounts:read`. > The token is shown **once**. Store it in an environment variable immediately. ```bash # .env GPMPAY_API_TOKEN=gpm_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy ``` ## 2. Install and verify the token ```bash 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 ``` `ping` probes each scope with a real request, so the `Missing` line tells you what the token actually lacks — before you discover it as a 403 in production. Requires Node >= 18.17. No runtime dependencies. Exit codes are script-friendly: `0` ok · `2` missing token / bad usage · `3` token rejected (401) · `4` network failure. ## 3. Create a client ```ts import { GpmPay } from '@gpmpay/sdk'; const client = new GpmPay({ apiToken: process.env.GPMPAY_API_TOKEN! }); ``` Or read `GPMPAY_API_TOKEN` straight from the environment: ```ts const client = GpmPay.fromEnv(); ``` The constructor **throws immediately** if the token is missing or malformed — before any request is sent. Do not wrap it in a try/catch that swallows the error: a missing token should crash the app at boot, not at checkout. ```ts new GpmPay({}); // GpmPayConfigError — missing_api_token new GpmPay({ apiToken: 'sk_live_x' }); // GpmPayConfigError — invalid_api_token_format ``` > **Server-side only.** Never put the token in a `NEXT_PUBLIC_*` variable, a > client component, or a mobile app. It has full access to your GPM Pay account. ## 4. Fetch the bank account ```ts const account = (await client.bankAccounts.list({ status: 'ACTIVE' })).data[0]!; account.id // used when simulating transactions in tests account.accountNumber // used to build the QR account.bank!.bin // NAPAS BIN, also for the QR ``` In practice you'll store these in config rather than fetching them every time. ## 5. Build the QR with your own code ```ts import { buildPaymentInstructions } from '@gpmpay/sdk/vietqr'; const code = `DH${localOrder.id}`; // your code — max 25 chars, keep it A-Z0-9 const { qrPayload, qrImageUrl, transferContent } = buildPaymentInstructions({ bankAccount: account, // carries the bank BIN amount: Math.round(localOrder.total), // VND, INTEGER transferContent: code, }); ``` Pure local computation — no network call, so this cannot fail on a GPM Pay outage. Store `code` on your local order. Two things to get right: - **The amount must be an integer number of VND.** A float from a currency library produces a QR with a slightly different amount, and the transfer will not match your order. - **Pick a code that is easy to extract.** A fixed prefix plus a numeric id (`DH123`) is plenty. Avoid diacritics, spaces, and punctuation — banks may normalize the content differently. ## 6. Show it to the customer ```tsx <img src={qrImageUrl} alt="Scan to pay" /> <p>Amount: {formatVnd(localOrder.total)}</p> <p>Transfer content: <strong>{code}</strong></p> ``` If you render the QR yourself from `qrPayload`, any QR library works (`qrcode`, `react-qr-code`, …). Customers who scan the QR get the content pre-filled. Customers who transfer manually need the code shown prominently with one-tap copy — in practice this is where things go wrong most often. ## 7. Register a webhook ```ts const { secret } = await client.webhookSettings.createHmacEndpoint({ url: 'https://shop.example.com/webhooks/gpmpay', }); // store `secret` as GPMPAY_WEBHOOK_SECRET — shown once only ``` ## 8. Receive and reconcile ```ts import express from 'express'; import { gpmpayWebhook } from '@gpmpay/sdk/webhooks'; app.post( '/webhooks/gpmpay', express.raw({ type: 'application/json' }), // REQUIRED gpmpayWebhook({ secret: process.env.GPMPAY_WEBHOOK_SECRET!, onEvent: async (event) => { const { id: transactionId, content, transferAmount, transferType } = event.payload; if (transferType !== 'in') return; const code = /DH(\d+)/.exec(content)?.[0]; if (!code) return; const order = await db.orders.findByCode(code); if (!order) return; // NOTHING compares the amount for you. if (order.total !== transferAmount) { await flagUnderpayment(order, transferAmount); return; } await fulfil(order, transactionId); }, }), ); ``` The handler must be **idempotent on `event.payload.id`** — the same transaction arrives more than once whenever your first response is slow or fails. Full details: [Webhooks](./03-webhooks.md). ## 9. Test the whole flow without real money ```ts const client = new GpmPay({ apiToken, sandbox: true }); await client.simulator.createTransaction({ bankAccountId: account.id, amount: Math.round(localOrder.total), transferContent: `DH${localOrder.id}`, // the same code you gave the customer }); // the webhook fires immediately and the step-8 handler runs ``` `simulator` refuses to run against production unless you pass `{ allowOnProduction: true }`. To catch webhooks on your own machine: ```bash npx gpmpay webhook listen --port 4444 --secret $GPMPAY_WEBHOOK_SECRET ngrok http 4444 ``` --- ## Next - [Payments & reconciliation](./02-payments.md) — choosing a code, matching pitfalls, VietQR - [Webhooks end to end](./03-webhooks.md) - [Errors & testing](./04-errors-and-testing.md) - [API reference](./05-api-reference.md)