Alur integrasi pembayaran
Gunakan API key hanya dari backend website Anda. Browser customer akan diarahkan ke payment_url yang dibuat oleh OtoPay.
payment_url dari backend Anda.
Autentikasi API
Semua request ke /api/v1/* wajib menggunakan API key merchant pada header Authorization.
http://otopay.click
Gunakan URL ini, bukan URL lokal atau URL dashboard. Base URL otomatis mengikuti subfolder instalasi.
oto_pg_••••••••
Dashboard → API & Integrasi. Jangan bagikan key ini kepada customer.
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonPastikan production memakai HTTPS. Endpoint API mengizinkan CORS, tetapi CORS bukan pengganti keamanan API key.
/api/v1/create-invoice
Membuat invoice QRIS
Endpoint ini membuat satu invoice, menghasilkan QRIS dinamis, dan mengembalikan halaman pembayaran yang siap dibuka customer.
Request body
{
"amount": 150000,
"description": "Premium Membership",
"external_id": "ORDER-20260615-001",
"customer_name": "Budi Santoso",
"customer_email": "budi@example.com"
}Response berhasil
{
"success": true,
"data": {
"invoice_id": 42,
"external_id": "ORDER-20260615-001",
"amount": 150000,
"status": "PENDING",
"payment_url": "https://domain-anda.com/pay/42",
"qris_image": "data:image/png;base64,...",
"expires_at": "2026-06-16T10:00:00.000Z",
"created_at": "2026-06-15T10:00:00.000Z"
}
}| Field | Tipe | Keterangan |
|---|---|---|
amount | integer | Wajib. Nominal dalam Rupiah tanpa desimal. Harus lebih dari 0 dan tidak melebihi batas paket. |
description | string | Opsional. Deskripsi yang tampil di halaman pembayaran. |
external_id | string | Opsional. ID order di sistem Anda untuk rekonsiliasi dan webhook. |
customer_name | string | Opsional. Nama customer yang ditampilkan. |
customer_email | string | Opsional. Email customer untuk disimpan pada invoice. |
invoice_id, external_id, dan payment_url di database Anda. Gunakan external_id yang unik dan stabil untuk retry; OtoPay mengembalikan invoice yang sama bila ID tersebut diulang. Jangan membuat invoice baru setiap kali customer me-refresh halaman.
Redirect customer ke halaman pembayaran
Cara paling aman dan kompatibel lintas perangkat adalah mengarahkan customer ke payment_url. Halaman OtoPay sudah menangani QRIS, e-wallet, mobile banking, dan polling status.
// Endpoint di bawah adalah endpoint backend website Anda,
// bukan endpoint OtoPay. Backend sudah menyimpan invoice_id.
const response = await fetch('/api/orders/ORDER-20260615-001/payment-url', {
credentials: 'same-origin'
});
const data = await response.json();
if (!response.ok || !data.payment_url) {
throw new Error(data.error || 'Payment URL tidak tersedia');
}
window.location.assign(data.payment_url);data.qris_image sebagai <img src="...">. Pastikan customer tetap diarahkan ke payment_url atau menggunakan alur status yang benar.
/api/v1/check-status
Cek status pembayaran
Gunakan endpoint ini dari backend atau worker. Panggil secara berkala hanya jika webhook belum tersedia. OtoPay akan mengecek status ke gateway dan menandai invoice sebagai PAID ketika berhasil. Invoice PENDING yang melewati expires_at otomatis berubah menjadi BATAL dan tidak dapat dibayar lagi.
Request
{
"invoice_id": 42
}Pending
{
"success": true,
"data": {
"invoice_id": 42,
"status": "PENDING",
"amount": 150000
}
}Status sudah dibayar
{
"success": true,
"data": {
"invoice_id": 42,
"status": "PAID",
"amount": 150000,
"paid_at": "2026-06-15T10:05:30.000Z"
}
}// Jalankan dari backend/worker, jangan menaruh API key di browser.
const API_BASE = process.env.OTOPAY_BASE_URL;
const API_KEY = process.env.OTOPAY_API_KEY;
const response = await fetch(API_BASE + '/api/v1/check-status', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ invoice_id: 42 })
});
const result = await response.json();
if (result.success && result.data.status === 'PAID') {
// baru fulfils order di sistem Anda
await markOrderAsPaid(result.data.invoice_id, result.data.paid_at);
}status: "PAID" dari webhook atau check-status.
Webhook invoice paid
Aktifkan URL webhook di Dashboard → Settings. Paket merchant harus mendukung webhook. OtoPay mengirim POST JSON ketika invoice berubah menjadi lunas.
{
"event": "invoice.paid",
"data": {
"invoice_id": 42,
"external_id": "ORDER-20260615-001",
"amount": 150000,
"paid_at": "2026-06-15T10:05:30.000Z"
}
}Contoh receiver PHP
<?php
// Endpoint ini harus ada di backend Anda.
// Contoh: https://website-anda.com/webhooks/otopay
$payload = json_decode(file_get_contents('php://input'), true);
if (($payload['event'] ?? '') !== 'invoice.paid') {
http_response_code(200);
exit;
}
$invoiceId = (int) ($payload['data']['invoice_id'] ?? 0);
$externalId = (string) ($payload['data']['external_id'] ?? '');
if ($invoiceId < 1 || $externalId === '') {
http_response_code(400);
exit;
}
// Verifikasi kembali ke OtoPay sebelum akhirnya fulfill order.
// Ganti helper ini dengan request POST /api/v1/check-status di backend Anda.
// Pastikan proses ini idempotent berdasarkan invoice_id / external_id.
verifyInvoiceWithOtoPay($invoiceId);
// Balas 2xx segera agar webhook tidak dianggap gagal.
http_response_code(200);
echo 'OK';event, membatasi ukuran payload, dan memverifikasi invoice melalui check-status sebelum fulfill order. Lakukan proses secara idempotent berdasarkan invoice_id.
Contoh integrasi siap pakai
Gunakan contoh di bawah sebagai titik awal. Ganti `YOUR_API_KEY`, dan simpan key melalui environment variable pada production.
PHP cURL — create invoice dari server
<?php
$apiBase = 'http://otopay.click';
$apiKey = getenv('OTOPAY_API_KEY') ?: 'YOUR_API_KEY';
$ch = curl_init($apiBase . '/api/v1/create-invoice');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 150000,
'description' => 'Premium Membership',
'external_id' => 'ORDER-' . time(),
'customer_name' => 'Budi Santoso',
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($result['success'])) {
http_response_code(502);
exit(json_encode(['error' => $result['error'] ?? 'Gagal membuat invoice']));
}
// Simpan invoice_id di database Anda, lalu kirim payment_url ke browser.
echo json_encode([
'invoice_id' => $result['data']['invoice_id'],
'payment_url' => $result['data']['payment_url'],
]);Node.js / Express — create invoice dari server
const API_BASE = process.env.OTOPAY_BASE_URL || 'http://otopay.click';
const API_KEY = process.env.OTOPAY_API_KEY;
const response = await fetch(API_BASE + '/api/v1/create-invoice', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 150000,
description: 'Premium Membership',
external_id: 'ORDER-' + Date.now(),
customer_name: 'Budi Santoso'
})
});
const result = await response.json();
if (!response.ok || !result.success) {
throw new Error(result.error || 'Gagal membuat invoice');
}
// Kirim payment_url ini ke frontend Anda.
console.log(result.data.invoice_id, result.data.payment_url);invoice_id, dan handler webhook.
Error codes & troubleshooting
Response error selalu berbentuk { "success": false, "error": "..." }. Jangan hanya membaca HTTP status; selalu periksa success.
| HTTP | Penyebab | Cara menangani |
|---|---|---|
400 | Amount invalid atau melewati batas paket. | Kirim integer Rupiah dan cek maxAmountPerInvoice. |
401 | API key tidak ada/tidak valid. | Periksa header Bearer dan key di Dashboard. |
403 | Paket nonaktif, API access tidak tersedia, atau kuota habis. | Aktifkan paket yang sesuai dan cek usage invoice. |
404 | Invoice tidak ditemukan atau milik merchant lain. | Simpan invoice_id saat create invoice. |
502/503 | Gateway QRIS belum dikonfigurasi atau sedang bermasalah. | Hubungi admin/operator gateway dan coba lagi dengan backoff. |
Checklist sebelum rilis
Paket aktif dan memiliki canApiAccess.
API key hanya berada di backend/secret manager.
Setiap order punya external_id yang unik.
Customer diarahkan ke payment_url yang dikembalikan API.
Webhook handler melakukan verifikasi dan idempotency.
Order baru di-fulfill setelah status PAID.