Developer · API
Error, idempotensi & retry
Tangani error secara terstruktur dan pulihkan request tanpa membuat pembelian ganda, mengubah nominal, atau melepaskan hold yang belum aman.
Unduh OpenAPI 3.1{
"ok": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Saldo tersedia belum cukup untuk nomor ini."
}
}Gunakan error.code untuk cabang program, error.message untuk pesan pengguna. Jangan bergantung pada terjemahan message. RequestError dari api() menyimpan code, message, dan status. NETWORK_ERROR dengan status 0 berasal dari client, bukan status HTTP server.
import { RequestError } from "@/lib/ping/client";
function classifyFailure(error: unknown) {
if (!(error instanceof RequestError)) return "inspect";
if (error.code === "UNAUTHENTICATED") return "sign-in";
if (["PRICE_CHANGED", "PRICE_OPTION_UNAVAILABLE"].includes(error.code)) {
return "refresh-quote-and-confirm";
}
if (error.code === "INSUFFICIENT_BALANCE") return "top-up";
if (error.status === 0 || error.status >= 500) return "check-existing-intent";
return "show-error-and-refresh";
}Idempotensi order dan top-up memakai clientRequestId pada body JSON. Header Idempotency-Key tidak dibaca oleh kedua route ini. Buat UUID sekali sebelum request pertama, simpan bersama pilihan dan nominal pending, lalu jangan ganti nilainya karena error jaringan.
| Operasi | Perilaku replay |
|---|---|
| POST /api/orders | Unik per userId + clientRequestId. Replay mengembalikan order lama sebelum membuat hold/pembelian baru. Payload yang berbeda tidak membuat intent baru dan tidak otomatis menghasilkan IDEMPOTENCY_CONFLICT; jangan reuse UUID untuk pilihan berbeda. |
| POST /api/payments | UUID yang sama dan nominal sama mengembalikan pembayaran awal. Nominal berbeda menghasilkan IDEMPOTENCY_CONFLICT. |
| Pembayaran aktif lain | Hanya satu QRIS terbaru per user. Nominal sama dapat mengembalikan pembayaran aktif. Untuk mengubah nominal, kirim replacePaymentId milik pembayaran PENDING dengan UUID intent baru. QR lama tidak dibatalkan provider; PAID tetap diverifikasi. CREATING yang ambigu tidak boleh diganti. |
| Cancel / complete | Tidak menerima clientRequestId. Settlement memakai kunci server tetap per order. Panggilan berulang tetap tunduk pada status; jangan mengharapkan setiap pengulangan menghasilkan 200. |
- Langkah 1
Jangan mengartikan timeout sebagai gagal beli
Provider mungkin sudah menerbitkan nomor atau QR walaupun respons tidak sampai. Jangan mengganti UUID, membuat top-up lain, atau membebaskan hold di client.
- Langkah 2
Pulihkan resource yang ada
Jika ID diketahui, baca detail. Untuk top-up, GET pembayaran aktif. Untuk order, tampilkan daftar milik akun; jika UUID awal masih diketahui, recovery request harus memakai UUID dan payload yang sama, bukan retry otomatis dengan intent baru.
- Langkah 3
Tunggu rekonsiliasi bila masih ambigu
Jika message menyebut konfirmasi provider atau error PURCHASE_UNRESOLVED/RECONCILIATION_REQUIRED, lanjutkan pembacaan terbatas dan biarkan scheduler/provider menyelesaikan hasilnya.
- Langkah 4
Mulai intent baru hanya setelah jelas
Jika pengguna benar-benar memilih transaksi baru setelah hasil sebelumnya diketahui, baru buat UUID baru. Perubahan harga memerlukan konfirmasi ulang.
Status berikut berasal dari pemetaan apiHandler. Endpoint webhook dapat memberi override status untuk redelivery; contohnya RECONCILIATION_REQUIRED pada callback SMS menjadi 503. Tidak semua kode muncul pada setiap endpoint.
| Kode | HTTP | Makna | Penanganan |
|---|---|---|---|
| ORDERING_PAUSED | 503 | Server sementara belum menerima pesanan baru; tidak ada alokasi saldo baru. | Tawarkan server lain untuk dipilih pengguna. Pesanan lama dan replay UUID yang sama tetap diproses. |
| BAD_REQUEST | 400 | Body JSON atau Content-Type tidak valid. | Perbaiki request; untuk aksi cancel/complete, kirim {}. |
| VALIDATION_ERROR | 400 | Input tidak sesuai schema. | Periksa tipe, field wajib, format UUID, dan field tambahan. |
| UNAUTHENTICATED | 401 | Sesi tidak tersedia atau sudah berakhir. | Minta pengguna masuk kembali. Jangan mengulang mutasi otomatis. |
| FORBIDDEN | 403 | Origin atau izin akses ditolak. | Periksa origin dan role; jangan menonaktifkan guard. |
| ACCOUNT_SUSPENDED | 403 | Akun ditangguhkan. | Hentikan operasi dan hubungi pengelola. |
| NOT_FOUND | 404 | Resource tidak ditemukan atau bukan milik sesi. | Periksa ID dan akun; ID resource tidak memberi akses lintas pengguna. |
| PRICE_CHANGED | 409 | Harga saat pembelian berbeda dari konfirmasi. | Ambil quote baru dan minta konfirmasi harga lagi. |
| PRICE_OPTION_UNAVAILABLE | 409 | Pilihan harga sudah tidak tersedia. | Ambil quote baru; jangan menebak atau membuat priceOptionId. |
| EARLY_CANCEL_DENIED | 409 | Belum melewati batas pembatalan. | Baca ulang order dan ikuti canCancel serta cancelAvailableAt. |
| INVALID_STATE | 409 | Aksi tidak cocok dengan status terkini. | GET detail order sebelum menawarkan aksi kembali. |
| INVALID_TRANSITION | 409 | Transisi status tidak diizinkan. | Muat ulang order; jangan memaksa perubahan status. |
| ORDER_ALREADY_TERMINAL | 409 | Order telah berakhir. | Tampilkan hasil terkini dan perbarui saldo. |
| PURCHASE_UNRESOLVED | 409 | Hasil pembelian sendiri belum pasti. | Pertahankan UUID awal dan tunggu rekonsiliasi; jangan membuat pembelian baru. |
| PURCHASE_BUSY | 409 | Nomor ini sedang diperpanjang oleh order lain yang masih terbuka. | Pantau order perpanjangan yang terbuka; minta lagi hanya setelah order itu selesai, gagal, atau dibatalkan. |
| RECONCILIATION_REQUIRED | 409 | Hasil provider perlu dikonfirmasi. | Pertahankan hold; baca ulang status. Callback SMS dapat memakai HTTP 503 untuk kode ini. |
| IDEMPOTENCY_CONFLICT | 409 | Kunci idempotensi pesanan, top-up, atau ledger sudah dipakai untuk detail transaksi berbeda. | Pulihkan transaksi awal dengan payload yang sama. UUID baru hanya untuk niat transaksi baru setelah hasil awal terkonfirmasi. |
| CONFLICT | 409 | Ada pembayaran aktif, detail provider tidak cocok, atau username sudah digunakan akun lain. | Baca pembayaran aktif, jangan terus membuat top-up baru. Untuk konflik profil, pilih username lain; kapitalisasi berbeda tetap dianggap username yang sama. |
| INSUFFICIENT_BALANCE | 422 | Saldo tersedia tidak cukup. | Tampilkan available dari server dan tawarkan top-up. |
| INVALID_AMOUNT | 422 | Nominal tidak valid untuk operasi saldo. | Periksa integer IDR. Validasi body top-up sendiri memakai VALIDATION_ERROR / 400. |
| SERVICE_UNAVAILABLE | 422 | Layanan tidak aktif atau tidak ditemukan. | Muat ulang katalog dan pilih layanan yang tersedia. |
| COUNTRY_UNAVAILABLE | 422 | Negara tidak aktif atau tidak ditemukan. | Muat ulang katalog dan pilih negara yang tersedia. |
| PROVIDER_MISMATCH | 422 | Pilihan katalog atau aksi tidak didukung server pesanan. | Pertahankan server asal. Resend tersedia pada semua server (v1–v4) saat provider mengizinkan; jangan memindahkan aktivasi ke provider lain. |
| PRICE_UNAVAILABLE | 422 | Quote/nomor tidak tersedia untuk pasangan ini. | Tawarkan pasangan lain atau coba baca quote lagi nanti. |
| ACTIVE_ORDER_LIMIT | 429 | Batas order aktif tercapai. | Selesaikan order yang ada; limit mengikuti pengaturan operator. |
| ORDER_RATE_LIMIT | 429 | Terlalu banyak percobaan pembelian. | Jeda percobaan. Jangan membuat loop POST. |
| RATE_LIMITED | 429 | Antrean server penuh atau cooldown provider sedang aktif. | Hormati Retry-After sebelum meminta ulang. Jangan retry pembelian otomatis atau membuat clientRequestId baru saat hasil awal belum pasti. |
| INTERNAL_ERROR | 500 | Kegagalan internal yang disanitasi. | Periksa status transaksi sebelum retry; jangan anggap transaksi pasti gagal. |
| PROVIDER_ERROR | 502 | Provider belum dapat memproses permintaan. | Baca ulang status. Jangan retry pembelian upstream secara otomatis. |
| PAYMENT_PROVIDER_ERROR | 502 | Gangguan penyedia pembayaran. | Baca pembayaran aktif atau detail dengan ID yang sama. |
| PROVIDER_UNAVAILABLE | 503 | Provider tidak dapat dihubungi atau belum disiapkan. | Periksa konfigurasi server; lanjutkan pembacaan dengan backoff. |
| PAYMENT_NOT_CONFIGURED | 503 | Pembayaran belum dikonfigurasi. | Pengelola memeriksa konfigurasi mitra pembayaran di server. Perubahan konfigurasi hanya menentukan tagihan baru; tagihan tersimpan tetap diperiksa melalui mitra asalnya. |
| PAYMENT_NOT_READY | 503 | Pembayaran belum siap diverifikasi. | Biarkan mitra pembayaran mengirim ulang callback. Jangan percaya shared secret yang pertama kali terlihat pada callback. |
| PROVIDER_TIMEOUT | 504 | Respons provider melewati batas waktu. | Hasil bisa ambigu. Pertahankan UUID dan hold, tunggu rekonsiliasi. |
Batas jumlah order aktif, frekuensi pembelian, provider budget, dan backoff adalah pengaturan/koordinasi server. Jangan menulis asumsi kuota tetap atau menjanjikan X-RateLimit/Retry-After pada semua error. Retry baca harus terbatas dan berhenti pada 401/403/404 yang tidak bisa dipulihkan otomatis.