Developer · API
Pembayaran QRIS
Buat dan pulihkan satu top-up aktif tanpa menggandakan pembayaran atau mengkreditkan saldo sebelum konfirmasi provider.
Unduh OpenAPI 3.1Minimum top-up diatur admin (default Rp 10.000), dengan maksimum Rp 1.000.000. Baca limits.min dari GET /api/payments. Perubahan minimum hanya berlaku untuk pembayaran baru; retry dengan kunci yang sudah tersimpan tetap memulihkan pembayaran semula. UI polling setiap 5 detik selama CREATING/PENDING. Hanya status PAID terkonfirmasi yang menambah saldo; totalAmount adalah nominal bayar, requestedAmount adalah nominal kredit. feeAmount dan uniqueAmount bukan saldo. Tagihan dibuat melalui mitra QRIS yang dipilih server. Perubahan mitra hanya berlaku untuk tagihan baru, tanpa failover otomatis saat timeout. limits.min juga memperhitungkan batas mitra pembayaran. Endpoint stabil /api/payments tidak bergantung pada mitra.
Endpoint, field, dan status HTTP mengikuti implementasi. Nilai ID, nomor, OTP, waktu, saldo, serta quote di contoh adalah ilustrasi, bukan data akun atau pilihan yang bisa langsung dibeli. Ganti dengan data dari instalasimu. Halaman ini tidak mengirim request atau menjalankan transaksi.
JavaScript dijalankan pada origin aplikasi setelah login. Contoh cURL memakai PING_ORIGIN dan cookie jar PING_COOKIE_JAR dari panduan autentikasi. Sukses memakai { ok: true, data }; create juga HTTP 200, bukan 201. Semua endpoint di halaman ini memerlukan sesi aktif dan dapat mengembalikan 401, 403, atau 500.
/api/paymentsSession cookieMembaca pembayaran CREATING/PENDING terakhir dari database dan batas top-up efektif. Bentuk data adalah { payment: Payment | null, limits: { min, max } }, bukan Payment langsung. Gunakan limits.min untuk nominal baru dan endpoint detail untuk pemeriksaan provider.
const response = await fetch("/api/payments", {
credentials: "same-origin",
cache: "no-store",
});
const body = await response.json();
if (!response.ok || !body.ok) {
throw Object.assign(
new Error(body.error?.message ?? "Request gagal"),
{ code: body.error?.code, status: response.status },
);
}
const data = body.data;/api/paymentsSession cookieMembuat top-up QRIS idempotent melalui mitra pembayaran yang dipilih server. Biaya tambahan pemrosesan tidak dikreditkan ke saldo. Callback diverifikasi oleh server sebelum status transaksi diperbarui. Tidak ada failover otomatis saat respons hilang. Satu QRIS terbaru per akun. Kirim replacePaymentId untuk mengganti pembayaran PENDING dengan nominal baru tanpa menunggu expiry. QR lama tidak dibatalkan provider dan pembayaran masuk tetap diverifikasi. CREATING tidak boleh diganti. Tanpa replacePaymentId, pembayaran aktif dengan nominal sama dapat dikembalikan. QRIS baru (termasuk pengganti) dibatasi 4 per 10 menit dan 12 per jam per akun, serta 12 per 10 menit dan 30 per jam per jaringan; respons RATE_LIMITED menyertakan Retry-After.
Menjalankan request ini membuat transaksi pembayaran nyata pada instalasi yang terhubung provider. Respons hilang tidak boleh memicu pembuatan transaksi baru; pulihkan melalui GET pembayaran aktif/detail.
| Body JSON | Tipe | Wajib | Keterangan |
|---|---|---|---|
| amount | integer | Ya | Rupiah utuh, maksimum 1000000. Minimum efektif ditentukan admin; baca limits.min dari GET /api/payments sebelum membuat pembayaran baru. |
| clientRequestId | string · uuid | Ya | UUID untuk satu top-up. Jangan ganti UUID karena respons jaringan hilang. |
| replacePaymentId | string · uuid | Tidak | Opsional: ID pembayaran PENDING milik akun yang hendak diganti. Membuat QR baru tanpa menunggu expiry; tidak membatalkan QR lama di provider. |
const response = await fetch("/api/payments", {
method: "POST",
credentials: "same-origin",
cache: "no-store",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
"amount": 10000,
"clientRequestId": "f41c29d8-967d-469d-ae4f-0114c38bf715"
}),
});
const body = await response.json();
if (!response.ok || !body.ok) {
throw Object.assign(
new Error(body.error?.message ?? "Request gagal"),
{ code: body.error?.code, status: response.status },
);
}
const data = body.data;| Kode error | HTTP | Tindakan |
|---|---|---|
| VALIDATION_ERROR | 400 | Periksa tipe, field wajib, format UUID, dan field tambahan. |
| IDEMPOTENCY_CONFLICT | 409 | Pulihkan transaksi awal dengan payload yang sama. UUID baru hanya untuk niat transaksi baru setelah hasil awal terkonfirmasi. |
| CONFLICT | 409 | Baca pembayaran aktif, jangan terus membuat top-up baru. Untuk konflik profil, pilih username lain; kapitalisasi berbeda tetap dianggap username yang sama. |
| RATE_LIMITED | 429 | Hormati Retry-After sebelum meminta ulang. Jangan retry pembelian otomatis atau membuat clientRequestId baru saat hasil awal belum pasti. |
| PAYMENT_NOT_CONFIGURED | 503 | Pengelola memeriksa konfigurasi mitra pembayaran di server. Perubahan konfigurasi hanya menentukan tagihan baru; tagihan tersimpan tetap diperiksa melalui mitra asalnya. |
| PAYMENT_PROVIDER_ERROR | 502 | Baca pembayaran aktif atau detail dengan ID yang sama. |
/api/payments/{id}Session cookieMembaca pembayaran milik sesi dan melakukan poll-through terbatas untuk pembayaran aktif. UI polling 5 detik, sedangkan klaim pemeriksaan provider dibatasi sekitar 10 detik. Respons langsung Payment, bukan { payment }. Pemeriksaan selalu memakai provider tagihan yang tersimpan: mitra pembayaran asal tagihan. Perubahan konfigurasi tidak memindahkan tagihan yang sudah dibuat.
| Parameter | Tipe | Wajib | Keterangan |
|---|---|---|---|
| id (path) | string · uuid | Ya | UUID pembayaran milik pengguna bersesi. |
const response = await fetch("/api/payments/5d81627b-4c2b-4cab-bd07-2a5ed675fe5f", {
credentials: "same-origin",
cache: "no-store",
});
const body = await response.json();
if (!response.ok || !body.ok) {
throw Object.assign(
new Error(body.error?.message ?? "Request gagal"),
{ code: body.error?.code, status: response.status },
);
}
const data = body.data;| Kode error | HTTP | Tindakan |
|---|---|---|
| VALIDATION_ERROR | 400 | Periksa tipe, field wajib, format UUID, dan field tambahan. |
| NOT_FOUND | 404 | Periksa ID dan akun; ID resource tidak memberi akses lintas pengguna. |
| PAYMENT_PROVIDER_ERROR | 502 | Baca pembayaran aktif atau detail dengan ID yang sama. |
| CONFLICT | 409 | Baca pembayaran aktif, jangan terus membuat top-up baru. Untuk konflik profil, pilih username lain; kapitalisasi berbeda tetap dianggap username yang sama. |
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
| id | string · uuid | Ya | Sesuai tipe yang ditampilkan. |
| provider | qris | Ya | Metode pembayaran publik, bukan identitas mitra pemroses. |
| requestedAmount | integer | Ya | Jumlah top-up yang dikreditkan jika PAID terkonfirmasi. Untuk QRIS bisa 1.000 atau 2.000 lebih besar dari amount saat antrean QRIS ramai; selisih itu ikut dikreditkan. |
| totalAmount | integer | null | Ya | Total yang harus dibayar ke provider; bisa berbeda dari requestedAmount. |
| feeAmount | integer | null | Ya | Biaya admin dalam rupiah jika disediakan terpisah, bukan kredit saldo; null jika tidak diketahui. Biaya berbentuk persentase tidak dianggap nominal rupiah. |
| uniqueAmount | integer | null | Ya | Kode unik dalam rupiah jika disediakan terpisah, bukan kredit saldo; null jika tidak diketahui. |
| status | CREATING | PENDING | PAID | EXPIRED | FAILED | Ya | Sesuai tipe yang ditampilkan. |
| qrisUrl | string | null | Ya | Materi QR dari provider; tersedia hanya untuk pembayaran aktif. |
| qrisImage | string | null | Ya | Sesuai tipe yang ditampilkan. |
| paymentUrl | string | null | Ya | Sesuai tipe yang ditampilkan. |
| expiresAt | string | null | Ya | Timestamp provider tanpa zona waktu, disimpan apa adanya. Bukan waktu ISO UTC. |
| createdAt | string · date-time | Ya | ISO 8601 dengan zona waktu. |
| paidAt | string | null · date-time | Ya | ISO 8601 dengan zona waktu. |
| supersededAt | string | null · date-time | Ya | Waktu QR diganti. Bukan pembatalan provider; callback PAID tetap diverifikasi dan dikreditkan sekali. |
| message | string | null | Ya | Sesuai tipe yang ditampilkan. |
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
| payment | Payment | null | Ya | Pembayaran aktif, atau null jika tidak ada. |
| limits | object | Ya | Sesuai tipe yang ditampilkan. |