# Bắt đầu

[English](../en/01-getting-started.md) · [Mục lục](./README.md)

Hướng dẫn này đưa bạn từ con số 0 đến một khoản thanh toán được xác nhận thật.

---

## GPM Pay hoạt động thế nào

Không có cổng thanh toán nào giữ tiền. Khách chuyển khoản ngân hàng 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.

Đó là toàn bộ sản phẩm. Việc của bạn: nhét mã đơn hàng **của chính bạn** vào nội dung chuyển khoản, rồi khi webhook về thì đối chiếu lại.

> **Việc đối soát là của bạn.** GPM Pay không sinh mã và không giữ trạng thái đơn. Bảng đơn hàng của bạn vẫn là nguồn sự thật duy nhất — xem [mô hình thanh toán](./02-payments.md#mô-hình).

---

## 1. Tạo API token

Vào <https://app.gpmpay.com/api-tokens> → **Tạo token**.

| Scope | Cần khi |
|---|---|
| `webhooks:manage` | tạo / sửa webhook bằng code |
| `bank-accounts:read` | lấy `bankAccountId` và mã BIN ngân hàng, chạy `client.ping()` |
| `transactions:read` | liệt kê giao dịch, đối soát định kỳ |

Đó là toàn bộ scope hiện có. Tối thiểu để bắt đầu: `webhooks:manage` + `bank-accounts:read`.

> Token chỉ hiện **một lần** lúc tạo. Lưu ngay vào biến môi trường.

```bash
# .env
GPMPAY_API_TOKEN=gpm_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy
```

## 2. Cài đặt và kiểm tra 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` gọi thật từng scope một, nên dòng `Missing` cho biết chính xác token đang thiếu gì — trước khi bạn phát hiện qua lỗi 403 lúc chạy thật.

Yêu cầu Node >= 18.17. Package không có dependency runtime nào.

Exit code dùng được trong script CI: `0` OK · `2` thiếu token / sai cú pháp · `3` token bị từ chối (401) · `4` lỗi mạng.

## 3. Khởi tạo client

```ts
import { GpmPay } from '@gpmpay/sdk';

const client = new GpmPay({ apiToken: process.env.GPMPAY_API_TOKEN! });
```

Hoặc đọc thẳng `GPMPAY_API_TOKEN` từ môi trường:

```ts
const client = GpmPay.fromEnv();
```

Constructor **ném lỗi ngay** nếu thiếu token hoặc token sai định dạng — chưa gửi request nào. Đừng bọc nó trong `try/catch` nuốt lỗi: thiếu token phải làm app chết lúc khởi động, không phải lúc khách đầu tiên bấm thanh toán.

```ts
new GpmPay({});                        // GpmPayConfigError — missing_api_token
new GpmPay({ apiToken: 'sk_live_x' }); // GpmPayConfigError — invalid_api_token_format
```

> **Chỉ dùng phía server.** Không đặt token vào biến `NEXT_PUBLIC_*`, không dùng trong client component, không nhúng vào app mobile. Token có toàn quyền trên tài khoản GPM Pay của bạn.

## 4. Lấy tài khoản ngân hàng

```ts
const account = (await client.bankAccounts.list({ status: 'ACTIVE' })).data[0]!;

account.id                  // dùng khi giả lập giao dịch để test
account.accountNumber       // dùng để dựng QR
account.bank!.bin           // mã BIN NAPAS, cũng để dựng QR
```

Thường bạn sẽ lưu mấy giá trị này vào config thay vì gọi mỗi lần.

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

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

const code = `DH${localOrder.id}`;   // mã của bạn — tối đa 25 ký tự, nên giữ A-Z0-9

const { qrPayload, qrImageUrl, transferContent } = buildPaymentInstructions({
  bankAccount: account,                     // đã kèm mã BIN của ngân hàng
  amount: Math.round(localOrder.total),     // VND, SỐ NGUYÊN
  transferContent: code,
});
```

Thuần tính toán local, không gọi mạng — nên bước này không thể hỏng vì GPM Pay gặp sự cố. Lưu `code` vào đơn nội bộ của bạn.

Hai lưu ý:

- **Số tiền phải là số nguyên VND.** Số thực từ thư viện tiền tệ sẽ tạo QR có số tiền lệch, khách chuyển xong không khớp được với đơn của bạn.
- **Chọn mã dễ dò.** Tiền tố cố định + id số (`DH123`) là đủ. Tránh dấu tiếng Việt, khoảng trắng, ký tự đặc biệt — ngân hàng có thể chuẩn hoá nội dung khác đi.

## 6. Hiển thị cho khách

```tsx
<img src={qrImageUrl} alt="Quét để thanh toán" />
<p>Số tiền: {formatVnd(localOrder.total)}</p>
<p>Nội dung chuyển khoản: <strong>{code}</strong></p>
```

Nếu tự render QR từ `qrPayload`, dùng thư viện QR bất kỳ (`qrcode`, `react-qr-code`…).

Khách quét QR thì nội dung đã được điền sẵn. Khách chuyển tay thì hiển thị `code` thật to và cho copy một chạm — đây là chỗ hay sai nhất trong thực tế.

## 7. Đăng ký webhook

```ts
const { secret } = await client.webhookSettings.createHmacEndpoint({
  url: 'https://shop.example.com/webhooks/gpmpay',
});
// lưu `secret` vào GPMPAY_WEBHOOK_SECRET — chỉ hiện một lần
```

## 8. Nhận và đối soát

```ts
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 { 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;

      // KHÔNG có ai so số tiền hộ bạn.
      if (order.total !== transferAmount) {
        await flagUnderpayment(order, transferAmount);
        return;
      }

      await giaoHang(order, transactionId);
    },
  }),
);
```

Handler phải **idempotent theo `event.payload.id`** — cùng một giao dịch sẽ tới nhiều lần nếu lần đầu chậm hoặc lỗi. Chi tiết đầy đủ: [Webhook](./03-webhooks.md).

## 9. Thử toàn bộ luồng mà không cần tiền thật

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

await client.simulator.createTransaction({
  bankAccountId: account.id,
  amount: Math.round(localOrder.total),
  transferContent: `DH${localOrder.id}`,   // đúng mã bạn đã đưa cho khách
});
// webhook bắn ngay, handler ở bước 8 chạy
```

`simulator` từ chối chạy trên production trừ khi truyền `{ allowOnProduction: true }`.

Muốn bắt webhook ngay trên máy local:

```bash
npx gpmpay webhook listen --port 4444 --secret $GPMPAY_WEBHOOK_SECRET
ngrok http 4444
```

---

## Tiếp theo

- [Thanh toán & đối soát](./02-payments.md) — chọn mã, các bẫy khớp lệnh, VietQR
- [Webhook từ A đến Z](./03-webhooks.md)
- [Xử lý lỗi & testing](./04-errors-and-testing.md)
- [Tham chiếu API](./05-api-reference.md)
