<!-- Source: https://docs.infraio.xyz/vi/get-started/quickstart -->
<!-- Last updated: 2026-10-04 -->

# Bắt đầu nhanh

Mục tiêu: thực hiện một giao dịch thật ở chế độ thử nghiệm từ đầu đến cuối. Khi
kết thúc, bạn sẽ có một session được tạo từ server của bạn, một checkout
mở trong trình duyệt người mua, và một webhook đã ký gửi đến localhost.

> **Note:**
>
> Bạn cần một cặp API key **test** (`pk_test_…` + `sk_test_…`) và một
> **secret ký webhook** (`whsec_…`). Tạo chúng trong [merchant
> dashboard](https://app.infraio.xyz) tại **Developers →
> API keys** và **Developers → Webhooks**.

## 1. Cài đặt SDK trình duyệt

**npm**

```bash
npm install @lartech/infraio-checkout-js
```

**pnpm**

```bash
pnpm add @lartech/infraio-checkout-js
```

**yarn**

```bash
yarn add @lartech/infraio-checkout-js
```

**bun**

```bash
bun add @lartech/infraio-checkout-js
```

## 2. Tạo checkout session (server)

Session được tạo từ server của bạn, ký bằng HMAC-SHA256 với khóa
**secret** của bạn. Không bao giờ đưa `sk_` vào trình duyệt.

### Dựng chuỗi canonical để ký

```
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY
```

Đối với request `POST /b2b/v1/checkout-sessions/quick` tại unix-timestamp
`1715990400` với body `{...}`, chuỗi đầu vào để ký là:

```
POST
/b2b/v1/checkout-sessions/quick
1715990400
{"items":[...],"currency":"USD","success_url":"..."}
```

Ký bằng `HMAC-SHA256(secret_key, signingString)`, kết xuất hex.

### Gửi request

```ts filename="server/create-session.ts"
import { fetch } from "undici";
import { createHmac } from "node:crypto";

const PUBLIC_KEY = process.env.INFRAIO_PUBLIC_KEY!;   // pk_test_…
const SECRET_KEY = process.env.INFRAIO_SECRET_KEY!;   // sk_test_…
const BASE_URL   = "https://api-dev.infraio.xyz";

const path = "/b2b/v1/checkout-sessions/quick";
const body = JSON.stringify({
items: [
  {
    sku: "tee-large-blue",
    label: "Indigo Tee — L",
    unit_price: "49.00",
    quantity: 1,
    currency: "USD",
    image_url: "https://your-shop.test/img/tee.png",
  },
],
currency: "USD",
customer_email: "buyer@example.com",
success_url: "https://your-shop.test/return?status=success",
cancel_url:  "https://your-shop.test/return?status=cancel",
external_ref: "ord_1234",        // order id của bạn; được lưu trên order, không echo trong webhook — tra cứu qua order_id
expires_in: 1800,                // giây; mặc định 30 phút
idempotency_key: crypto.randomUUID(), // an toàn để retry
});

const timestamp = Math.floor(Date.now() / 1000).toString();
const signingInput = ["POST", path, timestamp, body].join("\n");
const signature = createHmac("sha256", SECRET_KEY)
.update(signingInput)
.digest("hex");

const res = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: {
  "Content-Type": "application/json",
  "X-Client-ID":  PUBLIC_KEY,
  "X-Timestamp":  timestamp,
  "X-Signature":  signature,
},
body,
});

// Mọi response API đều được bọc trong một envelope — payload thực sự
// nằm dưới `data`. Coi `code`/`message` ở top level là chỉ báo kết quả
// (`201` / `"created"` nếu thành công).
const { code, message, data } = await res.json() as {
code: number;
message: string;
data: { session_key: string; checkout_url: string; order_id: string; expires_at: string };
};
const { session_key, checkout_url, order_id, expires_at } = data;
// Gửi session_key + checkout_url xuống trình duyệt. order_id và expires_at
// dùng cho việc ghi nhận nội bộ của bạn.
```

> **Warning:**
>
> Độ lệch đồng hồ rất quan trọng. API sẽ reject request có
> `X-Timestamp` lệch quá **5 phút** so với giờ server. Đồng bộ server
> qua NTP; đừng tin đồng hồ hệ thống của một cron chạy dài hạn.

## 3. Mở checkout (trình duyệt)

**Popup**

```ts filename="checkout.ts"
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo(process.env.NEXT_PUBLIC_INFRAIO_PK!);

sdk.checkout({
  sessionId: session_key,
  checkoutUrl: checkout_url,
  mode: "popup",            // mặc định
  onReady:   () => console.log("checkout interactive"),
  onSuccess: ({ sessionId }) => {
    // Chỉ phục vụ UX — xem cảnh báo bên dưới. Webhook mới là nguồn xác thực.
    location.assign("/thanks");
  },
  onCancel: () => console.log("buyer closed the popup"),
  onError:  (err) => console.error(err.code, err.message),
});
```

**Redirect**

```ts filename="checkout.ts"
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo(process.env.NEXT_PUBLIC_INFRAIO_PK!);

sdk.checkout({
  sessionId: session_key,
  checkoutUrl: checkout_url,
  mode: "redirect",
});
// Trình duyệt điều hướng sang checkout.infraio.xyz; người mua quay về
// `success_url` / `cancel_url` từ payload tạo session.
```

**Embed**

```ts filename="checkout.ts"
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo(process.env.NEXT_PUBLIC_INFRAIO_PK!);

sdk.checkout({
  sessionId: session_key,
  checkoutUrl: checkout_url,
  mode: "embed",
  container: "#checkout-container",   // mục tiêu DOM của bạn
  onReady: () => /* iframe đã load */ {},
});
```

## 4. Xử lý webhook (server)

Webhook là tín hiệu xác thực **duy nhất** cho thấy tiền đã chuyển. Các
callback phía trình duyệt (`onSuccess`) có thể bị kích hoạt mà chưa có
giao dịch thật ở một số timing trên testnet — đừng bao giờ fulfillment
dựa trên event phía trình duyệt.

```ts filename="server/webhook.ts (Next.js App Router)"
import { createHmac, timingSafeEqual } from "node:crypto";

const WEBHOOK_SECRET = process.env.INFRAIO_WEBHOOK_SECRET!; // whsec_…

export async function POST(req: Request) {
  const raw = await req.text();                    // bytes RAW, chưa parse
  const signature = req.headers.get("x-signature") ?? "";
  const timestamp = req.headers.get("x-timestamp") ?? "";

  // Chữ ký được tính trên `${timestamp}.${raw}` — KHÔNG phải body một mình.
  const expected = "sha256=" + createHmac("sha256", WEBHOOK_SECRET)
    .update(`${timestamp}.${raw}`)
    .digest("hex");

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return new Response("bad signature", { status: 400 });
  }

  // HTTP body CHÍNH LÀ object data của event đó (không có envelope ngoài
  // kiểu Stripe). Event type + delivery ID + timestamp nằm ở header.
  const eventType = req.headers.get("x-event") ?? "";        // ví dụ "payment.settled"
  const deliveryId = req.headers.get("idempotency-key")      // đồng nghĩa với X-Delivery
                   ?? req.headers.get("x-delivery") ?? "";

  const payload = JSON.parse(raw);
  switch (eventType) {
    case "payment.settled": {
      // Payload mang order_id, payment_intent_id, tx_hash,
      // amount_received, confirmations, settlement token + network, v.v.
      // KHÔNG bao gồm `external_ref` từ order — bạn tra cứu phía server
      // qua order_id nếu cần ánh xạ ngược.
      const { order_id, tx_hash, amount_received, confirmations } = payload;

      // Đánh dấu order đã thanh toán theo cách idempotent. Dùng deliveryId
      // (X-Delivery) làm khóa dedup — nó ổn định qua mọi lần retry của
      // cùng một delivery. Chúng tôi retry tối đa 6 lần (1m / 5m / 15m /
      // 1h / 6h) khi không nhận 2xx.
      break;
    }
    case "checkout.expired": { /* session TTL hết — giải phóng tồn kho */ break; }
    case "payment.refund.requested": { /* hiển thị trong queue admin */ break; }
    default:
      // Forward-compat: chúng tôi có thể thêm event. Chấp nhận và no-op.
  }
  return new Response("ok", { status: 200 });
}
```

Xem [Webhooks → Xác thực chữ ký](https://docs.infraio.xyz/vi/webhooks/signature-verification)
để biết các trường hợp đặc biệt (chuẩn hóa body, rotate secret, chống replay).

## 5. Kích hoạt một giao dịch thử

Session ở chế độ thử nghiệm được settle trên **các testnet thật** (Sepolia,
Base Sepolia, BSC Testnet, v.v.) — không có chain giả lập. Lấy token
thử từ faucet phù hợp, rồi gửi đến địa chỉ nhận tiền riêng cho từng đơn mà trang checkout
hiển thị. Webhook sẽ được gửi đi khi đạt đủ confirmation (1 conf trên
hầu hết các testnet).

Trên TRON Nile, Solana Devnet và TON Testnet (sắp ra mắt) không có địa chỉ nhận tiền riêng cho từng đơn: người mua trả từ ví thông qua trang checkout. Xem [Network thanh toán thẳng vào ví](https://docs.infraio.xyz/vi/concepts/chains#network-thanh-toán-thẳng-vào-ví).

## Tiếp theo

- [Khái niệm → Sessions](https://docs.infraio.xyz/vi/concepts/sessions) — mô hình dữ liệu ba
  cấp (CheckoutSession + Order + PaymentIntent).
- [Khái niệm → Chains & tài sản](https://docs.infraio.xyz/vi/concepts/chains) — các network và
  token được hỗ trợ trong live so với test.
- [Webhooks → Tổng quan](https://docs.infraio.xyz/vi/webhooks/overview) — mọi loại event và
  điều gì kích hoạt chúng.
- [Bảo mật → API keys](https://docs.infraio.xyz/vi/security/api-keys) — scope, rotate, rate limit.
