Dokumentasi API Pembayaran
Terima pembayaran lewat Virtual Account dan QRIS dengan satu antarmuka. Seluruh contoh pada halaman ini dapat langsung dicoba memakai kunci sandbox tanpa melibatkan dana sungguhan.
Mulai cepat
Tiga langkah sampai pembayaran pertama Anda terbentuk.
- Daftar dan terbitkan kunci sandbox dari halaman ringkasan.
- Kirim permintaan pembuatan pembayaran seperti contoh di bawah.
- Daftarkan alamat notifikasi agar sistem Anda tahu ketika pelanggan sudah membayar.
curl -X POST http://localhost:3040/api/v1/payments \
-H "Authorization: Bearer lg_sandbox_kunci_anda" \
-H "Idempotency-Key: PESANAN-0001" \
-H "Content-Type: application/json" \
-d '{
"reference_id": "PESANAN-0001",
"amount": 150000,
"method": { "type": "VIRTUAL_ACCOUNT", "channel": "BNI" }
}'lg_sandbox_ memakai lingkungan uji penyedia pembayaran. Nomor Virtual Account yang terbentuk sungguhan, tetapi tidak ada dana yang berpindah.Autentikasi
Setiap permintaan menyertakan kunci pada tajuk Authorization.
Authorization: Bearer lg_sandbox_a1b2c3d4_xxxxxxxxxxxxxxxx Idempotency-Key: PESANAN-0001 Content-Type: application/json
| Awalan | Lingkungan | Keterangan |
|---|---|---|
lg_sandbox_ | Sandbox | Untuk mencoba integrasi, tanpa dana sungguhan |
lg_live_ | Produksi | Memproses dana sungguhan |
Kanal pembayaran
Daftar kanal yang benar-benar dapat Anda pakai, sudah dikelompokkan dan mengikuti lingkungan kunci yang dipakai. Gunakan ini untuk menyusun pilihan pembayaran, agar Anda tidak menawarkan kanal yang akan ditolak saat tagihan dibuat.
curl https://api.langsunggas.example/api/v1/channels \ -H "Authorization: Bearer lg_sk_test_xxxxxxxx"
{
"environment": "SANDBOX",
"total": 9,
"data": [
{
"group": "VIRTUAL_ACCOUNT",
"group_name": "Virtual Account",
"channels": [
{
"code": "BNI",
"name": "Bank BNI",
"method_type": "VIRTUAL_ACCOUNT",
"description": "Virtual account nominal terkunci",
"available": true
}
]
},
{
"group": "QR",
"group_name": "QRIS",
"channels": [
{
"code": "QRIS",
"name": "QRIS",
"method_type": "QRIS",
"description": "Pindai kode dari aplikasi apa pun yang mendukung QRIS",
"available": true
}
]
}
]
}| Kelompok | Isi |
|---|---|
VIRTUAL_ACCOUNT | Transfer ke nomor virtual account bank |
QR | QRIS |
OVER_THE_COUNTER | Bayar tunai di minimarket |
EWALLET | E-Money |
PAYLATER | Bayar nanti |
Kolom code adalah nilai yang Anda kirim padamethod.channel, sedangkan method_typeadalah nilai untuk method.type.
?include_disabled=true bila Anda perlu melihat kanal yang sedang mati beserta alasannya.Membuat pembayaran
Virtual Account:
{
"reference_id": "PESANAN-2026-0001",
"amount": 275000,
"method": { "type": "VIRTUAL_ACCOUNT", "channel": "BNI" },
"customer": { "name": "Budi Santoso" }
}QRIS:
{
"reference_id": "PESANAN-2026-0002",
"amount": 45000,
"method": { "type": "QRIS" }
}Jawaban berhasil dengan kode 201:
{
"id": "pay_xxxxxxxxxxxx",
"reference_id": "PESANAN-2026-0001",
"status": "PENDING",
"amount": 275000,
"currency": "IDR",
"method": {
"type": "VIRTUAL_ACCOUNT",
"channel": "BNI",
"accountNumber": "8808999979278987",
"accountName": "XDT-InoPay"
},
"expires_at": "2026-08-21T03:59:21.317Z",
"created_at": "2026-08-21T02:59:21.631Z"
}| Kolom | Wajib | Keterangan |
|---|---|---|
reference_id | Ya | Nomor pesanan Anda. Harus unik untuk setiap tagihan. |
amount | Ya | Nominal dalam rupiah penuh, tanpa desimal. |
method.type | Ya | VIRTUAL_ACCOUNT atau QRIS. |
method.channel | Untuk VA | Kode bank dari GET /api/v1/channels, misalnya BNI, BRI, MANDIRI. |
expires_at | Tidak | Waktu kedaluwarsa. Bawaannya enam puluh menit. |
customer | Tidak | Nama dan surel pelanggan. |
metadata | Tidak | Data tambahan milik Anda, dikembalikan apa adanya. |
Idempotensi
Tajuk Idempotency-Key wajib disertakan pada setiap pembuatan pembayaran. Tajuk ini mencegah tagihan ganda ketika jaringan bermasalah dan permintaan yang sama terkirim dua kali.
| Keadaan | Yang terjadi |
|---|---|
| Kunci sama, isi sama | Jawaban pertama dikembalikan. Tidak ada pembayaran baru. |
| Kunci sama, isi berbeda | Ditolak dengan kode 409. |
| Kunci berbeda | Pembayaran baru dibuat. |
Membaca pembayaran
Daftar memakai penomoran berbasis kursor. Sertakan nilainext_cursor dari jawaban sebelumnya untuk mengambil halaman berikutnya.
{
"data": [
{
"id": "pay_xxxxxxxxxxxx",
"status": "PAID",
"amount": 275000,
"paid_at": "2026-08-21T03:01:04Z"
}
],
"has_more": true,
"next_cursor": "cmt2xxxxxxxxxx"
}Notifikasi
Ketika status pembayaran berubah, kami mengirim pemberitahuan ke alamat yang Anda daftarkan pada halaman Notifikasi.
{
"id": "evt_xxxxxxxxxxxx",
"type": "payment.paid",
"created_at": "2026-08-21T03:01:05Z",
"livemode": true,
"data": {
"payment": {
"id": "pay_xxxxxxxxxxxx",
"reference_id": "PESANAN-2026-0001",
"status": "PAID",
"amount": 275000,
"paid_at": "2026-08-21T03:01:04Z"
}
}
}livemode bernilai benar hanya pada pembayaran dengan dana sungguhan. Periksa kolom ini sebelum memenuhi pesanan, agar pembayaran uji tidak pernah dianggap lunas.| Tajuk | Isi |
|---|---|
X-Webhook-Id | Penanda unik pengiriman |
X-Webhook-Timestamp | Waktu penandatanganan dalam milidetik |
X-Webhook-Signature | Tanda tangan HMAC SHA-256 |
X-Webhook-Attempt | Nomor percobaan pengiriman |
Pengiriman dianggap berhasil hanya bila sistem Anda menjawab dengan kode 2xx dalam sepuluh detik. Bila gagal, pengiriman diulang dengan jeda menaik: satu menit, lima menit, tiga puluh menit, dua jam, delapan jam, lalu dua puluh empat jam.
Verifikasi tanda tangan
Contoh dengan Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
// Ambil isi mentah, jangan diuraikan lebih dulu.
const rawBody = await request.text();
const timestamp = request.headers.get("x-webhook-timestamp");
const signature = request.headers.get("x-webhook-signature");
// Tolak notifikasi lama untuk mencegah pengiriman ulang.
if (Math.abs(Date.now() - Number(timestamp)) / 1000 > 300) {
return new Response(null, { status: 400 });
}
const diharapkan = createHmac("sha256", RAHASIA_ANDA)
.update(timestamp + "." + rawBody)
.digest("hex");
const a = Buffer.from(diharapkan);
const b = Buffer.from(signature);
if (a.length !== b.length || timingSafeEqual(a, b) === false) {
return new Response(null, { status: 400 });
}
const event = JSON.parse(rawBody);
return new Response(null, { status: 200 });Contoh dengan PHP:
<?php
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
if (abs(round(microtime(true) * 1000) - (int)$timestamp) / 1000 > 300) {
http_response_code(400);
exit;
}
$diharapkan = hash_hmac('sha256', $timestamp . '.' . $rawBody, RAHASIA_ANDA);
if (!hash_equals($diharapkan, $signature)) {
http_response_code(400);
exit;
}
$event = json_decode($rawBody, true);
http_response_code(200);Status pembayaran
CREATED -> PENDING -> PAID
|
+--> EXPIRED / FAILED / CANCELLED| Status | Arti |
|---|---|
CREATED | Tagihan dibuat, menunggu penyedia |
PENDING | Siap dibayar pelanggan |
PAID | Sudah dibayar |
EXPIRED | Melewati batas waktu |
FAILED | Gagal dibuat |
CANCELLED | Dibatalkan |
Kode kesalahan
Seluruh kesalahan memakai bentuk yang sama.
{
"error": {
"code": "INVALID_AMOUNT",
"message": "Nominal di luar batas yang diizinkan",
"request_id": "req_xxxxxxxx",
"details": []
}
}| HTTP | Kode | Arti |
|---|---|---|
| 401 | UNAUTHORIZED | Kunci tidak ada atau tidak sah |
| 403 | FORBIDDEN | Cakupan kunci tidak mencukupi |
| 403 | MERCHANT_NOT_ACTIVE | Akun belum aktif atau dihentikan |
| 400 | VALIDATION_ERROR | Isi permintaan tidak sesuai |
| 400 | INVALID_AMOUNT | Nominal di luar batas |
| 409 | IDEMPOTENCY_CONFLICT | Kunci idempotensi dipakai dengan isi berbeda |
| 404 | RESOURCE_NOT_FOUND | Data tidak ditemukan |
| 429 | RATE_LIMITED | Permintaan terlalu sering |
| 502 | PROVIDER_UNAVAILABLE | Penyedia sedang bermasalah |
| 502 | PROVIDER_STATE_UNKNOWN | Status belum dapat dipastikan |
Batas permintaan
| Jenis | Batas |
|---|---|
| Membuat pembayaran | 60 per menit untuk setiap kunci |
| Membaca data | 300 per menit untuk setiap kunci |
Melampaui batas menghasilkan kode 429. Tunggu sesuai tajukRetry-After sebelum mencoba lagi.
Praktik yang disarankan
- Simpan nilai
idyang kami kembalikan, bukan hanya nomor pesanan Anda sendiri. - Sediakan pemeriksaan berkala sebagai cadangan bila notifikasi tidak kunjung tiba, misalnya karena peladen Anda sempat mati.
- Catat setiap notifikasi yang masuk beserta hasil pemeriksaan tanda tangannya, agar penelusuran masalah lebih mudah.
- Uji seluruh alur pada lingkungan sandbox, termasuk keadaan gagal, sebelum beralih ke produksi.