6.1 Contoh integrasi: beli dengan percobaan ulang
Contoh Node.js 18 atau lebih baru. Jalankan di server Anda, bukan di browser. Key dan alamat dasar dibaca dari environment variable.
// beli.mjs
import { randomUUID } from "node:crypto";
const BASE = process.env.AYOSEL_BASE_URL; // https://api.contoh.example/api/v1/t/tokoku/reseller
const API_KEY = process.env.AYOSEL_API_KEY; // dari secret manager atau env, bukan dari kode
// Aman diulang dengan Idempotency-Key yang sama.
const RETRYABLE = new Set(["insufficient_stock", "request_in_progress", "server_busy", "rate_limited"]);
const MAX_ATTEMPTS = 5;
const MAX_WAIT_MS = 60_000;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const backoff = (attempt) => Math.min(8000, 500 * 2 ** (attempt - 1)) + Math.random() * 250;
export async function buy(variantId, quantity, idempotencyKey = `buy-${randomUUID()}`) {
// Simpan idempotencyKey di database Anda sebelum percobaan pertama, dan pakai
// nilai yang sama pada setiap percobaan ulang untuk pembelian ini.
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
let res;
let body;
try {
res = await fetch(`${BASE}/orders`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({ variant_id: variantId, quantity }),
signal: AbortSignal.timeout(15_000),
});
body = await res.json().catch(() => null);
} catch {
// Timeout atau koneksi putus: hasilnya tidak diketahui. Ulangi dengan key yang sama.
await sleep(backoff(attempt));
continue;
}
if (body?.success) {
return {
order: body.data, // simpan body.data.items sekarang
replayed: res.headers.get("Idempotent-Replayed") === "true",
};
}
const code = body?.error?.code ?? `http_${res.status}`;
if (!RETRYABLE.has(code) && res.status < 500) {
// Perbaiki request, jangan diulang begitu saja.
throw new Error(`${code}: ${body?.error?.message ?? res.statusText}`);
}
const retryAfter = Number(res.headers.get("Retry-After"));
const wait = retryAfter > 0 ? retryAfter * 1000 : backoff(attempt);
if (wait > MAX_WAIT_MS) {
throw new Error(`${code}: terlalu lama menunggu (${Math.round(wait / 1000)} detik)`);
}
await sleep(wait);
}
// Hasil belum pasti. Cek GET /orders?idempotency_key=<key> sebelum menyerah.
throw new Error(`pembelian ${idempotencyKey} belum terkonfirmasi setelah ${MAX_ATTEMPTS} percobaan`);
}6.2 Daftar periksa sebelum produksi
- Key hanya ada di server dan di secret manager atau environment variable, tidak di kode, repositori, browser, atau aplikasi mobile.
- Header
Authorizationtidak ikut tercatat di log aplikasi dan proxy. - Allowlist IP key berisi IP server produksi Anda.
- Setiap pembelian membuat
Idempotency-Keybaru, menyimpannya sebelum request pertama, dan memakai ulang key itu saat mencoba lagi. - Percobaan ulang memakai
Retry-Afteratau jeda yang membesar, dengan jumlah percobaan terbatas. itemsdari responsPOST /ordersdisimpan segera.- Penanganan untuk
insufficient_balance,insufficient_stock, danstock_reservedsudah ada. - Percabangan memakai
error.code, bukan tekserror.message. - Jumlah request paralel dijaga 1 sampai 2.
- Ada prosedur memutar key bila diduga bocor, termasuk memperbarui semua aplikasi sekaligus.
request_iddicatat untuk setiap galat supaya mudah dilacak bersama toko.
Butuh bantuan dengan integrasi?
Sertakan request_id dari respons galat, lalu hubungi tim Ayosel lewat Telegram atau WhatsApp, atau email [email protected].