Dokumentasi Teknis Solosmartpay
Panduan lengkap integrasi pembayaran QRIS, Bank Virtual Account, E-Wallet, dan Retail: mulai dari autentikasi token, contoh kode multi-bahasa, kamus field JSON, hingga verifikasi signature Webhook HMAC-SHA256.
Ringkasan Alur & Pilihan Mode Integrasi
SoloSmartPay menyediakan REST API berbasis JSON yang memungkinkan sistem Anda membuat tagihan QRIS dinamis maupun Virtual Account/E-Wallet, memantau pelunasan secara otomatis, dan menerima notifikasi instan melalui Webhook bertanda tangan kriptografi.
Nominal + Kode Unik
Backend Anda memanggil POST /v1/qris dengan Idempotency-Key untuk menerima quote QRIS, lalu mengonfirmasi quote_id dengan key baru untuk provisioning.
Pencocokan Mutasi
Pelanggan memindai QRIS dari e-wallet atau m-banking apa pun. Sistem verifikasi otomatis mendeteksi dana masuk yang cocok dengan total_amount secara otomatis dalam hitungan detik.
Saldo & Notifikasi
Saldo bersih langsung dikreditkan ke ledger toko Anda dan event payment.paid dikirim ke endpoint Webhook HTTPS Anda.
Setelah provisioning_status menjadi ready, arahkan pelanggan ke data.checkout_url untuk QRIS atau data.payment_url untuk Bank VA/E-Wallet/Retail.
Untuk QRIS yang sudah siap, gunakan data.qris_string bila Anda perlu merender kode QR sendiri. Wajib: tampilkan nilai data.total_amount dengan jelas agar pelanggan membayar nominal tepat beserta kode uniknya.
API v1 Asinkron (Direkomendasikan untuk Produksi)
Gunakan POST /v1/payments atau POST /v1/qris dengan header Idempotency-Key. Invoice yang diprovisikan mengembalikan 202 Accepted, data, dan status_url; tunggu sampai provisioning_status=ready. QRIS pertama langsung mengembalikan quote; konfirmasi memakai quote_id dan key baru.
Autentikasi Token, Scopes & Daftar Endpoint
API Base URL https://solosmartpay.com/v1. Buat kredensial di menu Developer / API & Webhook → API Tokens, lalu kirimkan melalui header X-API-Token: ssp_… atau Authorization: Bearer ssp_….
qris:write
Izin untuk membuat quote atau provisioning QRIS (POST /v1/qris).
payments:write
Izin untuk mengantrekan tagihan Bank Virtual Account, E-Wallet, dan Retail (POST /v1/payments).
read
Izin membaca status pembayaran & daftar metode aktif (GET /v1/*).
| Method | Endpoint Path | Scope Wajib | Fungsi & Keterangan |
|---|---|---|---|
| POST | /v1/qris | qris:write |
Membuat quote QRIS atau mengantrekan provisioning dari quote_id. |
| GET | /v1/qris/{id} | read |
Mengecek detail & status pembayaran QRIS (pending, paid, expired). |
| GET | /v1/payment-methods | read |
Mengambil daftar metode Bank VA, E-Wallet, dan Retail yang aktif beserta biaya & batas nominal. |
| POST | /v1/payments | payments:write |
Mengantrekan tagihan Bank Virtual Account / E-Wallet / Retail; respons 202 menyertakan status_url. |
| GET | /v1/payments/{id} | read |
Mengecek status pembayaran tagihan Bank VA / E-Wallet / Retail berdasarkan ID publik. |
Membuat QRIS Dinamis & Kamus Field Response
Panggil POST https://solosmartpay.com/v1/qris dengan amount, header Idempotency-Key, dan reference opsional. Request pertama langsung mengembalikan quote. Untuk menerbitkan QRIS, kirim ulang amount dan quote_id dengan key baru; responsnya 202 dengan status_url.
Poll URL tersebut sampai provisioning_status menjadi ready sebelum menggunakan detail pembayaran.
curl -X POST https://solosmartpay.com/v1/qris -H "X-API-Token: ssp_live_xxxxxxxxxxxxxxxx" -H "Idempotency-Key: order-123-quote" -H "Content-Type: application/json" -d '{
"amount": 50000,
"reference": "ORDER-123"
}'
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'X-API-Token' => config('services.solosmartpay.token'),
'Idempotency-Key' => 'order-123-quote',
])->post('https://solosmartpay.com/v1/qris', [
'amount' => 50000,
'reference' => 'ORDER-123',
]);
$quote = $response->throw()->json('data');
// Kirim quote_id dengan key baru untuk provisioning, lalu poll status_url.
const response = await fetch('https://solosmartpay.com/v1/qris', {
method: 'POST',
headers: {
'X-API-Token': process.env.SSP_API_TOKEN,
'Idempotency-Key': 'order-123-quote',
'Content-Type': 'application/json',
},
body: JSON.stringify({ amount: 50000, reference: 'ORDER-123' }),
});
const { data } = await response.json();
console.log('Total Bayar:', data.total_amount, 'Checkout URL:', data.checkout_url);
import os, requests
resp = requests.post(
"https://solosmartpay.com/v1/qris",
headers={"X-API-Token": os.environ["SSP_API_TOKEN"], "Idempotency-Key": "order-123-quote"},
json={"amount": 50000, "reference": "ORDER-123"},
timeout=10,
)
resp.raise_for_status()
data = resp.json()["data"]
print("Bayar tepat Rp", data["total_amount"], "di", data["checkout_url"])
body := strings.NewReader(`{"amount":50000,"reference":"ORDER-123"}`)
req, _ := http.NewRequest("POST", "https://solosmartpay.com/v1/qris", body)
req.Header.Set("X-API-Token", os.Getenv("SSP_API_TOKEN"))
req.Header.Set("Idempotency-Key", "order-123-quote")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
{
"data": {
"id": "9d2f6c1e-5b8a-4c1e-9f0a-2b7d3e4f5a6b",
"provisioning_status": "queued"
},
"status_url": "https://solosmartpay.com/v1/qris/9d2f6c1e-5b8a-4c1e-9f0a-2b7d3e4f5a6b"
}
| Field Respons | Tipe Data | Penjelasan untuk Developer |
|---|---|---|
| id | string (UUID) | ID publik transaksi di SoloSmartPay. Simpan di database Anda untuk memanggil GET /v1/qris/{id}. |
| reference | string | null | ID order/invoice dari sistem Anda yang dikirim saat pembuatan tagihan. |
| status | string | Status tagihan: pending (menunggu pembayaran), paid (lunas), atau expired (melewati 15 menit). |
| redirect_url | string | null | URL https:// di toko Anda yang dikirim saat pembuatan tagihan. Kalau diisi, Hosted Checkout (checkout_url) mengalihkan pelanggan ke sana otomatis begitu status jadi paid. |
| amount & unique_code | integer | amount adalah nominal pokok; sampai Rp500.000, unique_code adalah kode 3 digit (1–999) untuk rekonsiliasi otomatis dan fee SoloSmartPay flat Rp300. Di atas Rp500.000 nilainya 0 dan fee total 0,5%. |
| total_amount | integer | Nominal tepat yang wajib dibayar pelanggan: amount + unique_code. Di atas Rp500.000 pelanggan membayar nominal tepat tanpa angka unik. |
| fee & net_amount | integer | fee adalah potongan biaya platform; net_amount adalah saldo bersih yang masuk ke akun toko Anda. |
| provisioning_status | string | Status penerbitan invoice. Gunakan detail pembayaran hanya setelah nilainya ready. |
| status_url (top-level) | string | URL tingkat atas pada respons 202 untuk polling status provisioning. |
Pembayaran Bank Virtual Account, E-Wallet & Retail
Selain QRIS, Anda dapat menerima pembayaran melalui Virtual Account Bank, E-Wallet, dan Gerai Retail. Ambil daftar kode metode yang aktif melalui GET /v1/payment-methods, lalu antrekan tagihan ke POST /v1/payments dengan Idempotency-Key. Respons 202 menyertakan status_url untuk dipoll. Pilih kode seperti BC untuk mengunci metode, atau gunakan all agar pelanggan memilih dari metode aktif di checkout.
# 1. Ambil daftar metode pembayaran yang aktif untuk toko Anda
curl -X GET https://solosmartpay.com/v1/payment-methods -H "X-API-Token: ssp_live_xxxxxxxxxxxxxxxx"
# 2. Buat tagihan Bank VA / E-Wallet / Retail (min. Rp 10.000)
curl -X POST https://solosmartpay.com/v1/payments -H "X-API-Token: ssp_live_xxxxxxxxxxxxxxxx" -H "Idempotency-Key: order-va-77-create" -H "Content-Type: application/json" -d '{
"amount": 150000,
"method": "BC",
"reference": "ORDER-VA-77"
}'
// Respons 202 Accepted:
{
"data": {
"id": "3c1f8a20-9b12-4e33-8a11-9c8b7a6f5e4d",
"provisioning_status": "queued"
},
"status_url": "https://solosmartpay.com/v1/payments/3c1f8a20-9b12-4e33-8a11-9c8b7a6f5e4d"
}
Verifikasi Webhook Merchant (HMAC-SHA256)
Daftarkan URL HTTPS (wajib port 443) di dashboard (Developer Hub → Webhooks). Setiap kali pembayaran lunas (payment.paid) atau kedaluwarsa (invoice.expired), SoloSmartPay mengirim notifikasi HTTP POST dengan jaminan pengiriman at-least-once dan retry otomatis hingga 8 kali. Verifikasi signature HMAC-SHA256 dan balas dengan kode HTTP 2xx dalam waktu ≤ 8 detik.
| HTTP Header Webhook | Contoh Nilai | Kegunaan |
|---|---|---|
| X-SoloSmartPay-Signature | sha256=8f92a3c1… | Signature HMAC-SHA256 dari timestamp + "." + raw_json_body menggunakan Signing Secret Anda. |
| X-SoloSmartPay-Timestamp | 1758945792 | Unix epoch detik saat webhook dikirim. Tolak jika selisih waktu > 300 detik untuk mencegah replay attack. |
| X-SoloSmartPay-Event | payment.paid | Jenis event: payment.paid, invoice.expired, atau webhook.test. |
| X-SoloSmartPay-Event-ID | evt_3f1c9a… | ID unik event. Simpan di database Anda sebagai kunci deduplikasi (idempotency) jika terjadi retry. |
| X-SoloSmartPay-Delivery-ID | dlv_9a8b7c… | ID unik untuk setiap percobaan pengiriman HTTP. |
X-SoloSmartPay-Signature = "sha256=" + HMAC_SHA256(webhook_secret, timestamp + "." + raw_json_body)
Exponential Backoff (Maks. 8x Percobaan): Jika server Anda gagal merespons 2xx, pengiriman diulang otomatis dengan jeda 10 detik → 1 menit → 5 menit → 30 menit → 2 jam → 12 jam → 24 jam.
$rawBody = $request->getContent();
$timestamp = (string) $request->header('X-SoloSmartPay-Timestamp');
$signature = (string) $request->header('X-SoloSmartPay-Signature');
$eventId = (string) $request->header('X-SoloSmartPay-Event-ID');
// 1. Tolak jika selisih waktu > 300 detik (mencegah replay attack)
if (abs(time() - (int) $timestamp) > 300) {
abort(401, 'Timestamp expired');
}
// 2. Hitung ulang HMAC-SHA256 menggunakan signing secret webhook Anda
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, config('services.solosmartpay.webhook_secret'));
if (!hash_equals($expected, $signature)) {
abort(401, 'Invalid webhook signature');
}
// 3. Deduplikasi berdasarkan $eventId, lalu tandai pesanan LUNAS
$event = json_decode($rawBody, true);
if (($event['type'] ?? '') === 'payment.paid') {
Order::where('ref', $event['data']['reference'])->update(['status' => 'paid']);
}
return response()->json(['ok' => true]);
const crypto = require('crypto');
app.post('/webhook/solosmartpay', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.header('X-SoloSmartPay-Timestamp') || '';
const signature = req.header('X-SoloSmartPay-Signature') || '';
const rawBody = req.body.toString('utf8');
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).json({ error: 'Timestamp expired' });
}
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.SSP_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
if (expected.length !== signature.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(rawBody);
// event.type === 'payment.paid' → tandai pesanan event.data.reference lunas
return res.status(200).json({ received: true });
});
import hmac, hashlib, time
def verify_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
signed_payload = timestamp.encode("utf-8") + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
mac := hmac.New(sha256.New, []byte(os.Getenv("SSP_WEBHOOK_SECRET")))
mac.Write([]byte(timestamp + "." + string(rawBody)))
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
http.Error(w, "Invalid webhook signature", http.StatusUnauthorized)
return
}
Kode Status HTTP & Penanganan Error
Semua respons error mengembalikan objek JSON dengan properti message (dan errors untuk validasi field HTTP 422).
| HTTP Status | Arti | Penyebab & Cara Mengatasi |
|---|---|---|
| 200 / 201 | OK / Created | Tagihan baru berhasil dibuat (201) atau tagihan idempoten / cek status berhasil diambil (200). |
| 401 | Unauthorized | Token tidak dikirim, salah, sudah dicabut/kedaluwarsa, atau akun toko sedang dibekukan. |
| 403 | Forbidden | IP server pemanggil tidak terdaftar pada Whitelist IP token, atau token tidak memiliki scope yang sesuai (qris:write / payments:write / read). |
| 404 | Not Found | ID transaksi (id) tidak ditemukan atau bukan milik toko dari token yang digunakan. |
| 409 | Conflict | Nilai reference sudah dipakai oleh tagihan aktif dengan nominal atau metode yang berbeda. Gunakan reference unik untuk setiap order. |
| 422 | Unprocessable | Validasi gagal (mis. amount di luar batas) atau verifikasi KYC toko belum disetujui. |
| 429 | Rate Limited | Melebihi batas rate limit yang dikonfigurasi per token. Periksa header X-RateLimit-Limit dan tunggu sesuai header Retry-After. |
Biaya Transaksi, Withdraw & Checklist Produksi
Biaya transaksi dipotong otomatis dari dana yang masuk ke saldo toko; pelanggan cukup membayar sebesar total_amount. Penarikan dana (Withdraw) diajukan dari dashboard ke rekening bank terdaftar.
Biaya transfer bertingkat sesuai nominal penarikan. Minimal penarikan Rp10.000.
Biaya transfer per penarikan untuk nominal di atas batas tarif.
ssp_… di kode frontend/mobile. Isi daftar IP server produksi Anda saat membuat token.
total_amount dengan Tegas: Jika Anda menggunakan Custom UI (bukan checkout_url), pastikan pelanggan membayar tepat nominal + kode unik agar pembayaran otomatis cocok.
X-SoloSmartPay-Signature menggunakan raw request body dan simpan X-SoloSmartPay-Event-ID agar order tidak diproses ganda saat terjadi retry.
Panduan Pengaturan Toko, Metode Pembayaran & Keamanan
Setiap akun SoloSmartPay mendukung hingga 10 toko terpisah. Masing-masing toko memiliki saldo ledger, status KYC, pilihan metode pembayaran (Bank VA, E-Wallet, Retail), rekening pencairan, API Token, dan endpoint Webhook sendiri.
Aktivasi Penerimaan Pembayaran
Di menu Toko Anda → Verifikasi KYC:
1. Lengkapi profil usaha (Perorangan atau Badan Usaha), alamat, dan kategori bisnis.
2. Unggah foto KTP, Selfie memegang KTP, serta NPWP (opsional untuk perorangan).
3. Setelah disetujui (maks. 1×24 jam kerja), toko Anda langsung dapat membuat tagihan QRIS/VA/E-Wallet dan mencairkan dana.
Bank VA, E-Wallet & Gerai Retail
Di menu Toko Anda → Pengaturan Toko → Metode Pembayaran:
1. Aktifkan saklar Terima semua metode aktif atau pilih metode spesifik per kelompok (Bank Virtual Account, E-Wallet, dan Gerai Retail). QRIS tersedia sebagai jalur pembayaran terpisah.
2. Endpoint GET /v1/payment-methods otomatis menyesuaikan daftar metode sesuai pengaturan toko Anda.
| Menu Panel Merchant | Fungsi & Praktik Keamanan Terbaik |
|---|---|
| Terima Pembayaran /qris-terminal |
Membuat link pembayaran tanpa koding untuk QRIS, Bank VA, E-Wallet, maupun Retail. Untuk QRIS, layar otomatis berubah menjadi centang hijau saat pembayaran masuk. |
| Rekening & Withdraw /withdraw |
Mengatur rekening bank pencairan (nama pemilik rekening wajib sesuai identitas KYC) dan mengajukan penarikan dana Reguler (hari kerja) maupun Instan (diprioritaskan ≤ 30 menit). |
| Developer Hub (API & Webhook) /developer |
Membuat API Token (ssp_…) dengan pembatasan Scopes (qris:write, payments:write, read), IP/CIDR Whitelist server produksi, serta menguji pengiriman Webhook HMAC-SHA256. |
| Keamanan Akun & 2FA /settings |
Mengaktifkan autentikasi dua faktor (TOTP 2FA via Google Authenticator / Authy), menyimpan 8 recovery codes, serta memantau dan mengakhiri sesi perangkat yang sedang login. |