Satu integrasi buat terima QRIS, VA, e-wallet, sampai retail. Cuma 3 endpoint + 1 callback, beres.
https://risapay.xfazrin.my.id/Lima langkah dari nol sampai menerima pembayaran.
① Daftar, terus ambil api_key, merchant_code (misal R39), private_key di dashboard.
② Hit POST api/transaction/create, simpan data.reference.
③ Tampilkan QR / nomor VA / link bayar ke pelanggan - atau arahkan ke {base}checkout/?page={reference}.
④ Siapin endpoint callback: verify signature → match nominal → mark paid → balas 200.
⑤ Polling/rekon berkala via GET api/transaction/detail.
Satu header untuk semua request.
Authorization: Bearer <api_key>Regenerate key di dashboard = key lama auto mati. Key kosong/salah → JSON success:false (HTTP 200, bukan 401. Yang 429 cuma rate-limit).
HMAC-SHA256 dari string mentah yang dikirim.
HMAC_SHA256(merchant_code + merchant_ref + amount, private_key)amount adalah string persis seperti dikirim ("50000", bukan angka format). RisaPay match ke semua merchant milikmu buat nemuin merchant_code yang kepake.
Body form-encoded, bukan JSON.
https://risapay.xfazrin.my.id/api/transaction/create| Parameter | Wajib | Keterangan |
|---|---|---|
method | Ya | Kode channel: QRIS, BRIVA, DANA, OVO, … (tabel channel) |
merchant_ref | Ya | Referensi unik versimu per merchant |
amount | Ya | Rupiah > 0, dalam batas min/max channel |
signature | Ya | HMAC bagian 3 |
customer_name | Tidak | Nama pelanggan |
customer_email | Tidak | Default email@example.com |
customer_phone | Tidak | No. HP pelanggan |
return_url | Tidak | Default https://example.com/ |
callback_url | Tidak | Override callback per-transaksi |
expired_time | Tidak | Unix timestamp; default VA 24 jam, lainnya 48 jam |
order_items | Tidak | Rincian item belanja |
PHP$merchantCode = 'R39';
$merchantRef = 'INV-2026-0001';
$amount = '50000';
$signature = hash_hmac('sha256', $merchantCode . $merchantRef . $amount, $privateKey);
$ch = curl_init('https://risapay.xfazrin.my.id/api/transaction/create');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
CURLOPT_POSTFIELDS => http_build_query([
'method' => 'QRIS', 'merchant_ref' => $merchantRef, 'amount' => $amount,
'customer_name' => 'Budi', 'customer_email' => 'budi@mail.com',
'customer_phone' => '081234567890', 'signature' => $signature,
]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
$reference = $res['data']['reference']; // SIMPAN - kunci segalanyaNode.jsconst crypto = require('crypto');
const merchantCode = 'R39', merchantRef = 'INV-2026-0001', amount = '50000';
const signature = crypto.createHmac('sha256', process.env.RISAPAY_PRIVATE_KEY)
.update(merchantCode + merchantRef + amount).digest('hex');
const body = new URLSearchParams({ method: 'QRIS', merchant_ref: merchantRef,
amount, customer_name: 'Budi', signature });
const r = await fetch('https://risapay.xfazrin.my.id/api/transaction/create', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + process.env.RISAPAY_API_KEY },
body });
const res = await r.json();
const reference = res.data.reference; // SIMPANcURLcurl -X POST "https://risapay.xfazrin.my.id/api/transaction/create" \
-H "Authorization: Bearer API_KEY_KAMU" \
--data-urlencode "method=QRIS" \
--data-urlencode "merchant_ref=INV-2026-0001" \
--data-urlencode "amount=50000" \
--data-urlencode "customer_name=Budi" \
--data-urlencode "signature=HASH_HMAC_KAMU"VALID - Respons QRIS - HTTP 200{
"success": true,
"message": "Successfully generate transaction",
"data": {
"reference": "R1700000000XXXXX",
"merchant_ref": "INV-2026-0001",
"payment_method": "QRIS",
"amount": 50000,
"fee_merchant": 70,
"fee_customer": 0,
"total_fee": 70,
"amount_received": 49930,
"pay_code": null,
"pay_url": null,
"status": "UNPAID",
"expired_time": 1700172800,
"qr_string": "00020101..."
}
}
QRIS → render qr_string jadi QR · VA → tampilin pay_code · e-wallet → redirect ke pay_url · atau lempar semua ke {base}checkout/?page={reference}.
VALID - Respons VA - field yang beda{
"success": true,
"data": {
"reference": "R1700000000XXXXX",
"payment_method": "BRIVA",
"payment_name": "BRI Virtual Account",
"amount": 50000,
"fee_merchant": 3250,
"amount_received": 46750,
"pay_code": "88810123456789",
"status": "UNPAID",
"expired_time": 1700086400
}
}
GAGAL - HTTP 200{
"success": false,
"message": "Invalid signature"
}
success:false. Satu-satunya HTTP non-200 itu 429 pas kena rate-limit (> 60 req/menit/IP). Full list di section Error.Buat polling & rekon. Cuma transaksi milik api_key itu yang kebaca.
https://risapay.xfazrin.my.id/api/transaction/detail?reference={reference}cURLcurl "https://risapay.xfazrin.my.id/api/transaction/detail?reference=R1700000000XXXXX" \
-H "Authorization: Bearer API_KEY_KAMU"
VALID - HTTP 200{
"success": true,
"message": "Transaction found",
"data": {
"reference": "R1700000000XXXXX",
"merchant_ref": "INV-2026-0001",
"payment_method": "QRIS",
"amount": "10000",
"status": "UNPAID",
"paid_at": null,
"expired_time": "1700172800",
"qr_string": "00020101...",
"checkout_url": "https://..."
}
}
paid_at = unix timestamp pas lunas, null kalau belum. checkout_url itu alias dari pay_url.
GAGAL{ "success": false, "message": "Transaction not found" }
{ "success": false, "message": "Invalid API Key" }
Flow khusus OVO: create dulu, baru push bill ke HP user.
① POST api/transaction/create dengan method=OVO → dapet reference.
② Tanpa Authorization:
https://risapay.xfazrin.my.id/api/ovo-payment/process.phpFormreference=R1700000000XXXXX&ovo_number=0812xxxx
VALID{ "status": "PAID", "message": "Periksa aplikasi OVO Anda" }
GAGAL{ "status": "GAGAL", "message": "Gagal update ke database" }
{ "status": "GAGAL", "message": "Server Error 500 ..." }
Wajib implement. Auto-retry 3x kalau respons bukan 200.
{callback_url} · Content-Type: application/json · X-Callback-Signature · X-Callback-Event: payment_statusVALID - Payload (14 field){
"reference": "R1715094140S41FP",
"merchant_ref": "Q3123660",
"payment_method": "QRIS",
"payment_method_code": "QRIS",
"total_amount": 117,
"fee_merchant": 0,
"fee_customer": 0,
"total_fee": 0,
"amount_received": 117,
"is_closed_payment": 1,
"status": "PAID",
"paid_at": 1715094192,
"note": "Transaksi sukses"
}
Verifikasi - PHP$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_CALLBACK_SIGNATURE'] ?? '';
$calc = hash_hmac('sha256', $raw, $privateKeyAnda);
if (!hash_equals($calc, $sig)) { http_response_code(403); exit; }
$data = json_decode($raw, true);
// cocokkan merchant_ref + total_amount, tandai lunas per $data['reference'] (idempoten!)
http_response_code(200);Verifikasi - Node.js// butuh express.raw({ type: 'application/json' }) agar dapat Buffer mentah
const sig = req.headers['x-callback-signature'] || '';
const calc = crypto.createHmac('sha256', process.env.RISAPAY_PRIVATE_KEY)
.update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(calc), Buffer.from(sig)))
return res.sendStatus(403);
const data = JSON.parse(req.body.toString());
// ... tandai lunas per data.reference (idempoten)
res.sendStatus(200);reference. Non-200 = di-retry (maks 3x, timeout 10 dtk).Komplet, semua message diambil langsung dari code.
| Message | HTTP | Betulkan |
|---|---|---|
Maintenance | 200 | Coba lagi nanti |
Header Authorization tidak ada dalam permintaan | 200 | Header tidak terkirim (cek proxy) |
Invalid Header Authorization/tidak sesuai format | 200 | Harus persis Bearer <key> |
Rate limit terlampaui. Coba lagi sebentar. | 429 | > 60 req/menit/IP - kurangi frekuensi |
Invalid API Key | 200 | Key salah / sudah regenerate |
Invalid parameter permintaan | 200 | Keempatnya hilang: method+ref+amount+signature |
method / merchant_ref / amount / signature tidak ada dalam permintaan | 200 | Parameter tsb tidak dikirim |
Invalid method | 200 | Kode channel salah ketik |
Invalid signature | 200 | Cek rumus, urutan, string mentah & private_key |
merchant_ref sudah digunakan (ref: …, status: …) | 200 | Pakai merchant_ref baru yang unik |
Invalid amount | 200 | Bukan angka / ≤ 0 |
Method tidak aktif | 200 | Channel dimatikan admin |
Minimum payment amount is … | 200 | Di bawah minimum channel |
Maximum payment amount is Rp … | 200 | Di atas maksimum - pecah transaksi |
Server Error … | 200 | Provider hulu error - coba lagi |
Error database, mohon melaporkan pada Admin | 200 | Hubungi RisaPay |
Transaction not found | 200 | Reference bukan milik api_key ini |
Lifecycle satu transaksi.
expired_time default 24 jam (VA) / 48 jam (lainnya), bisa di-override manual. Jangan mark paid dari return page, cuma dari verified callback atau detail = PAID.
22 channel aktif.
| Method | Nama | Min | Max | Fee |
|---|---|---|---|---|
ASTRAPAY |
AstraPay | 1 | 9.800.000 | 2% |
linkaja |
LinkAja | 100 | 10.000.000 | 2% |
qris2x |
QRIS2X | 100 | 20.000.000 | 1% |
shopeepay |
ShopeePay | 100 | 20.000.000 | 2% |
ALFAMART |
Alfamart | 10.000 | 10.000.000 | Rp 3.000 |
AGVA |
Artha Graha Virtual Account | 10.000 | 20.000.000 | Rp 3.000 |
ATMBVA |
ATM Bersama Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
BNCVA |
BNC Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
BNIVA |
BNI Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
BRIVA |
BRI Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
CIMBVA |
CIMB Niaga Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
dana |
Dana | 1 | 9.800.000 | 2% |
MANDIRIVA |
Mandiri Virtual Account | 10.000 | 20.000.000 | Rp 4.250 |
MYBVA |
Maybank Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
ovo |
OVO | 1 | 10.000.000 | 3% |
PERMATAVA |
Permata Virtual Account | 10.000 | 20.000.000 | Rp 3.250 |
qris |
QRIS | 1 | 10.000.000 | 0,7% |
qris2 |
QRIS2 | 1 | 10.000.000 | 0,7% |
qrisc |
QRISC | 1 | 10.000.000 | 0,7% |
qris_shopeepay |
QRIS_SHOPEEPAY | 1 | 10.000.000 | 0,7% |
BCAVA |
BCA Virtual Account | 5.000 | 20.000.000 | Rp 4.500 |
qrisi |
QRISI | 1 | 10.000.000 | 0,7% |
Start tanpa ngetik dari nol.
Import Postman → isi environment base_url, api_key, merchant_code, private_key - signature & merchant_ref auto-generate. Paste risapay-api.md ke AI favoritmu, langsung paham full flow.