Skip to Content
Bắt đầuBắt đầu nhanh

Bắt đầu nhanh

Mục tiêu: thực hiện một giao dịch thật ở test mode 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.

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  tại Developers → API keysDevelopers → Webhooks.

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

npm install @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

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: "[email protected]", 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.

Độ lệch đồng hồ rất quan trọng. Gateway 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)

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), });

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.

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ý để 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 ở test mode đượ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ỉ deposit 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).

Tiếp theo