QRIS Payment API
Terima pembayaran QRIS dinamis dengan LapakinAja. API RESTful kami memungkinkan Anda membuat transaksi, memantau status, dan menerima webhook real-time.
Base URL
https://api.lapakinaja.netAutentikasi
Untuk mengautentikasi request API Anda, Anda harus menyertakan API ID dan API Key di header HTTP. Anda dapat menemukan kredensial ini di tab Dashboard > Merchant > Kredensial setelah membuat instance merchant.
X-API-ID: {api_id_anda}
X-API-KEY: {api_key_anda}
Content-Type: application/jsonBuat Transaksi
Buat transaksi QRIS baru. Ini akan mengembalikan totalAmount yang mencakup digit kode unik untuk verifikasi otomatis.
/api/transaction/createParameter Body (JSON)
| Field | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| amount | number | Ya | Nominal pembayaran (harus positif) |
| order_id | string | Ya | ID unik pesanan dari sistem Anda |
| custom_name | string | Tidak | Nama merchant kustom yang tampil di QRIS (maks 25 karakter). Hanya berlaku untuk QRIS Mandiri. |
Body Request
{
"amount": 10000,
"order_id": "INV-001",
"custom_name": "Toko Saya"
}custom_name bersifat opsional
Respon Sukses
{
"transaction_id": "uuid-...",
"amount": 10000,
"unique_code": 230,
"total_amount": 10230,
"qris_url": "https://api.lapakinaja.net/api/qr?text=...",
"qris_string": "00020101021226...",
"status": "PENDING",
"expired_at": "2026-02-19T..."
}Custom Nama Merchant (Khusus QRIS Mandiri)
Parameter custom_name memungkinkan Anda mengubah nama merchant yang ditampilkan pada kode QRIS. Fitur ini hanya berlaku untuk QRIS dari Bank Mandiri. Jika QRIS Anda bukan dari Mandiri, parameter ini akan diabaikan. Nama kustom maksimal 25 karakter sesuai standar EMVCo.
Penting: Anda harus menampilkan total_amount (termasuk kode unik) kepada pelanggan untuk memastikan verifikasi otomatis.
Contoh cURL
curl -X POST https://api.lapakinaja.net/api/transaction/create \
-H "X-API-ID: yourApiId" \
-H "X-API-KEY: yourApiKey" \
-H "Content-Type: application/json" \
-d '{"amount":10000,"order_id":"INV-001"}'curl -X POST https://api.lapakinaja.net/api/transaction/create \
-H "X-API-ID: yourApiId" \
-H "X-API-KEY: yourApiKey" \
-H "Content-Type: application/json" \
-d '{"amount":10000,"order_id":"INV-001","custom_name":"Toko Saya"}'Webhooks (Callback)
LapakinAja akan melakukan POST payload ke callbackUrl terdaftar Anda setiap kali transaksi berhasil (SUCCESS) atau kedaluwarsa (EXPIRED). Setiap callback dilengkapi tanda tangan kriptografis untuk keamanan.
Payload Callback
{
"transaction_id": "1f043286...",
"order_id": "INV-001",
"nominal": 10230,
"status": "SUCCESS"
}Status bisa: SUCCESS atau EXPIRED
Header Keamanan
X-Signature: {hmac_sha256}
X-Timestamp: {unix_timestamp}Signature = HMAC_SHA256(API_KEY, JSON_BODY + TIMESTAMP)
Anda harus membuat signature sendiri menggunakan API Key Anda, lalu samakan dengan X-Signature yang kami kirim.
Auto Retry: Jika server Anda tidak merespon 200 OK, kami akan retry hingga 5x dengan interval exponential (5s, 10s, 20s, 40s, 80s).
Contoh Kode PHP — Webhook Handler Lengkap
Berikut contoh lengkap file PHP yang bisa Anda gunakan sebagai endpoint callbackUrl. File ini menerima callback dari LapakinAja, memverifikasi signature, dan memproses data transaksi.
<?php
/**
* ==============================================
* LapakinAja - Webhook Callback Handler (PHP)
* ==============================================
*
* Letakkan file ini di server Anda, lalu daftarkan URL-nya
* sebagai callbackUrl di Dashboard > Merchant > Settings.
* Contoh: https://domain-anda.com/callback.php
*/
// 1. KONFIGURASI
$apiKey = 'YOUR_API_KEY_HERE'; // Ganti dengan API Key Anda
$maxTimestampAge = 300; // Toleransi 5 menit
// 2. TERIMA DATA DARI LAPAKINAJA
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit(json_encode(['error' => 'Method Not Allowed']));
}
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$rawBody = file_get_contents('php://input');
// 3. VALIDASI TIMESTAMP (Cegah Replay Attack)
if (empty($timestamp) || abs(time() - intval($timestamp)) > $maxTimestampAge) {
http_response_code(401);
exit(json_encode(['error' => 'Request expired']));
}
// 4. VERIFIKASI SIGNATURE (HMAC-SHA256)
// Rumus: HMAC_SHA256(API_KEY, JSON_BODY + TIMESTAMP)
$expected = hash_hmac('sha256', $rawBody . $timestamp, $apiKey);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit(json_encode(['error' => 'Signature tidak valid']));
}
// 5. PARSING & PROSES DATA
$data = json_decode($rawBody, true);
/**
* Data yang diterima:
* transaction_id => "uuid-xxxx"
* order_id => "INV-001"
* nominal => 10230
* status => "SUCCESS" atau "EXPIRED"
*/
if ($data['status'] === 'SUCCESS') {
updateOrder($data['order_id'], 'PAID', $data['nominal']);
} elseif ($data['status'] === 'EXPIRED') {
updateOrder($data['order_id'], 'EXPIRED', $data['nominal']);
}
// 6. RESPON 200 OK (WAJIB!)
// Jika tidak 200, LapakinAja akan retry hingga 5x
http_response_code(200);
echo json_encode(['success' => true, 'message' => 'Callback diterima']);
// ============================================
// FUNGSI HELPER (sesuaikan dengan database Anda)
// ============================================
function updateOrder($orderId, $status, $nominal) {
try {
$pdo = new PDO('mysql:host=localhost;dbname=db_anda', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("
UPDATE orders
SET status = :status, paid_amount = :nominal, updated_at = NOW()
WHERE order_id = :order_id
");
$stmt->execute([
':status' => $status,
':nominal' => $nominal,
':order_id' => $orderId,
]);
} catch (PDOException $e) {
error_log('Callback DB Error: ' . $e->getMessage());
}
}Contoh Kode PHP — Framework Laravel
Jika Anda menggunakan Laravel, berikut contoh Controller yang bisa langsung dipakai.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Models\Order;
class LapakinAjaController extends Controller
{
/**
* Handle callback dari LapakinAja
* Route: POST /api/lapakinaja/callback
*/
public function handleCallback(Request $request)
{
$apiKey = config('services.lapakinaja.api_key');
$signature = $request->header('X-Signature', '');
$timestamp = $request->header('X-Timestamp', '');
// Penting: Gunakan raw body, bukan $request->all()
$rawBody = $request->getContent();
// 1. Validasi timestamp (maks 5 menit)
if (empty($timestamp) || abs(time() - intval($timestamp)) > 300) {
Log::warning('LapakinAja: timestamp expired');
return response()->json(['error' => 'Request expired'], 401);
}
// 2. Verifikasi signature HMAC-SHA256
$expected = hash_hmac('sha256', $rawBody . $timestamp, $apiKey);
if (!hash_equals($expected, $signature)) {
Log::warning('LapakinAja: invalid signature');
return response()->json(['error' => 'Invalid signature'], 401);
}
// 3. Proses data
$data = json_decode($rawBody, true);
Log::info('LapakinAja callback:', $data);
$order = Order::where('order_id', $data['order_id'])->first();
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
// Hindari proses duplikat
if ($order->status === 'PAID') {
return response()->json(['success' => true, 'message' => 'Already processed']);
}
if ($data['status'] === 'SUCCESS') {
$order->update([
'status' => 'PAID',
'paid_amount' => $data['nominal'],
'paid_at' => now(),
]);
// event(new PaymentReceived($order));
} elseif ($data['status'] === 'EXPIRED') {
$order->update(['status' => 'EXPIRED']);
}
// Wajib return 200 OK
return response()->json(['success' => true, 'message' => 'Callback processed']);
}
}
// ==============================================
// SETUP YANG DIPERLUKAN:
// ==============================================
// 1. Route → routes/api.php:
// Route::post('/lapakinaja/callback', [LapakinAjaController::class, 'handleCallback']);
//
// 2. Config → config/services.php:
// 'lapakinaja' => ['api_key' => env('LAPAKINAJA_API_KEY')],
//
// 3. Environment → .env:
// LAPAKINAJA_API_KEY=your_api_key_here
//
// 4. PENTING: Kecualikan dari CSRF middleware!
// Di VerifyCsrfToken.php:
// protected \$except = ['api/lapakinaja/callback'];Contoh Buat Transaksi dari PHP
Contoh cara membuat transaksi QRIS dari server PHP Anda.
<?php
/**
* Contoh: Buat transaksi QRIS via LapakinAja API
* Parameter custom_name bersifat opsional (khusus QRIS Mandiri)
*/
$apiId = 'YOUR_API_ID';
$apiKey = 'YOUR_API_KEY';
$baseUrl = 'https://api.lapakinaja.net';
$payload = json_encode([
'amount' => 10000,
'order_id' => 'INV-' . time(),
'custom_name' => 'Toko Saya', // Opsional, hanya untuk QRIS Mandiri (maks 25 karakter)
]);
$ch = curl_init($baseUrl . '/api/transaction/create');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-ID: ' . $apiId,
'X-API-KEY: ' . $apiKey,
],
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 200 || $httpCode === 201) {
echo "Transaksi berhasil dibuat!\n";
echo "Transaction ID : " . $result['transaction_id'] . "\n";
echo "Total Bayar : Rp " . number_format($result['total_amount']) . "\n";
echo "QRIS URL : " . $result['qris_url'] . "\n";
echo "Expired : " . $result['expired_at'] . "\n";
} else {
echo "Error: " . ($result['message'] ?? 'Unknown error') . "\n";
}Verifikasi Tanda Tangan
Untuk memastikan callback benar-benar dari LapakinAja, buat signature Anda sendiri menggunakan API Key Anda, lalu samakan dengan header X-Signature yang kami kirim. Pastikan juga X-Timestamp masih baru (maks 5 menit) untuk mencegah replay attack.
Node.js
const crypto = require("crypto");
const signature = req.headers["x-signature"];
const timestamp = req.headers["x-timestamp"];
const body = JSON.stringify(req.body);
// 1. Cek timestamp (maks 5 menit)
if (Math.abs(Date.now()/1000 - parseInt(timestamp)) > 300) {
return res.status(401).json({ error: "Request expired" });
}
// 2. Buat signature sendiri & bandingkan
const mySignature = crypto
.createHmac("sha256", process.env.MY_API_KEY)
.update(body + timestamp)
.digest("hex");
if (signature !== mySignature) {
return res.status(401).json({ error: "Invalid signature" });
}
// ✅ VALID - proses data
const data = req.body;
console.log(data.order_id, data.status);PHP
<?php
$signature = $_SERVER['HTTP_X_SIGNATURE'];
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'];
$body = file_get_contents('php://input');
// 1. Cek timestamp (maks 5 menit)
if (abs(time() - intval($timestamp)) > 300) {
http_response_code(401);
exit('Request expired');
}
// 2. Buat signature sendiri & bandingkan
$mySignature = hash_hmac('sha256', $body . $timestamp, $myApiKey);
if (!hash_equals($mySignature, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
// ✅ VALID - proses data
$data = json_decode($body, true);
echo $data['order_id'] . ' ' . $data['status'];Kode Status HTTP
| Kode | Arti | Deskripsi |
|---|---|---|
| 400 | Request Tidak Valid | Body JSON tidak valid atau field tidak lengkap. |
| 401 | Tidak Terotorisasi | API Key hilang atau tidak valid. |
| 403 | Dilarang | IP Anda tidak terdaftar dalam whitelist merchant ini. |
| 429 | Batas Rate | Terlalu banyak permintaan. Periksa batas paket Anda. |
| 500 | Error Internal | Masalah sistem. Silakan hubungi support. |
Praktik Keamanan Terbaik
Wajib HTTPS
Semua request API harus dikirim melalui HTTPS. Request tanpa enkripsi akan ditolak.
Isolasi Kunci
Perlakukan API Key Anda seperti password. Segera putar (rotate) jika terkompromi.
Whitelist IP
Batasi panggilan API ke IP server Anda menggunakan fitur Whitelist kami untuk perlindungan ekstra.
Idempotensi
Gunakan Order ID unik untuk mencegah penagihan ganda untuk tujuan pelanggan yang sama.