@poppackage/qris-payment-sdk
v1.0.0
Published
SDK JavaScript/TypeScript ringan untuk integrasi pembayaran QRIS melalui gateway Poppay. Memungkinkan Anda membuka jendela pembayaran (popup) dengan minimal konfigurasi dan mendukung data dinamis.
Downloads
63
Readme
QRIS Payment SDK
SDK JavaScript/TypeScript ringan untuk integrasi pembayaran QRIS melalui gateway Poppay. Memungkinkan Anda membuka jendela pembayaran (popup) dengan minimal konfigurasi dan mendukung data dinamis.
✨ Fitur
- 🚀 Ringan: Tanpa dependensi berat.
- 📦 Multi-Format: Mendukung ESM (NPM) dan UMD (CDN).
- 🛠 Dinamis: Data transaksi bisa diinput dari sisi client.
- 🔗 Auto-Inquiry: Sudah termasuk logika pengecekan status pembayaran otomatis.
⚙️ Instalasi
Menggunakan NPM (Next.js, React, Vue, Laravel Vite)
npm i @poppackage/qris-payment-sdk
Menggunakan CDN (Laravel Blade, HTML Statis)
Tambahkan script ini sebelum penutup tag </body>:
<script src="https://unpkg.com/@poppackage/qris-payment-sdk/dist/qris-sdk.umd.js"></script>
🚀 Cara Penggunaan
Parameter wajib: selain amount, Anda harus mengisi invoice (string unik per transaksi/order dari sistem Anda). Nilai ini dipakai agar gateway dan alur onSuccess di backend dapat mengaitkan pembayaran QRIS dengan baris order yang benar—jangan memakai placeholder statis di production.
1. Penggunaan di Framework Modern (ESM)
Jika Anda menggunakan bundler seperti Vite atau Webpack:
import { QrisSDK } from '@poppackage/qris-payment-sdk';
const qris = new QrisSDK({
amount: 50000,
invoice: 'INV-1001',
resultContainerId: 'payment-result',
onSuccess: (data) => {
console.log('Payment success:', data);
// Handle success, misal redirect atau tampilkan notifikasi
},
onFailed: (status) => {
console.log('Payment failed:', status);
// Handle failure
}
});
// Panggil fungsi ini pada event click tombol
const handlePay = () => {
qris.openPayment();
};
2. Penggunaan di Laravel / Vanilla JS (UMD)
Setelah memanggil script dari CDN, class QrisSDK akan tersedia di objek window.
<div id="payment-result"></div>
<button id="pay-button">Bayar Sekarang</button>
<script>
const btn = document.getElementById('pay-button');
const qris = new QrisSDK({
amount: 15000,
invoice: 'INV-1002',
resultContainerId: 'payment-result',
onSuccess: (data) => {
console.log('Payment success:', data);
},
onFailed: (status) => {
console.log('Payment failed:', status);
}
});
btn.onclick = () => {
qris.openPayment();
};
</script>
📌 USE CASES
Di semua skenario di bawah, invoice wajib dikirim ke new QrisSDK({ ... }) (biasanya nilai yang sama dengan nomor invoice/order di database Anda). Backend pada onSuccess harus memverifikasi invoice tersebut (bukan hanya mempercayai isi body dari browser).
1. WordPress
WordPress tidak menyediakan “plugin resmi” untuk SDK ini; integrasi dilakukan dengan muat script UMD, elemen HTML di template/shortcode, dan titik backend (AJAX atau REST API) bila Anda perlu memperbarui data di database (misalnya status pesanan) setelah onSuccess. Pola di bawah berlaku universal untuk tema custom, child theme, atau plugin buatan sendiri—bukan terikat satu plugin toko tertentu.
Prasyarat
- Domain situs production sudah didaftarkan ke admin PopPay (sama seperti penggunaan di stack lain).
- Pastikan halaman pembayaran memuat variabel global
ajaxurlbila memakaiadmin-ajax.php(banyak tema menambahkan ini diwp_head; jika belum ada, gunakanadmin_url('admin-ajax.php')diwp_localize_scriptatau atributdata-*pada elemen HTML).
Pola integrasi universal (ringkas)
- Muat SDK dari CDN hanya di halaman yang relevan (checkout, thank-you, atau halaman custom), agar performa tetap ringan:
- Di child theme
functions.php: hookwp_enqueue_scripts, cek kondisi halaman (is_page(),is_checkout()WooCommerce, dll.), laluwp_enqueue_scriptdengan URLhttps://unpkg.com/@poppackage/qris-payment-sdk/dist/qris-sdk.umd.js(atau salin URL yang sama ke tag<script>di template jika tidak memakai enqueue).
- Di child theme
- Output HTML di template halaman tersebut (atau lewat shortcode):
- Satu elemen untuk
resultContainerId(misalnya<div id="payment-result"></div>). - Satu tombol yang memicu
qris.openPayment().
- Satu elemen untuk
- Data dinamis dari PHP (disarankan):
- Nominal (
amount),invoice(wajib), catatan (notes), nama/email pembeli (payor_name,payor_email) diisi dari meta post, post meta order, atau objek order plugin yang Anda pakai—bukan hardcode. - Untuk keamanan pembaruan status di server: hasilkan nonce WordPress (
wp_create_nonce) dan kirim bersama permintaan AJAX darionSuccess.
- Nominal (
- Backend wajib jika status harus tersimpan di database:
- Daftarkan handler dengan
add_action('wp_ajax_...', ...)danadd_action('wp_ajax_nopriv_...', ...)untuk pengguna tidak login jika perlu. - Di handler: verifikasi nonce, validasi order/invoice, lalu
UPDATEdata (post meta, tabel custom, atau API plugin toko). Templat PHP checkout saja tidak ikut dieksekusi pada requestadmin-ajax.php, jadi logika update tidak boleh hanya berada di file template yang di-includesekali saat halaman tampil—harus di file yang selalu dimuat lewatfunctions.php/ plugin utama /ajax.phptheme.
- Daftarkan handler dengan
- JavaScript: setelah
QrisSDKtersedia (typeof QrisSDK !== 'undefined'), inisialisasi seperti pada bagian Laravel / Vanilla JS, lalu dionSuccesspanggilfetch(ajaxurl, { method: 'POST', body: URLSearchParams dengan action + nonce + id order })lalu perbarui UI (sembunyikan tombol, tampilkan pesan sukses, redirect, dll.).
Dengan pola di atas, Anda bisa memasang SDK di halaman statis, WooCommerce, Easy Digital Downloads, toko theme custom, atau kombinasi plugin—yang berubah hanya sumber data PHP (dari mana diambil amount, invoice, dan id pesanan) dan bentuk penyimpanan status di server.
Contoh referensi nyata: ayoborong.com
Situs ayoborong.com memakai WordPress dengan ekosistem toko berbasis theme vmplace dan alur Velocity (keranjang → checkout → pembayaran PGAuto / QRIS). Contoh implementasi yang selaras dengan panduan universal di atas:
| Lapisan | Peran |
| --- | --- |
| Tampilan & inisialisasi SDK | File template theme, misalnya payment-pgauto-display.php: menghitung nominal dan data pembeli dari POST atau dari baris order di database, menampilkan #poppay-payment-result, tombol bayar, serta tag <script> yang memuat UMD dan membuat instance QrisSDK dengan onSuccess / onFailed. |
| Pembaruan status “dibayar” di server | File PHP terpisah yang selalu dimuat dari functions.php (atau digabung ke file AJAX theme), mendefinisikan action admin-ajax yang memverifikasi nonce + invoice, memastikan metode pembayaran sesuai, lalu mengupdate status order di tabel custom toko. |
| Alur pengguna | Pembeli menyelesaikan checkout → halaman instruksi pembayaran menampilkan QR existing + opsi Bayar pakai QRIS PopPay → setelah sukses di SDK, browser memanggil AJAX → UI sukses dan data order konsisten dengan status dibayar di sistem. |
Contoh ini mengikuti prinsip universal: frontend (template + CDN + QrisSDK) dan backend (AJAX + nonce) terpisah dengan jelas. Anda dapat menyalin polanya ke proyek WordPress lain dengan mengganti sumber data order dan query update sesuai plugin/tema masing-masing.
Contoh kode: backend (PHP) — admin-ajax + update database
Letakkan di plugin custom, functions.php child theme, atau file include theme yang selalu dimuat. Ganti nama action, nama tabel/meta, dan logika update sesuai struktur data Anda (WooCommerce, tabel custom, CPT, dll.).
Pola umum: tabel custom ($wpdb->prefix . 'nama_tabel' — contoh field invoice + status):
<?php
/**
* Handler AJAX: tandai order lunas setelah onSuccess SDK (panggil dari fetch di frontend).
* Action: my_qris_mark_paid → POST field: action, invoice, nonce
*/
add_action( 'wp_ajax_my_qris_mark_paid', 'my_qris_mark_paid_handler' );
add_action( 'wp_ajax_nopriv_my_qris_mark_paid', 'my_qris_mark_paid_handler' );
function my_qris_mark_paid_handler() {
$invoice = isset( $_POST['invoice'] ) ? sanitize_text_field( wp_unslash( $_POST['invoice'] ) ) : '';
$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
if ( ! $invoice || ! wp_verify_nonce( $nonce, 'my_qris_' . $invoice ) ) {
wp_send_json_error( array( 'message' => 'Permintaan tidak valid.' ), 403 );
}
global $wpdb;
$table = $wpdb->prefix . 'orders'; // SESUAIKAN nama tabel order Anda.
$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE invoice = %s", $invoice ) );
if ( ! $row ) {
wp_send_json_error( array( 'message' => 'Order tidak ditemukan.' ), 404 );
}
// Opsional: pastikan pembeli yang login = pemilik order.
if ( is_user_logged_in() && isset( $row->user_id ) && (int) $row->user_id !== get_current_user_id() ) {
wp_send_json_error( array( 'message' => 'Akses ditolak.' ), 403 );
}
$updated = $wpdb->update(
$table,
array( 'status' => 'paid' ), // SESUAIKAN nilai / kolom status.
array( 'invoice' => $invoice ),
array( '%s' ),
array( '%s' )
);
if ( false === $updated ) {
wp_send_json_error( array( 'message' => 'Gagal memperbarui database.' ), 500 );
}
wp_send_json_success( array( 'message' => 'Status diperbarui.' ) );
}Pola alternatif: WooCommerce (jika order_id diketahui dan plugin aktif):
<?php
function my_qris_wc_mark_paid_handler() {
$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
if ( ! $order_id || ! wp_verify_nonce( $nonce, 'my_qris_wc_' . $order_id ) ) {
wp_send_json_error( array( 'message' => 'Permintaan tidak valid.' ), 403 );
}
if ( ! function_exists( 'wc_get_order' ) ) {
wp_send_json_error( array( 'message' => 'WooCommerce tidak aktif.' ), 400 );
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
wp_send_json_error( array( 'message' => 'Order tidak ditemukan.' ), 404 );
}
// Contoh: tandai sudah dibayar (sesuaikan dengan alur Anda).
$order->payment_complete();
// atau: $order->update_status( 'processing' );
wp_send_json_success( array( 'message' => 'Pembayaran tercatat.' ) );
}
// add_action( 'wp_ajax_my_qris_wc_mark_paid', 'my_qris_wc_mark_paid_handler' );
// add_action( 'wp_ajax_nopriv_my_qris_wc_mark_paid', 'my_qris_wc_mark_paid_handler' );Contoh kode: frontend universal WordPress — enqueue SDK + inisialisasi
Langkah A — muat SDK (dan opsional skrip init) dari functions.php atau plugin:
add_action( 'wp_enqueue_scripts', function () {
// SESUAIKAN kondisi halaman (slug, ID, is_checkout(), dll.).
if ( ! is_page( 'thank-you' ) ) {
return;
}
wp_enqueue_script(
'qris-poppay-sdk',
'https://unpkg.com/@poppackage/qris-payment-sdk/dist/qris-sdk.umd.js',
array(),
null,
true
);
} );Langkah B — di template halaman pembayaran (misalnya page-thank-you.php atau template checkout theme), output data dari PHP + tombol + skrip:
Nilai $amount, $invoice, $payor_name, $payor_email harus berasal dari order sungguhan (query DB / fungsi plugin), bukan angka statis.
<?php
// Contoh data dari order (sesuaikan dengan proyek Anda):
$amount = 50000;
$invoice = 'INV-1001';
$payor_name = 'Budi';
$payor_email = '[email protected]';
$nonce = wp_create_nonce( 'my_qris_' . $invoice );
$ajax_url = esc_url( admin_url( 'admin-ajax.php' ) );
?>
<div id="qris-payment-result"></div>
<button type="button" id="qris-pay-button" class="button">Bayar QRIS PopPay</button>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof QrisSDK === 'undefined') {
console.error('QrisSDK belum dimuat. Pastikan script CDN di-enqueue.');
return;
}
var btn = document.getElementById('qris-pay-button');
var invoice = <?php echo wp_json_encode( $invoice ); ?>;
var nonce = <?php echo wp_json_encode( $nonce ); ?>;
var qris = new QrisSDK({
amount: <?php echo (int) $amount; ?>,
invoice: invoice,
notes: 'Invoice ' + invoice,
payor_name: <?php echo wp_json_encode( $payor_name ); ?>,
payor_email: <?php echo wp_json_encode( $payor_email ); ?>,
resultContainerId: 'qris-payment-result',
onSuccess: function (data) {
var body = new URLSearchParams();
body.append('action', 'my_qris_mark_paid');
body.append('invoice', invoice);
body.append('nonce', nonce);
var ajaxUrl = (typeof ajaxurl !== 'undefined') ? ajaxurl : <?php echo wp_json_encode( $ajax_url ); ?>;
fetch(ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: body.toString(),
credentials: 'same-origin'
})
.then(function (r) { return r.json(); })
.then(function (json) {
if (json && json.success) {
alert('Pembayaran berhasil & status order diperbarui.');
} else {
alert((json && json.data && json.data.message) ? json.data.message : 'Gagal memperbarui server.');
}
})
.catch(function (err) { console.error(err); });
},
onFailed: function (status) {
console.log('QRIS gagal / timeout:', status);
}
});
btn.addEventListener('click', function () {
qris.openPayment();
});
});
</script>Catatan: Jika tema Anda belum mendefinisikan ajaxurl di frontend, gunakan variabel ajaxUrl seperti di atas (sudah memakai admin_url( 'admin-ajax.php' ) dari PHP). Nama action AJAX (my_qris_mark_paid) harus sama dengan add_action di backend.
2. Laravel
Di Laravel, pola universalnya: halaman Blade atau Inertia memuat SDK (CDN atau bundler), onSuccess memanggil route POST (biasanya di routes/web.php dengan CSRF + middleware auth bila pembeli login). Controller memverifikasi bahwa order milik user (atau token sekali pakai untuk guest—sesuaikan), lalu Eloquent / Query Builder meng-update baris order.
Prasyarat
- Domain production terdaftar di admin PopPay.
- Pastikan CSRF token tersedia di frontend (meta tag
csrf-tokendi layout, atau@csrfpada form) untuk requestPOSTdari JavaScript. - Untuk API stateless (mobile / SPA terpisah), pertimbangkan Laravel Sanctum atau signed URL alih-alir contoh
web+ session di bawah.
Pola integrasi universal (ringkas)
- Muat SDK di layout halaman pembayaran (
@push('scripts')atau Vite). - Saat inisialisasi
QrisSDK, sertakaninvoicewajib (sama dengan kolom invoice order di database). - Controller menerima
invoice(dan opsionalorder_id), memvalidasi, mengupdate kolomstatus/payment_statuspada modelOrder. - Frontend di
onSuccess:fetchke route Laravel dengan headerX-CSRF-TOKENdanAccept: application/json, body JSON{ invoice }.
Contoh kode: backend (Laravel) — route + controller + update database
routes/web.php (contoh; sesuaikan prefix & middleware):
<?php
use App\Http\Controllers\OrderController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth'])->group(function () {
Route::post('/orders/qris-mark-paid', [OrderController::class, 'markPaidFromQris'])
->name('orders.qris-mark-paid');
});app/Http/Controllers/OrderController.php:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class OrderController extends Controller
{
/**
* Dipanggil dari onSuccess SDK — update status order di database.
*/
public function markPaidFromQris(Request $request): JsonResponse
{
$validated = $request->validate([
'invoice' => ['required', 'string', 'max:100'],
]);
$order = Order::query()
->where('invoice', $validated['invoice'])
->where('user_id', auth()->id()) // pastikan pemilik order = user login
->first();
if (! $order) {
return response()->json(['message' => 'Order tidak ditemukan.'], 404);
}
// Opsional: cegah double-update jika sudah paid
if ($order->status === 'paid') {
return response()->json(['message' => 'Sudah dibayar.', 'already' => true]);
}
$order->update([
'status' => 'paid', // SESUAIKAN nama kolom / nilai enum Anda
]);
return response()->json(['message' => 'Status diperbarui.']);
}
}app/Models/Order.php: pastikan invoice, user_id, status ada di $fillable (atau gunakan $guarded = [] hanya untuk development).
Contoh kode: frontend (Blade + UMD dari CDN)
Letakkan di view Blade tempat data order sudah tersedia ($order). Untuk guest checkout, ganti middleware/route dengan pola signed URL atau token sekali pakai yang Anda generate di server—jangan mempercayai invoice saja tanpa verifikasi tambahan.
{{-- resources/views/orders/pay.blade.php --}}
<meta name="csrf-token" content="{{ csrf_token() }}">
<div id="qris-payment-result"></div>
<button type="button" id="qris-pay-button">Bayar QRIS PopPay</button>
<script src="https://unpkg.com/@poppackage/qris-payment-sdk/dist/qris-sdk.umd.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof QrisSDK === 'undefined') return;
var invoice = @json($order->invoice);
var csrf = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
var url = @json(route('orders.qris-mark-paid'));
var qris = new QrisSDK({
amount: {{ (int) $order->total_amount }},
invoice: invoice,
notes: 'Invoice ' + invoice,
payor_name: @json($order->customer_name ?? ''),
payor_email: @json($order->customer_email ?? ''),
resultContainerId: 'qris-payment-result',
onSuccess: function () {
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': csrf,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ invoice: invoice }),
credentials: 'same-origin'
})
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.message) { alert(data.message); }
})
.catch(console.error);
},
onFailed: function (status) {
console.log('QRIS gagal / timeout:', status);
}
});
document.getElementById('qris-pay-button').addEventListener('click', function () {
qris.openPayment();
});
});
</script>3. Next.js (App Router)
Di Next.js, jangan mengupdate database langsung dari komponen klien. Pola universal: Server Action atau Route Handler (app/api/.../route.ts) yang memverifikasi sesi (misalnya NextAuth, Clerk, atau cookie session Anda), lalu memanggil ORM / SQL (Prisma, Drizzle, Kysely, dll.). Komponen klien memuat ESM @poppackage/qris-payment-sdk, lalu di onSuccess memanggil fetch('/api/...') dengan credentials bila memakai cookie session.
Prasyarat
- Domain production terdaftar di admin PopPay.
- Pastikan route API tidak mengekspos secret server ke browser; gunakan session atau JWT server-side untuk membuktikan identitas pembeli.
Pola integrasi universal (ringkas)
- Pasang paket:
npm i @poppackage/qris-payment-sdk. - Buat Route Handler
POSTyang membaca body{ invoice }, memverifikasi sesi, mengupdate tabel order. - Di Client Component (
'use client'), inisialisasiQrisSDKdenganinvoicewajib (string dari server, misalnya dari props halaman) dan padaonSuccesspanggil API denganfetch.
Contoh kode: backend (Next.js Route Handler) — update database
Sesuaikan impor DB dengan stack Anda (contoh di bawah memakai pola Prisma; ganti dengan query Anda).
app/api/orders/qris-mark-paid/route.ts:
import { NextResponse } from 'next/server';
// import { getServerSession } from 'next-auth'; // jika pakai NextAuth
// import { prisma } from '@/lib/prisma';
export async function POST(request: Request) {
try {
const body = await request.json();
const invoice = typeof body.invoice === 'string' ? body.invoice.trim() : '';
if (!invoice) {
return NextResponse.json({ message: 'invoice wajib diisi.' }, { status: 400 });
}
// --- Verifikasi sesi (contoh; sesuaikan dengan auth Anda) ---
// const session = await getServerSession(authOptions);
// if (!session?.user?.id) {
// return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
// }
// --- Contoh Prisma (uncomment & sesuaikan model) ---
// const order = await prisma.order.findFirst({
// where: {
// invoice,
// userId: session.user.id as string,
// },
// });
// if (!order) {
// return NextResponse.json({ message: 'Order tidak ditemukan.' }, { status: 404 });
// }
// await prisma.order.update({
// where: { id: order.id },
// data: { status: 'paid' },
// });
return NextResponse.json({ message: 'Status diperbarui.' });
} catch (e) {
console.error(e);
return NextResponse.json({ message: 'Server error.' }, { status: 500 });
}
}Uncomment dan sambungkan blok Prisma (atau db.execute mentah) sesuai skema Anda. Intinya: satu sumber kebenaran di server yang menulis ke database.
Contoh kode: frontend (Client Component + ESM)
components/QrisPayButton.tsx:
'use client';
import { useCallback, useEffect, useRef } from 'react';
import { QrisSDK } from '@poppackage/qris-payment-sdk';
type Props = {
amount: number;
invoice: string;
payorName: string;
payorEmail: string;
};
export function QrisPayButton({ amount, invoice, payorName, payorEmail }: Props) {
const qrisRef = useRef<QrisSDK | null>(null);
useEffect(() => {
qrisRef.current = new QrisSDK({
amount,
invoice,
notes: `Invoice ${invoice}`,
payor_name: payorName,
payor_email: payorEmail,
resultContainerId: 'qris-payment-result',
onSuccess: async () => {
const res = await fetch('/api/orders/qris-mark-paid', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ invoice }),
credentials: 'include', // penting jika auth berbasis cookie
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
alert(data.message ?? 'Gagal memperbarui order.');
return;
}
alert(data.message ?? 'Pembayaran tercatat.');
},
onFailed: (status) => {
console.log('QRIS gagal / timeout:', status);
},
});
}, [amount, invoice, payorName, payorEmail]);
const open = useCallback(() => {
qrisRef.current?.openPayment();
}, []);
return (
<>
<div id="qris-payment-result" />
<button type="button" onClick={open}>
Bayar QRIS PopPay
</button>
</>
);
}Halaman server (page.tsx) mengambil order dari database dan meneruskan amount, invoice, dll. ke <QrisPayButton /> sebagai props.
📖 Konfigurasi (Options)
Anda dapat mengirimkan objek konfigurasi saat inisialisasi new QrisSDK(config).
| Parameter | Wajib / Opsional | Tipe | Fungsi | Contoh isi |
| --- | --- | --- | --- | --- |
| amount | Wajib | number | Nominal pembayaran dalam Rupiah (IDR). Dikirim ke gateway saat membuat transaksi. | 50000 |
| invoice | Wajib | string | Pengenal unik transaksi/order di sistem Anda (nomor invoice, order id publik, dll.). Harus konsisten dengan data di database agar backend dapat memvalidasi pembayaran di onSuccess. | 'INV-2026-0042' |
| notes | Opsional | string | Catatan transaksi (mutasi/laporan). Jika tidak diisi, SDK memakai string kosong. | 'Pesanan #1001' |
| payor_email | Opsional | string | Email pembayar, jika ingin dicatat bersama transaksi. | '[email protected]' |
| payor_name | Opsional | string | Nama pembayar, jika ingin dicatat bersama transaksi. | 'Budi Santoso' |
| expirationInterval | Opsional | number | Masa berlaku QR dalam detik. Default SDK: 300 (5 menit). | 300 |
| onSuccess | Opsional | (data: any) => void | Dipanggil saat pembayaran berhasil. Jika tidak diisi, SDK hanya console.log. | (data) => { /* redirect / toast */ } |
| onFailed | Opsional | (status: any) => void | Dipanggil saat gagal atau timeout. Jika tidak diisi, SDK hanya console.log. | (status) => { /* tampilkan error */ } |
| resultContainerId | Opsional | string | ID elemen HTML tempat hasil pembayaran akan di-inject secara otomatis. | 'payment-result' |
