Panduan lengkap integrasi sistem pembayaran QRIS GS SNAP untuk backend PHP dan JavaScript (Node.js & Frontend)
Semua request API dari server toko/member Anda ke Payment Hub wajib menyertakan 3 header keamanan utama untuk verifikasi identitas dan integritas data:
| Nama Header | Tipe Data | Wajib? | Keterangan |
|---|---|---|---|
X-API-KEY |
String |
Wajib | Kunci API toko Anda. Gunakan mk_test_... untuk Sandbox (Uji Coba) atau mk_live_... untuk Produksi.
|
X-TIMESTAMP |
String |
Wajib | Waktu saat request dikirim dalam format ISO 8601 UTC (contoh: 2026-09-18T10:30:00Z). |
X-SIGNATURE |
String |
Wajib | Signature HMAC-SHA256 dari teks {X-TIMESTAMP}:{RAW_JSON_BODY} menggunakan
SECRET_KEY yang sesuai (gunakan sk_test_... saat mode sandbox atau sk_live_... saat live).
|
Content-Type |
String |
Wajib | Wajib bernilai application/json. |
Mode Sandbox memungkinkan Anda menguji seluruh alur integrasi pembayaran QRIS (pembuatan QRIS, tampilan di checkout toko, dan pengiriman notifikasi Webhook) tanpa perlu memotong uang nyata.
mk_test_xxxxxxsk_test_xxxxxxmk_live_xxxxxxsk_live_xxxxxx
Ketika transaksi QRIS dibuat dengan mk_test_..., pada halaman tampilan QRIS pembeli (/api/v1/payment/render-qr/:reference_no) akan muncul banner kuning MODE SANDBOX dan tombol โก Simulasi Bayar Lunas. Mengklik tombol tersebut akan langsung melunasi transaksi dan menembakkan notifikasi Webhook ke server toko Anda secara instan.
Endpoint ini dipanggil dari backend toko Anda saat pembeli memilih pembayaran QRIS di checkout.
| Parameter Body | Tipe | Wajib? | Deskripsi |
|---|---|---|---|
partner_reference_no |
string |
Ya | ID Order/Invoice unik dari database toko Anda (misal: ORD-100234). |
amount |
number |
Ya | Nominal belanja (misal: 50000). |
customer_name |
string |
Opsional | Nama lengkap pembeli (misal: Budi Santoso). |
customer_phone |
string |
Opsional | Nomor WhatsApp / HP pembeli. |
customer_email |
string |
Opsional | Alamat email pembeli. |
keterangan |
string |
Opsional | Catatan pesanan / rincian item produk (misal: "Topup 100 Diamond - ID: 88219"). |
expired_minutes |
number |
Opsional | Masa berlaku QRIS dalam menit (default: 60 menit). |
fee_type |
string |
Opsional | "CUSTOMER" (fee dibebankan ke pembeli) atau "MERCHANT" (fee ditanggung
toko). Default: mengikuti pengaturan toko Anda. |
<?php
/**
* Contoh Pembuatan QRIS di PHP (cURL murni tanpa dependensi)
*/
$serverUrl = "https://pg.gscon.net"; // Ganti dengan URL Payment Hub Anda
$apiKey = "mk_live_xxxxxxxxx"; // Ganti dengan X-API-KEY Toko Anda
$secretKey = "sk_live_xxxxxxxxx"; // Ganti dengan SECRET_KEY Toko Anda
// 1. Susun Data Pesanan
$orderData = [
"partner_reference_no" => "ORD-" . time(), // Order ID unik toko Anda
"amount" => 50000, // Nominal belanja (Rp 50.000)
"customer_name" => "Budi Santoso",
"customer_phone" => "08123456789",
"customer_email" => "[email protected]",
"expired_minutes" => 60,
"fee_type" => "CUSTOMER" // "CUSTOMER" atau "MERCHANT"
];
$jsonBody = json_encode($orderData);
// 2. Format Timestamp ISO-8601 UTC
$timestamp = gmdate("Y-m-d\TH:i:s\Z");
// 3. Buat Signature HMAC-SHA256
$stringToSign = $timestamp . ":" . $jsonBody;
$signature = hash_hmac("sha256", $stringToSign, $secretKey);
// 4. Eksekusi Request cURL
$ch = curl_init("$serverUrl/api/v1/payment/qris");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-KEY: $apiKey",
"X-SIGNATURE: $signature",
"X-TIMESTAMP: $timestamp"
],
CURLOPT_POSTFIELDS => $jsonBody,
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 201 && isset($result['success']) && $result['success']) {
$data = $result['data'];
$invoiceRef = $data['reference_no']; // ID Invoice Gateway (GSN-xxx)
$qrString = $data['qr_content']; // String EMVCo QRIS GS
$qrImageUrl = $data['qr_image_url']; // URL Gambar QRIS Siap Render
$qrDataUrl = $data['qr_data_url']; // Base64 Data Image
$totalAmount = $data['total_amount']; // Total Tagihan Pembeli
echo "โ
QRIS Berhasil Dibuat!\n";
echo "Invoice: $invoiceRef\n";
echo "Total Bayar: Rp " . number_format($totalAmount, 0, ',', '.') . "\n";
echo "URL Gambar QR: $qrImageUrl\n";
} else {
echo "โ Gagal Membuat QRIS: " . ($result['message'] ?? $response) . "\n";
}
?>
Gunakan contoh kode HTML & JavaScript berikut di halaman checkout toko Anda agar otomatis memeriksa status pembayaran setiap 3 detik dan melakukan redirect saat pembayaran lunas.
<!-- Halaman Pembayaran Checkout (checkout.html / bayar.php) -->
<div style="text-align: center; max-width: 400px; margin: 40px auto; padding: 24px; border: 1px solid #ddd; border-radius: 16px; font-family: sans-serif;">
<h3>Scan QRIS untuk Membayar</h3>
<p style="color: #666; font-size: 14px;">Buka aplikasi GoPay, OVO, Dana, BCA, atau Mobile Banking Anda.</p>
<!-- Gambar QRIS dari Payment Hub -->
<div style="background: #fff; padding: 12px; display: inline-block; border-radius: 12px; margin: 16px 0;">
<img id="qris-image" src="https://pg.gscon.net/api/v1/payment/render-qr/GSN-xxxxxx" alt="QRIS" width="220" height="220">
</div>
<h2 id="total-bayar" style="color: #10B981; margin: 0 0 8px;">Rp 50.850</h2>
<div id="payment-status-badge" style="color: #F59E0B; font-weight: 600; font-size: 13px;">โณ Menunggu Pembayaran...</div>
</div>
<script>
const invoiceRef = "GSN-xxxxxx"; // Ganti dengan nomor invoice transaksi
const serverUrl = "https://pg.gscon.net";
// Auto-polling status transaksi setiap 3 detik
const pollInterval = setInterval(async () => {
try {
const res = await fetch(`${serverUrl}/api/v1/payment/status/${invoiceRef}`);
const json = await res.json();
if (json.success && json.data.status === 'PAID') {
clearInterval(pollInterval);
document.getElementById('payment-status-badge').innerText = 'โ
Pembayaran Berhasil!';
document.getElementById('payment-status-badge').style.color = '#10B981';
alert('๐ Terima kasih! Pembayaran Anda telah diterima.');
window.location.href = '/success.html'; // Redirect ke halaman sukses toko Anda
}
} catch (err) {
console.error('Polling error:', err);
}
}, 3000);
</script>
Ketika pelanggan menyelesaikan pembayaran QRIS, Payment Hub akan mengirimkan HTTP POST
secara instan ke callback_url toko Anda. Wajib verifikasi signature untuk memastikan data valid dan
tidak dimanipulasi.
Bagaimana toko Anda mengetahui pesanan mana yang dibayar saat notifikasi webhook diterima:
| Field Payload | Tipe | Fungsi Identifikasi di Sistem Toko |
|---|---|---|
partner_reference_no |
string |
KUNCI UTAMA TOKO: Nomor Invoice / Order ID yang dikirim toko Anda saat membuat
QRIS. Gunakan field ini untuk WHERE order_id = ... di database toko. |
reference_no |
string |
ID GATEWAY: Nomor unik transaksi dari Payment Hub (contoh:
GSN-1789719317036-777). Simpan sebagai referensi bukti gateway.
|
status |
string |
Bernilai "PAID" jika pembayaran berhasil diselesaikan oleh pembeli. |
customer |
object |
IDENTITAS PEMBELI & KETERANGAN: Detail data pembeli dan catatan pesanan untuk
identifikasi & notifikasi otomatis:
|
net_amount |
number |
Dana bersih yang masuk ke saldo toko Anda setelah potongan biaya QRIS. |
total_amount |
number |
Total nominal yang dibayarkan oleh pembeli di aplikasi e-wallet / mobile banking. |
200 OK dan JSON {"status":"OK"}. Jika server toko merespon selain
200 (misal timeout atau error 500), Payment Hub akan melakukan retry otomatis hingga 3 kali.
<?php
/**
* File: webhook.php (Ditaruh di server toko Anda, misal: https://tokoanda.com/webhook.php)
*/
$secretKey = "sk_live_xxxxxxxxx"; // Ganti dengan SECRET_KEY Toko Anda
// 1. Ambil Raw Payload JSON dan Headers
$rawPayload = file_get_contents('php://input');
$headers = getallheaders();
$incomingTimestamp = $headers['X-TIMESTAMP'] ?? $headers['x-timestamp'] ?? '';
$incomingSignature = $headers['X-SIGNATURE'] ?? $headers['x-signature'] ?? '';
// 2. Hitung Expected Signature HMAC-SHA256
$stringToSign = $incomingTimestamp . ":" . $rawPayload;
$expectedSignature = hash_hmac('sha256', $stringToSign, $secretKey);
// 3. Verifikasi Keaslian Notifikasi
if (!hash_equals($expectedSignature, $incomingSignature)) {
http_response_code(401);
echo json_encode(["status" => "ERROR", "message" => "Invalid Signature"]);
exit;
}
// 4. Decode Data Notifikasi
$data = json_decode($rawPayload, true);
if ($data && $data['event'] === 'PAYMENT_SUCCESS' && $data['status'] === 'PAID') {
$orderId = $data['partner_reference_no']; // ID Order toko Anda
$invoiceRef = $data['reference_no']; // ID Invoice Gateway
$amount = $data['amount']; // Nominal asli belanja
$feeAmount = $data['fee_amount']; // Biaya fee gateway
$feeChargedTo = $data['fee_charged_to']; // "CUSTOMER" atau "MERCHANT"
$netAmount = $data['net_amount']; // Bersih yang diterima toko
$paymentDate = $data['payment_date'];
// DATA IDENTITAS PEMBELI & KETERANGAN PESANAN
$customerName = $data['customer']['name'] ?? 'Pelanggan';
$customerPhone = $data['customer']['phone'] ?? '';
$customerEmail = $data['customer']['email'] ?? '';
$customerKeterangan = $data['customer']['keterangan'] ?? '';
// 5. UPDATE STATUS ORDER DI DATABASE TOKO ANDA
// Contoh query database toko Anda:
// mysqli_query($conn, "UPDATE orders SET status = 'PAID', note = '$customerKeterangan', payment_date = NOW() WHERE order_id = '$orderId'");
// 6. Wajib Balas HTTP 200 OK ke Payment Hub
http_response_code(200);
echo json_encode([
"status" => "OK",
"message" => "Webhook processed successfully",
"order_id" => $orderId,
"received_at" => date('Y-m-d H:i:s')
]);
exit;
}
http_response_code(400);
echo json_encode(["status" => "IGNORED", "message" => "Event is not PAYMENT_SUCCESS"]);
?>