DocsDokumentasi API Reseller

Production Guide

Contoh integrasi dan checklist sebelum produksi.

Bagian 6 dari 6Diperbarui

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.

JavaScript
// 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 Authorization tidak ikut tercatat di log aplikasi dan proxy.
  • Allowlist IP key berisi IP server produksi Anda.
  • Setiap pembelian membuat Idempotency-Key baru, menyimpannya sebelum request pertama, dan memakai ulang key itu saat mencoba lagi.
  • Percobaan ulang memakai Retry-After atau jeda yang membesar, dengan jumlah percobaan terbatas.
  • items dari respons POST /orders disimpan segera.
  • Penanganan untuk insufficient_balance, insufficient_stock, dan stock_reserved sudah ada.
  • Percabangan memakai error.code, bukan teks error.message.
  • Jumlah request paralel dijaga 1 sampai 2.
  • Ada prosedur memutar key bila diduga bocor, termasuk memperbarui semua aplikasi sekaligus.
  • request_id dicatat 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].