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 keys và Developers → Webhooks.
1. Cài đặt SDK trình duyệt
npm
npm install @lartech/infraio-checkout-js2. 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
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)
Popup
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.
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
- Khái niệm → Sessions — mô hình dữ liệu ba cấp (CheckoutSession + Order + PaymentIntent).
- Khái niệm → Chains & tài sản — các network và token được hỗ trợ trong live so với test.
- Webhooks → Tổng quan — mọi loại event và điều gì kích hoạt chúng.
- Bảo mật → API keys — scope, rotate, rate limit.