Dokumentasi API untuk Developer
Panduan lengkap integrasi QRIS dinamis, webhook, dan Payment API FN CREATIVE — ringkas, jelas, dan siap diterapkan.
1. Kredensial API
Setiap request wajib menyertakan header berikut — nilai di bawah ini milik akun Anda setelah login:
id_merchant di bawah sudah valid.
x-api-key— login dulu —id_merchant—webhook_secret—
Base URL
https://pay.fncreative.org/api2. Create Transaction
/qris/createMembuat QRIS dinamis dengan kode unik. Gunakan nilai total_amount dari response sebagai nominal yang wajib dibayar pelanggan.
Kode unik kecil per tier — nilai live dari GET /api/docs/meta.
| Base amount | Kode unik (+) |
|---|---|
| Memuat… | |
| Field | Keterangan |
|---|---|
order_id * | ID unik transaksi (INV-1001) |
amount * | Nominal dasar integer (min 500) |
keterangan | Deskripsi opsional |
callback_url | Override webhook URL (opsional) |
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://pay.fncreative.org/api/qris/create',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => '{
"order_id": "INV-123",
"amount": 10000,
"keterangan": "Pembayaran Invoice #123"
}',
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'x-api-key: YOUR_API_KEY',
'id_merchant: YOUR_MERCHANT_ID'
),
));
total_amount, bukan nominal dasar amount.expired_at dan expired_menit di response — jangan hardcode 60 menit di UI merchant.Response (201)
{
"status": true,
"code": "transaction_created",
"message": "Transaction Created Successfully",
"data": {
"order_id": "INV-123",
"amount": "10000.00",
"base_amount": 10000,
"unique_code": 45,
"amount_uniq": "45.00",
"total_amount": "10045.00",
"status": "PENDING",
"expired_at": "2026-06-13 16:05:01 WIB",
"expired_menit": "5",
"signature": "...",
"keterangan": "Pembayaran Invoice #123",
"qris_image": "data:image/png;base64,...",
"qr_string": "00020101...",
"customer_instruction": "Scan QRIS, bayar sesuai total sampai 3 digit terakhir, lalu tunggu status SUCCESS."
}
}
3. Masa Berlaku QR & Kode Unik
Setiap transaksi QRIS dinamis memiliki batas waktu pembayaran. Nilai aktual platform saat ini (live dari GET /api/docs/meta):
| Parameter | Nilai | Arti |
|---|---|---|
unique_code.slot_minutes | 5 menit | QR PENDING mengunci slot kode unik selama ini |
unique_code.expire_minutes | 5 menit | QR otomatis EXPIRED jika belum dibayar |
unique_code.match_window_minutes | 180 menit | Transaksi SUCCESS tetap mengunci slot (anti bentrok) |
unique_code.payment_match_grace_minutes | 10 menit | Toleransi backend: pembayaran telat setelah QR EXPIRED masih bisa dicocokkan otomatis |
Tampilan vs backend
- UI merchant menampilkan countdown ±5 menit (sesuai
expired_menitdi response create). - Backend tetap memantau ±10 menit sejak QR dibuat untuk mencocokkan mutasi bank — customer yang bayar menit ke-6–10 setelah create masih bisa masuk saldo otomatis.
- Lewat toleransi backend → status tetap EXPIRED, uang terdeteksi masuk ke log Tidak Masuk Saldo — merchant bisa minta tinjauan atau admin kredit manual.
Alur merchant
- Panggil
POST /qris/create→ tampilkan QR +total_amount+ countdown dariexpired_at. - Customer bayar dalam waktu aktif → webhook PAID atau polling
GET /qris/status/{order_id}→ SUCCESS. - Lewat waktu tanpa bayar → status EXPIRED → buat QR baru (disarankan
order_idbaru).
code dan next_step, tampilkan next_step ke operator/merchant agar penanganannya jelas.4. Webhook Callback
Gateway dapat mengirim ulang callback yang sama (retry otomatis/admin). Selalu cek order_id sudah diproses sebelum mengaktifkan produk/layanan.
// Node.js contoh handler
app.post("/payment/callback", async (req, res) => {
const { order_id, status, total_amount, payment_date, signature, delivery_id, signature_v2 } = req.body;
if (await db.isOrderPaid(order_id)) {
return res.status(200).json({ ok: true, duplicate: true });
}
const legacy = hmacLegacy(order_id, status, total_amount, payment_date);
const v2 = delivery_id ? hmacV2(delivery_id, order_id, status, total_amount, payment_date) : null;
if (signature !== legacy && (!v2 || signature_v2 !== v2)) {
return res.status(401).json({ ok: false, error: "invalid_signature" });
}
if (status !== "PAID") return res.status(200).json({ ok: true, ignored: true });
await db.markPaid(order_id);
return res.status(200).json({ ok: true });
});
Field baru: delivery_id (unik per pengiriman), delivered_at (ISO UTC), signature_v2. Field signature lama tetap ada untuk kompatibilitas.
Server merchant harus merespon HTTP 200 OK. Callback dikirim saat pembayaran sukses — field status bernilai PAID (bukan SUCCESS seperti di API status/history).
Webhook PAID vs API SUCCESS
| Channel | Field status | Arti |
|---|---|---|
| Webhook callback ke server merchant | PAID | Bayar sukses — proses order / kirim produk di sistem Anda |
GET /qris/status/{order_id} | SUCCESS | Arti sama dengan PAID — gunakan untuk polling |
GET /qris/history | SUCCESS | Transaksi sukses di riwayat & laporan |
| Sebelum bayar | PENDING | Menunggu pembayaran |
| Lewat masa berlaku QR | EXPIRED | QR kadaluarsa — buat transaksi baru |
SUCCESS di webhook — field itu tidak ada di callback.callback_url di body /qris/create jika ingin notifikasi otomatis. Tanpa URL, gunakan polling GET /qris/status/{order_id}.Validasi Signature
Secret webhook akun Anda: —
signature = HMAC_SHA256( YOUR_WEBHOOK_SECRET, order_id + "|" + status + "|" + total_amount + "|" + payment_date )
{
"order_id": "INV-123",
"status": "PAID",
"amount": 10000,
"total_amount": 10045,
"payment_date": "2026-06-01 21:48:01",
"created_at": "2026-06-01 21:18:01 WIB",
"updated_at": "2026-06-01 21:48:01",
"keterangan": "Pembayaran Invoice #123",
"direct_url": null,
"signature": "..."
}
5. Check Status
/qris/status/{order_id}Header sama seperti create: x-api-key + id_merchant. Status di response API: PENDING, SUCCESS, atau EXPIRED.
6. Transaction History
/qris/historyHeader integrasi: x-api-key + id_merchant. Mendukung filter dan paginasi.
| Query | Keterangan |
|---|---|
page | Halaman (default 1) |
per_page | Item per halaman, max 50 (default 30) |
status | ALL · SUCCESS · PENDING · EXPIRED |
q atau search | Cari order ID / keterangan |
date_from | Tanggal awal filter (YYYY-MM-DD, WIB) |
date_to | Tanggal akhir filter (YYYY-MM-DD, WIB) |
GET https://pay.fncreative.org/api/qris/history?page=1&per_page=30&status=SUCCESS&date_from=2026-06-01&date_to=2026-06-26
x-api-key: YOUR_API_KEY
id_merchant: YOUR_MERCHANT_ID
Response mencakup status_counts (jumlah per status), total_pages, dan array data berisi transaksi.
Export CSV
/qris/history/exportQuery filter sama seperti history. Mengembalikan file CSV (maks. 5.000 baris) dengan kolom: order_id, created_at, base_amount, unique_code, total_amount, status, paid_at, expired_at, keterangan.
x-api-key + id_merchant — cocok untuk rekonsiliasi otomatis di sistem merchant.7. Ringkasan Harian
/qris/summary?date=2026-06-01Menampilkan total transaksi sukses dan pendapatan per hari (zona Asia/Jakarta). Parameter date opsional — default hari ini.
| Field | Arti |
|---|---|
revenue.gross_amount | Total uang masuk (nominal dibayar customer, termasuk kode unik) |
revenue.base_amount | Total harga dasar produk (tanpa kode unik) |
revenue.success_count | Jumlah transaksi SUCCESS di tanggal tersebut |
activity.pending_today | QR dibuat hari itu yang masih PENDING |
8. Login & Dashboard
/merchants/loginDashboard web memakai session token (Bearer). API integrasi tetap memakai x-api-key.
{
"email": "dev@tokoabc.com",
"password": "password-anda"
}
Response berisi session_token. Kredensial API lihat via GET /merchants/me setelah login (bukan di body login).
/merchants/meHeader: Authorization: Bearer <session_token>
9. Dompet & Penarikan
Fee platform: 1,2% dari total pembayaran (termasuk kode unik) per transaksi SUCCESS. Penarikan minimum Rp 25.000, maksimal 3 kali per hari (zona WIB).
Notifikasi Telegram & email merchant diatur lewat dashboard web. Admin platform tetap menerima alert operasional.
| Endpoint | Auth | Fungsi |
|---|---|---|
GET /merchants/wallet | Bearer | Saldo, fee, kuota penarikan hari ini, rekening bank |
PUT /merchants/bank-account | Bearer | Simpan rekening penarikan |
POST /merchants/withdraw | Bearer | Ajukan penarikan (amount atau "all") |
GET /merchants/withdrawals | Bearer | Riwayat penarikan |
GET /merchants/wallet/ledger | Bearer | Mutasi saldo (kredit, fee, hold penarikan) |
PUT /merchants/callback-url | Bearer | Simpan/hapus webhook URL default |
GET /merchants/telegram-notify | Bearer | Status notifikasi Telegram |
PUT /merchants/telegram-notify | Bearer | Aktif/nonaktif + Chat ID |
POST /merchants/telegram-notify/link | Bearer | Buat link hubung bot Telegram |
POST /merchants/telegram-notify/test | Bearer | Kirim pesan tes Telegram |
GET /merchants/email-notify | Bearer | Status notifikasi email merchant |
PUT /merchants/email-notify | Bearer | Aktif/nonaktif notifikasi email |
POST /merchants/email-notify/test | Bearer | Kirim email tes ke alamat merchant |
callback_url harus HTTPS publik (bukan localhost/IP privat). Isi per transaksi di body /qris/create atau di dashboard.withdraw_daily_limit dan withdraw_today_count di response wallet menunjukkan kuota penarikan harian.10. Pembayaran Tidak Masuk Saldo (Payment Issues)
Kasus di mana mutasi bank terdeteksi sistem tetapi tidak otomatis masuk saldo merchant. Biasanya terjadi karena customer transfer setelah QR EXPIRED di tampilan (±5 menit) dan melewati toleransi auto-match backend (±10 menit sejak QR dibuat).
Authorization: Bearer <session_token> (login dashboard). Bukan x-api-key.Alur kasus
- Sistem mendeteksi transfer masuk tetapi tidak bisa dicocokkan otomatis → kasus tercatat (
needs_action: true). - Merchant melihat kasus di dashboard atau via
GET /merchants/payment-issues. - Jika customer sudah bayar, merchant ajukan tinjauan →
POST .../claim. - Tim FN CREATIVE memproses dari panel admin: kredit manual ke order atau tutup kasus (refund/ditutup).
- Setelah selesai, merchant menerima notifikasi Telegram + Gmail (jika diaktifkan) dan riwayat tersedia di
notification_history.
Jenis kasus (result)
| Nilai | Label | Arti |
|---|---|---|
no_match | Tidak cocok | Transfer terdeteksi, tidak ada order PENDING/EXPIRED dengan nominal yang sama dalam jendela toleransi. |
ambiguous_grace | Nominal kembar | Ada ≥2 order dengan nominal identik — sistem tidak bisa memastikan order mana yang dibayar. |
Status penyelesaian (resolution_status)
| Nilai | Label | Arti |
|---|---|---|
| (kosong) | — | Belum diproses — needs_action: true |
credited | Sudah dikredit | Admin mengkredit manual ke order — saldo merchant bertambah + webhook PAID dikirim. |
refunded | Sudah direfund | Kasus ditutup — dana direfund ke merchant (tanpa kredit saldo). |
dismissed | Ditutup | Kasus ditutup tanpa kredit (duplikat / false alarm). |
Daftar kasus
/merchants/payment-issues| Query | Keterangan |
|---|---|
page | Halaman (default 1) |
per_page | Item per halaman, maks. 50 (default 20) |
include_resolved | 1 = sertakan kasus yang sudah dikredit/ditutup/refund. Default: hanya yang masih perlu tindakan. |
GET https://pay.fncreative.org/api/merchants/payment-issues?page=1&per_page=20&include_resolved=1
Authorization: Bearer YOUR_SESSION_TOKEN
Response:
{
"status": true,
"data": {
"current_page": 1,
"per_page": 20,
"total": 2,
"total_pages": 1,
"grace_minutes": 10,
"display_expire_minutes": 5,
"data": [
{
"id": 218,
"amount": 237063,
"created_at": "2026-06-26 20:15:42 WIB",
"result": "no_match",
"result_label": "Tidak cocok",
"reason": "Tidak ada order PENDING yang cocok.",
"recommendation": "Pembayaran terdeteksi tetapi tidak masuk saldo Anda — ...",
"needs_action": false,
"is_resolved": true,
"resolution_status": "refunded",
"resolution_label": "Sudah direfund",
"resolution_note": "Sudah direfund ke merchant / tidak jadi kredit.",
"resolved_at": "2026-06-26 20:22:15 WIB",
"resolved_order_id": "",
"merchant_claimed_at": "2026-06-26 20:18:00 WIB",
"merchant_claim_note": "Customer transfer jam 20:14 WIB, Order ORDER-76250626114",
"related_orders": [
{
"order_id": "ORDER-76250626114",
"status": "EXPIRED",
"base_amount": 237000,
"unique_code": 63,
"expected_amount": 237063,
"total_amount": 237063,
"created_at": "2026-06-26 20:05:12 WIB",
"expired_at": "2026-06-26 20:10:12 WIB",
"keterangan": "Topup saldo"
}
],
"notification_history": [
{
"id": 12,
"log_id": 218,
"audience": "merchant",
"channel": "telegram",
"event": "Pembayaran sudah direfund",
"recipient_masked": "***8546",
"status": "sent",
"created_at": "2026-06-26 20:31:05 WIB"
},
{
"id": 13,
"log_id": 218,
"audience": "merchant",
"channel": "email",
"event": "Pembayaran sudah direfund",
"recipient_masked": "pi***@gmail.com",
"status": "sent",
"created_at": "2026-06-26 20:31:06 WIB"
}
]
}
]
}
}
Field objek kasus
| Field | Tipe | Keterangan |
|---|---|---|
id | number | ID kasus (log) — dipakai di claim dan notifications |
amount | number | Nominal transfer terdeteksi (integer Rupiah) |
created_at | string | Waktu deteksi pembayaran (WIB) |
result | string | no_match atau ambiguous_grace |
result_label | string | Label UI: Tidak cocok / Nominal kembar |
reason | string | Alasan teknis sistem |
recommendation | string | Saran tindakan untuk merchant |
needs_action | boolean | true jika masih perlu tindakan merchant/admin |
is_resolved | boolean | true jika sudah dikredit/ditutup |
resolution_status | string | credited / refunded / dismissed / kosong |
resolution_label | string | Label penyelesaian (UI) |
resolution_note | string | Catatan admin saat menutup/mengkredit |
resolved_at | string | Waktu penyelesaian (WIB) |
resolved_order_id | string | Order tujuan jika dikredit manual |
merchant_claimed_at | string | Waktu merchant ajukan tinjauan (kosong jika belum) |
merchant_claim_note | string | Catatan merchant saat claim |
related_orders | array | Order terkait milik merchant ini (lihat tabel di bawah) |
notification_history | array | Riwayat notifikasi Telegram/Gmail untuk kasus selesai (hanya jika is_resolved: true) |
Field related_orders[]
| Field | Keterangan |
|---|---|
order_id | ID order merchant |
status | Biasanya EXPIRED saat kasus muncul |
base_amount | Nominal dasar (tanpa kode unik) |
unique_code | Kode unik QRIS |
expected_amount / total_amount | Total yang harus dibayar customer |
created_at / expired_at | Waktu QR dibuat & expired (WIB) |
keterangan | Deskripsi order |
Ajukan tinjauan (claim)
/merchants/payment-issues/{log_id}/claimMerchant melaporkan bahwa customer sudah transfer. Satu kasus hanya bisa di-claim sekali. Wajib sertakan Order ID & kapan customer transfer di note.
POST https://pay.fncreative.org/api/merchants/payment-issues/218/claim
Authorization: Bearer YOUR_SESSION_TOKEN
Content-Type: application/json
{
"note": "Customer transfer jam 22:05 WIB, Order ORDER-76250626114, bukti sudah dikirim via WA"
}
Response sukses:
{
"status": true,
"message": "Permintaan tinjauan dikirim. Tim FN CREATIVE akan mengecek bukti transfer.",
"data": {
"log_id": 218,
"amount": 237063,
"detected_at": "2026-06-26 20:15:42 WIB",
"merchant_claimed_at": "2026-06-26 22:06:10 WIB",
"merchant_claim_note": "Customer transfer jam 22:05 WIB, ..."
}
}
Error umum (HTTP 400):
Kasus ini bukan milik merchant Anda.Kasus ini sudah diselesaikan.Anda sudah mengajukan tinjauan untuk kasus ini.
Riwayat notifikasi kasus
/merchants/payment-issues/{log_id}/notificationsMengembalikan riwayat pengiriman notifikasi untuk merchant yang login (Telegram + Gmail). Notifikasi admin/ops tidak ditampilkan di endpoint ini.
GET https://pay.fncreative.org/api/merchants/payment-issues/218/notifications
Authorization: Bearer YOUR_SESSION_TOKEN
| Field | Keterangan |
|---|---|
channel | telegram atau email |
event | Judul notifikasi, mis. Pembayaran sudah direfund, Pembayaran dikredit manual |
recipient_masked | Chat ID / email tujuan (disamarkan) |
status | sent / skipped (notif nonaktif) / failed |
created_at | Waktu pengiriman (WIB) |
include_resolved=1 pada daftar kasus, field notification_history sudah disertakan per item — endpoint terpisah ini berguna untuk refresh tanpa memuat ulang seluruh daftar.Notifikasi otomatis
Merchant menerima Telegram dan/atau Gmail (jika diaktifkan di dashboard) pada event berikut:
| Event | Pemicu |
|---|---|
| Pembayaran tidak masuk saldo ⚠️ | Kasus baru terdeteksi (order terkait merchant) |
| Permintaan tinjauan diterima ✅ | Merchant berhasil POST .../claim |
| Pembayaran dikredit manual ✅ | Admin mengkredit kasus ke order |
| Pembayaran sudah direfund | Admin menutup kasus (refund ke merchant) |
| Kasus pembayaran ditutup | Admin menutup kasus tanpa kredit |
Aktifkan notifikasi: Webhook Log & Email (Telegram + Gmail).
Endpoint admin (FN CREATIVE Ops)
x-admin-key. Bukan untuk integrasi merchant — dokumentasi referensi internal.| Endpoint | Fungsi |
|---|---|
GET /admin/payment-match-logs | Daftar log pembayaran. Query: filter=unmatched, resolution=pending|resolved|all, q, page, limit |
POST /admin/payment-match-logs/{id}/credit | Kredit manual. Body: order_id, note |
POST /admin/payment-match-logs/{id}/dismiss | Tutup kasus. Body: type (refund / dismissed / duplicate), note |
GET /admin/payment-match-logs/{id}/notifications | Riwayat notifikasi lengkap (merchant + admin) |
11. Webhook Log & Notifikasi Email
Log pengiriman webhook
/merchants/webhook-log?page=1&per_page=20Header: Authorization: Bearer <session_token>. Menampilkan riwayat callback ke URL merchant: order_id, target_url, status_code, success, error_text, created_at.
Notifikasi email merchant
Selain Telegram, merchant bisa menerima email untuk event pembayaran, penarikan, kasus tidak masuk saldo, dan penyelesaian kasus (kredit/refund/tutup). Aktifkan di dashboard atau via API:
PUT https://pay.fncreative.org/api/merchants/email-notify
Authorization: Bearer YOUR_SESSION_TOKEN
Content-Type: application/json
{ "enabled": true }
Tes kirim: POST /merchants/email-notify/test
12. Kredensial & Registrasi OTP
Manajemen API key (session)
| Endpoint | Fungsi |
|---|---|
POST /merchants/claim-api-key | Masukkan API key lama yang masih aktif agar bisa ditampilkan lagi di dashboard |
POST /merchants/regenerate-api-key | Ganti API key — setelah berhasil, API key lama langsung tidak bisa dipakai lagi |
POST /merchants/regenerate-webhook-secret | Rotasi webhook_secret untuk validasi HMAC |
POST https://pay.fncreative.org/api/merchants/regenerate-api-key
Authorization: Bearer YOUR_SESSION_TOKEN
Registrasi dengan OTP email
Jika server mengaktifkan verifikasi OTP (lihat email.otp_register_required di GET /api/docs/meta):
POST /merchants/register/send-otp— kirim kode ke email (body sama seperti register)POST /merchants/register/confirm— konfirmasi denganverification_id+otp_code
/merchants/register/confirm{
"verification_id": "dari response send-otp",
"otp_code": "123456"
}
Jika OTP tidak aktif, gunakan POST /merchants/register langsung (lihat Onboarding Partner).
Best Practice Integrasi (Produksi)
- Auth ganda: integrasi bot/website pakai
x-api-key+id_merchant; dashboard, kasus tidak masuk saldo & riwayat notifikasi pakaiBearer session_token. - Jika customer klaim sudah transfer tetapi saldo belum masuk, cek Payment Issues → ajukan
claimdengan Order ID & bukti. - Tampilkan total_amount besar di checkout — bukan hanya
amount. - Countdown dari
expired_at— semua timestamp API dalam format WIB (Asia/Jakarta). - Satu
order_id= satu invoice. Jangan create ulang berkali-kali tanpa perlu. - Jika masih PENDING, API mengembalikan QR yang sama — cek status dulu sebelum create baru.
- Setelah EXPIRED, create dengan
order_idbaru atau hapus record expired (reuse order_id diperbolehkan setelah expired). - Validasi webhook dengan
webhook_secret+ HMAC — abaikan callback tanpa signature valid. - Fee platform (1,2%) hanya pada transaksi SUCCESS — EXPIRED tidak dikenai fee.
Status Types
| Status | Arti |
|---|---|
| PENDING | Menunggu pembayaran — QR masih aktif sampai expired_at |
| SUCCESS | Pembayaran berhasil (API status/history) — saldo merchant dikredit (setelah fee) |
| PAID | Sama artinya dengan SUCCESS — hanya dipakai di webhook callback ke server merchant |
| EXPIRED | QR kadaluarsa (±5 menit default) — buat QR baru untuk customer |
Onboarding Partner
User bisa daftar sendiri tanpa hubungi admin:
- Buka https://pay.fncreative.org/register/
- Isi nama, email, password, dan opsional
callback_urlwebhook HTTPS - Setujui Syarat & Ketentuan dan Kebijakan Privasi
- Akun menunggu persetujuan admin — setelah disetujui, kredensial API tersedia di dashboard
- Login kapan saja di /login/ — API key yang sama terlihat di dashboard
callback_url di body /qris/create.total_amount. Detail teknis: bagian Masa Berlaku QR.API Registrasi (opsional)
/merchants/register/merchants/register/send-otp · /merchants/register/confirmDetail OTP & manajemen kredensial: lihat Kredensial & Registrasi OTP.
{
"name": "Toko ABC",
"email": "dev@tokoabc.com",
"password": "password-min-8",
"password_confirm": "password-min-8",
"store_name": "Toko ABC",
"callback_url": "https://tokoabc.com/webhook/fn-payment",
"terms_accepted": true
}