Public API Docs
QRIS SaaS API Documentation
Dokumentasi singkat untuk integrasi payment QRIS, API token, webhook notifikasi transaksi, dan operasional merchant.
Base URL
Gunakan APP_BASE_URL atau NEXT_PUBLIC_API_BASE_URL. Default local: http://localhost:3000
Authentication
Endpoint merchant dan developer memakai session login, dan integrasi server-to-server memakai header X-API-Token.
Generate QRIS
POST /api/payment/qris/generate dengan body amount, payment_reference opsional, dan return_url opsional.
Payment Status
GET /api/payment/qris/{qrisId}/status untuk memeriksa pending, paid, expired, atau cancelled.
Cancel QRIS
POST /api/payment/qris/{qrisId}/cancel untuk membatalkan QRIS yang masih pending.
Transactions & Balance
GET /api/payment/transactions dan GET /api/payment/balance untuk melihat histori transaksi dan saldo merchant.
Merchant Profile
GET/PUT /api/merchant/profile untuk mengatur merchantName, merchantCity, qrisStatic, dan webhookUrl.
Developer Tools
GET/POST /api/developer/tokens, DELETE /api/developer/tokens/{id}, dan POST /api/developer/webhook/test.
Contoh Header
Content-Type: application/json X-API-Token: cki_your_token_here
Contoh Response Generate QRIS
{
"status": "success",
"data": {
"qris_id": "abc-123",
"amount": 50000,
"qris_image_url": "http://localhost:3000/api/public/qris/abc-123/image",
"expires_in_seconds": 900
}
}cURL
Contoh request untuk generate QRIS dinamis.
curl -X POST https://qris.commitflow.space/api/payment/qris/generate -H "X-API-Token: YOUR_API_TOKEN" -H "Content-Type: application/json" -d '{
"amount": 50000,
"payment_reference": "Order-123",
"return_url": "https://yoursite.com/thanks"
}'JavaScript
Contoh integrasi fetch untuk generate QRIS, cek status, dan cancel.
const API_TOKEN = 'YOUR_API_TOKEN';
const API_URL = 'https://qris.commitflow.space';
async function generateQRIS(amount, paymentRef = null, returnUrl = null) {
const body = { amount };
if (paymentRef) body.payment_reference = paymentRef;
if (returnUrl) body.return_url = returnUrl;
const response = await fetch(`https://qris.commitflow.space/api/payment/qris/generate`, {
method: 'POST',
headers: {
'X-API-Token': API_TOKEN,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
return await response.json();
}
async function checkPaymentStatus(qrisId) {
const response = await fetch(`https://qris.commitflow.space/api/payment/qris/${qrisId}/status`, {
headers: { 'X-API-Token': API_TOKEN }
});
return await response.json();
}
async function cancelQRIS(qrisId) {
const response = await fetch(`https://qris.commitflow.space/api/payment/qris/${qrisId}/cancel`, {
method: 'POST',
headers: { 'X-API-Token': API_TOKEN }
});
return await response.json();
}PHP
Contoh integrasi server-side dengan cURL.
<?php
define('API_TOKEN', 'YOUR_API_TOKEN');
define('API_URL', 'https://qris.commitflow.space');
function generateQRIS($amount, $paymentRef = null, $returnUrl = null) {
$url = API_URL . '/api/payment/qris/generate';
$data = ['amount' => $amount];
if ($paymentRef) {
$data['payment_reference'] = $paymentRef;
}
if ($returnUrl) {
$data['return_url'] = $returnUrl;
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Token: ' . API_TOKEN
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}Python
Contoh integrasi memakai requests.
import requests
API_TOKEN = 'YOUR_API_TOKEN'
API_URL = 'https://qris.commitflow.space'
def generate_qris(amount, payment_ref=None, return_url=None):
url = f'{API_URL}/api/payment/qris/generate'
headers = {
'X-API-Token': API_TOKEN,
'Content-Type': 'application/json'
}
data = {'amount': amount}
if payment_ref:
data['payment_reference'] = payment_ref
if return_url:
data['return_url'] = return_url
response = requests.post(url, json=data, headers=headers)
return response.json()
result = generate_qris(50000, 'Order-123', 'https://yoursite.com/thanks')
print(result['data']['qris_image_url'])🛡️ Webhook Security & Signature Verification
Setiap kali QRISPAY mengirimkan event webhook ke server Anda, request tersebut akan disertai dengan header keamanan bernama X-Qrispay-Signature. Gunakan header ini dan Webhook Secret unik Anda (dapat dilihat di Merchant Profile atau Developer Menu) untuk memverifikasi keaslian payload webhook dan mencegah pengiriman data palsu (fake webhooks).
Format Header Signature:
X-Qrispay-Signature: t=1672531199,v1=f6a9c7b8d8...
- t: Unix timestamp dalam satuan detik saat webhook dikirim. Periksa ini di server Anda untuk mencegah replay attacks (mis. tolak jika selisih waktu > 5 menit).
- v1: Tanda tangan hash HMAC-SHA256 yang dibuat dengan memformulasikan data
${timestamp}.${payloadString}menggunakan Webhook Secret Anda.
Contoh Kode Verifikasi (Node.js / Express):
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook/callback', (req, res) => {
const signatureHeader = req.headers['x-qrispay-signature'];
const WEBHOOK_SECRET = 'YOUR_WEBHOOK_SECRET'; // whsec_...
if (!signatureHeader) {
return res.status(400).send('Missing signature header');
}
const parts = signatureHeader.split(',');
const timestampPart = parts.find(p => p.startsWith('t='));
const signaturePart = parts.find(p => p.startsWith('v1='));
if (!timestampPart || !signaturePart) {
return res.status(400).send('Invalid signature format');
}
const timestamp = timestampPart.split('=')[1];
const receivedSignature = signaturePart.split('=')[1];
const diffInMinutes = Math.abs(Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)) / 60;
if (diffInMinutes > 5) {
return res.status(400).send('Request expired / replay attack suspected');
}
const payloadString = JSON.stringify(req.body);
const signaturePayload = `${timestamp}.${payloadString}`;
const computedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(signaturePayload)
.digest('hex');
if (computedSignature === receivedSignature) {
console.log('✅ Webhook terverifikasi asli!');
res.status(200).json({ received: true });
} else {
console.log('❌ Webhook palsu / signature tidak cocok!');
res.status(401).send('Unauthorized signature');
}
});