Callback System (Event-Driven) โ
Setelah pelanggan membayar, bagaimana aplikasi kamu tahu? Jawabannya: Callback (Webhook).
๐ค Apa Itu Callback? โ
Callback adalah permintaan HTTP POST yang dikirim oleh Duitku ke server kamu setelah pelanggan menyelesaikan pembayaran.
Analogi: Bayangkan kamu antar baju ke laundry. Saat baju selesai dicuci, laundry menelepon kamu: "Baju sudah selesai!" Callback itu "telepon" dari Duitku ke server kamu.
Yang terjadi:
Pelanggan bayar di Duitku
โ
Duitku kirim HTTP POST ke URL callback kamu
โ
Server kamu terima data (merchantOrderId, resultCode, dll)
โ
Aplikasi kamu update database: order = paidIMPORTANT
Callback dikirim server-to-server (backend ke backend). Ini berbeda dengan redirect yang terjadi di browser pelanggan. Callback lebih reliable karena tidak bergantung pada browser pelanggan.
๐ก๏ธ Kenapa Harus Validasi Signature? โ
Siapa saja bisa mengirim HTTP POST ke URL callback kamu โ termasuk hacker. Untuk memastikan bahwa data benar-benar dari Duitku (bukan penipu), kamu harus memvalidasi signature.
Analogi: Bayangkan ada orang ketuk pintu dan bilang "Paket dari Toko A". Kamu harus cek identitasnya dulu sebelum terima paket. Signature = identitas yang dicek.
SDK ini otomatis melakukan validasi signature. Kamu tidak perlu hitung MD5 manual.
โ๏ธ Setup Route untuk Callback โ
Pertama, buat route untuk menerima callback. URL ini harus didaftarkan di Dashboard Duitku.
// routes/api.php
Route::post('/duitku/callback', [PaymentController::class, 'callback']);WARNING
Route callback harus exempt dari CSRF protection! Karena Duitku mengirim POST request tanpa CSRF token.
Tambahkan URL callback ke exception list di bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'duitku/callback', // Exempt callback Duitku dari CSRF
]);
})Atau di app/Http/Middleware/VerifyCsrfToken.php (Laravel 10):
protected $except = [
'duitku/callback',
];Cara 1: Manual (Simple) โ
Jika kamu ingin handle sendiri dengan logic sederhana:
use Duitku\Laravel\Facades\Duitku;
use Duitku\Laravel\Support\PaymentCode;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function callback(Request $request)
{
// 1. Validasi signature (cek keaslian data)
if (!Duitku::validateCallback($request->all())) {
// Data tidak valid (mungkin palsu) โ tolak!
abort(403, 'Invalid Signature');
}
// 2. Proses berdasarkan result code
$orderId = $request->merchantOrderId;
$resultCode = $request->resultCode;
if ($resultCode === PaymentCode::SUCCESS) {
// โ
Pembayaran berhasil โ update database
Order::where('order_id', $orderId)->update(['status' => 'paid']);
} else {
// โ Pembayaran gagal/expired
Order::where('order_id', $orderId)->update(['status' => 'failed']);
}
return response('OK', 200);
}
}Cara 2: Event-Driven (Recommended) โญ โ
Cara yang lebih bersih dan scalable adalah menggunakan Laravel Events. Dengan cara ini, controller kamu cuma 1 baris, dan semua logic business ada di Listener terpisah.
Langkah 1: Di Controller โ
use Duitku\Laravel\Facades\Duitku;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function callback(Request $request)
{
// Satu baris ini melakukan:
// 1. Validasi signature โ
// 2. Dispatch event yang sesuai โ
// 3. Throw exception jika signature invalid โ
Duitku::handleCallback($request->all());
return response('OK', 200);
}
}Kenapa lebih baik? Controller cukup tipis (1 baris). Logic "apa yang dilakukan setelah bayar" ada di Listener โ lebih mudah di-test dan di-maintain.
Langkah 2: Buat Listener โ
php artisan make:listener UpdateOrderPaid// app/Listeners/UpdateOrderPaid.php
namespace App\Listeners;
use App\Models\Order;
use Duitku\Laravel\Events\DuitkuPaymentReceived;
class UpdateOrderPaid
{
public function handle(DuitkuPaymentReceived $event): void
{
// Ambil data callback (sudah tervalidasi signature-nya)
$callback = $event->callback;
// Data yang tersedia:
// $callback->merchantOrderId โ ID order kamu
// $callback->amount โ Nominal pembayaran
// $callback->resultCode โ '00' = sukses
// $callback->reference โ Reference ID Duitku
// $callback->signature โ Signature (sudah divalidasi)
// Update database
$order = Order::where('order_id', $callback->merchantOrderId)->first();
if ($order) {
$order->update([
'status' => 'paid',
'paid_at' => now(),
'duitku_reference' => $callback->reference,
]);
}
}
}Langkah 3: Daftarkan Listener (Laravel 10) โ
// app/Providers/EventServiceProvider.php
protected $listen = [
\Duitku\Laravel\Events\DuitkuPaymentReceived::class => [
\App\Listeners\UpdateOrderPaid::class,
],
\Duitku\Laravel\Events\DuitkuPaymentFailed::class => [
\App\Listeners\HandleFailedPayment::class,
],
];TIP
Laravel 11+ mendukung Event Discovery โ listener akan otomatis terdeteksi tanpa perlu didaftarkan, selama event di-type-hint dengan benar di method handle().
๐ Daftar Events yang Tersedia โ
| Event | Kapan Di-dispatch? |
|---|---|
DuitkuCallbackReceived | Setiap callback yang valid (baik sukses maupun gagal) |
DuitkuPaymentReceived | Hanya jika resultCode = '00' (pembayaran berhasil) |
DuitkuPaymentFailed | Jika resultCode โ '00' (pembayaran gagal/expired) |
Kapan pakai mana?
- Gunakan
DuitkuPaymentReceiveduntuk update order jadi "paid" - Gunakan
DuitkuPaymentFaileduntuk notifikasi admin atau kirim email reminder - Gunakan
DuitkuCallbackReceiveduntuk logging/audit trail (semua callback)
๐งช Testing Callback di Lokal โ
Duitku mengirim callback ke URL yang bisa diakses dari internet. Saat development di lokal, server kamu biasanya tidak bisa diakses dari luar. Solusinya:
Pakai ngrok (Gratis) โ
# 1. Install ngrok: https://ngrok.com
# 2. Jalankan tunnel ke port Laravel kamu
ngrok http 8000
# 3. Kamu akan dapat URL seperti:
# https://abc123.ngrok.io
# 4. Set URL callback di Dashboard Duitku:
# https://abc123.ngrok.io/api/duitku/callbackPakai expose (Alternatif) โ
expose share http://localhost:8000TIP
Jangan lupa update URL callback di Dashboard Duitku setiap kali URL ngrok berubah!
Selanjutnya, pelajari cara menangani error dengan Error Handling. ๐ก๏ธ