Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API

By | 16 November 2024

Hai. Tulisan ini sebenarnya merupakan dokumentasi pribadi saya, mengingat saya cukup banyak menggunakan Midtrans sebagai payment gateway pada proyek-proyek yang kerjakan, baik yang menggunakan Laravel, Node JS, maupun Spring Boot (baru-baru ini).

Midtrans, yang sekarang sudah jadi milik Gojek, merupakan payment gateway favorit saya untuk menerima pembayaran dan saya selalu menyarankan ke klien untuk menggunakan Midtrans dibandingkan payment gateway lain. Bukan hanya karena saya sudah familiar, tapi kemudahan yang diberikan, pilihan channel pembayaran, biaya yang masuk akal, stabil, simulator pembayaran, dan yang paling penting: penarikan uang yang cepat.

Sejauh ini, ada dua metode integrasi yang sudah saya gunakan, yaitu SNAP API dan Core API, tergantung permintaan klien. Apa bedanya? Jika menggunakan SNAP API, kita akan menampilkan interface yang diberikan Midtrans seperti pemilihan metode pembayaran, detail pembelian hingga nomor rekening pembayaran di website kita. Tampilannya seperti ini:

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 1. Tampilan SNAP API (Dokumentasi Midtrans)

Kelebihannya, integrasi SNAP API sangat mudah dan cepat. Kekurangannya, tampilannya cuma itu-itu saja dan sama di semua website. Kita memang bisa mengubah font, warna hingga logo, tapi tampilan dasarnya tetap sama. Orang akan mudah tau jika itu menggunakan Midtrans. Sebenarnya ini tidak bisa disebut sebagai kekurangan…

Jika ingin tampilan yang unik dengan desain sendiri, tidak ingin ada yang tau payment gateway apa yang kita gunakan, kita bisa menggunakan CORE API. Dengan CORE API, kita bisa membuat alur pembayaran sendiri, hingga desain unik yang tidak akan sama dengan website lain. Tapi implementasi CORE API cukup rumit.

Di posting ini, saya akan menulis mengenai integrasi Midtrans ke Laravel 11 menggunakan SNAP API. Sebenarnya bisa digunakan di Laravel 10, 9, 8, 7 bahkan 6 dengan kode yang hampir sama. Saya pertama mengintegrasikan Midtrans saat Laravel masih versi 6, dan ternyata kodenya masih sama saja dengan versi 11.

Saya akan coba menjelaskan teori, implementasi kode, dan repository yang bisa kamu clone. Disini, saya berasumsi bahwa kamu sudah mengetahui basic Laravel seperti route, model, controller, view, konfigurasi, hingga middleware.

Konsep yang harus dipahami

Pertama, saya ingin menjelaskan konsep yang perlu kita pahami. Dengan memahami konsep ini, integrasi akan menjadi lebih mudah, bahkan jika menggunakan teknologi lain seperti Node JS atau Java sekalipun, karena konsepnya sama.

1. Snap Token

Semua website tentu memiliki unique identifier untuk data-data yang dimiliki. Dalam kasus e-commerceidentifier ini dapat berupa order id, uuid, atau auto generated primary key. Nah, demikian juga Midtrans. Untuk menggunakan SNAP API, butuh sebuah unique identifier yang disebut snap token. Snap token merupakan token pembayaran yang berbeda untuk setiap order. Misalnya, customer Sindi melakukan pembelian lipstik dan parfum, kemudian website kita memberikan id 202411081235ABCD untuk order tersebut, maka, order 202411081235ABCD akan memiliki snap token tersendiri, begitu juga order-order lain.

Jadi, snap token merupakan identifier pembayaran untuk suatu order tertentu.

2. Pembuatan SNAP Token

Pertama, saya anggap bahwa website kamu sudah memiliki sistem order dan sudah dapat mencatat data order seperti total harga, item, ongkos kirim, dan lain sebagainya. Jika sudah, pada saat customer pertama kali akan melakukan pembayaran, back-end kita mengirimkan data-data order seperti total harga, data customer, item pembelian dan data lain ke Midtrans melalui API. Kemudian Midtrans akan memberikan balasan berupa snap token.

Untuk apa tokennya? Pada tabel orders di database, kamu wajib membuat kolom bernama “snap_token” (bisa juga membuat tabel khusus dengan relasi one to many). Awalnya snap_token ini masih null, namun kemudian akan diupdate dengan token yang diberikan Midtrans.

Jadi, untuk membuat snap token, sistem kita perlu mengirimkan data-data yang diperlukan ke Midtrans, kemudian Midtrans akan memberikan snap token untuk digunakan sebagai identifier.

3. Alur pembayaran

Setelah mendapatkan snap token, kita perlu menggunakan token tersebut pada kode Javascript yang digunakan untuk menampilkan interface seperti pada gambar 1. Dengan token ini, interface tersebut akan dapat menampilkan data customer, data order dan total harga sesuai dengan data yang kita kirimkan saat membuat snap token.

Kemudian, customer dapat memilih salah satu channel pembayaran yang tersedia, misalnya GoPay, transfer bank, virtual account hingga pembayaran melalui Indomaret. Anggap saja customer memilih membayar menggunakan virtual account, dan sudah mendapatkan kode pembayaran, customer akan melakukan pembayaran misalnya melalui aplikasi mobile banking.

4. Callback

Setelah customer berhasil melakukan pembayaran, Midtrans akan mengirim notifikasi ke website kita yang disebut dengan callbackCallback ini berisi data-data pembayaran, dan yang paling penting adalah status pembayaran, seperti berhasil, batal, expire, fraud, dan sebagainya. Nah, kita perlu menangani status ini. Misalnya jika berhasil, kita perlu memperbarui status order menjadi misalnya “sudah dibayar” dan melanjutkan proses selanjutnya, jika expire, kita perlu memperbarui status order menjadi misalnya “kadaluarsa” dan mengulang proses pembayaran.

Dalam proses ini, kita perlu menyiapkan endpoint khusus, misalnya: /payment/midtrans-callback bebas apa endpointnya dan harus bisa diakses publik (tidak ada otorisasi).

Implementasi

Pertama, silahkan buat akun di Midtrans. Disini, kita akan menggunakan mode sandbox selama tahap pengembangan dan uji coba.

Supaya tidak terlalu panjang, saya hanya akan menjelaskan mulai dari pembuatan tabel orders hingga pembayaran saja. Untuk ENV, file konfigurasi dan pengecualian CSRF, silahkan lihat di repository berikut. Jangan lupa berikan star ya, terima kasih!

https://github.com/mulyosyahidin/laravel-midtrans

Access Key

Pertama, kita perlu mendapatkan access key. Ada dua access key, yaitu client key dan server keyServer key akan digunakan untuk berkomunikasi di sisi back-end seperti proses pembuatan token, sedangkan client key digunakan disisi front-end.

Untuk mendapatkan access key, silahkan buka Pengaturan > Access Key seperti pada gambar 2.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 2. Access Key

Silahkan catat merchant id, client key dan server key yang diberikan.

Membuat Tabel Orders

Singkatnya, tabel orderini berisi data-data order seperti id, order id, dan total harga. Untuk item order, akan disimpan di tabel order_items, sedangkan data pembayaran Midtrans akan disimpan di tabel payments dengan relasi one to many dari tabel orders, ini untuk mengantisipasi jika pembayaran kadaluarsa, maka kita bisa membuat snap token baru tanpa menghapus token yang lama.

Membuat tabel orders

Di tabel orders saya hanya membuat data basic saja. Silahkan sesuaikan dengan kebutuhan kamu, misalnya menambahkan kolom user_id untuk menandai siapa yang melakukan order.

php artisan make:model Order -m
Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->string('order_id', 32)->unique();
    $table->double('total_price', 8, 2);
    $table->enum('status', ['pending', 'processing', 'completed', 'cancelled'])->default('pending');
    $table->enum('payment_status', ['unpaid', 'paid', 'failed'])->default('unpaid');
    $table->timestamps();
});

Membuat tabel order_items

Tabel order_items akan menyimpan item-item order. Umumnya data ini berasal dari tabel products, namun disini saya akan membuatnya statis seperti nama, harga dan kuantitasnya. Silahkan sesuaikan dengan kebutuhan kamu.

php artisan make:model Order_item -m
Schema::create('order_items', function (Blueprint $table) {
   $table->id();
   $table->foreignIdFor(\App\Models\Order::class, 'order_id')->nullable()->constrained()->cascadeOnDelete();
   $table->string('product_name');
   $table->double('price', 8, 2);
   $table->integer('quantity');
   $table->timestamps();
});

Membuat tabel payments

Sebenarnya, kolom snap_token bisa saja disimpan di tabel orders, namun terkadang saya memiliki kebutuhan untuk menyimpan log pembayaran customer yang akan digunakan untuk kebutuhan lain seperti analisa perilaku customer hingga profiling.

php artisan make:model Payment -m
Schema::create('payments', function (Blueprint $table) {
  $table->id();
  $table->foreignIdFor(\App\Models\Order::class, 'order_id')->nullable()->constrained()->cascadeOnDelete();
  $table->string('snap_token')->nullable();
  $table->string('status', 32)->default('pending');
  $table->dateTime('expired_at')->nullable();
  $table->dateTime('paid_at')->nullable();
  $table->timestamps();
});

Untuk melihat model Order, Order_item dan Payment, silahkan buka repository Github nya.

Membuat data dummy

Untuk keperluan uji coba, saya membuat data dummy berisi data-data order. Karena kodenya cukup panjang, silahkan buka pada tautan berikut: DummyOrderSeeder.php

Midtrans Service

Midtrans Service merupakan utilitas untuk mengakses API Midtrans. Pertama, install dependency yang dibutuhkan:

composer require midtrans/midtrans-php

Midtrans Service berisi fungsi-fungsi seperti pembuatan token, validasi signature key, mengambil data order dari callback dan penentuan status. Buat filenya di: app/Services/MidtransService.php dan isi dengan kode berikut: MidtransService.php

Membuat Controller Order

Controller ini akan berada di folder app\Http\Controllers\Customer.

php artisan make:controller Customer/OrderController
class OrderController extends Controller
{
    public function index()
    {
        $orders = Order::all();

        return view('customer.orders.index', compact('orders'));
    }

    /**
     * @throws \Exception
     */
    public function show(MidtransService $midtransService, Order $order)
    {
        // get last payment
        $payment = $order->payments->last();

        if ($payment == null || $payment->status == 'EXPIRED') {
            $snapToken = $midtransService->createSnapToken($order);

            $order->payments()->create([
                'snap_token' => $snapToken,
                'status' => 'PENDING',
            ]);
        } else {
            $snapToken = $payment->snap_token;
        }

        return view('customer.orders.show', compact('order', 'snapToken'));
    }
}

Membuat View

Selanjutnya, buatkan view index.blade.php dan show.blade.php. Untuk isinya, silahkan lihat di: https://github.com/mulyosyahidin/laravel-midtrans/tree/main/resources/views/customer/orders

Saya akan menjelaskan sedikit mengenai show.blade.php Perhatikan bagian berikut:

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 3. Javascipt SNAP API

Pada baris ke 62, kita perlu memberikan client key yang tadi sudah kita dapatkan dari dashboard Midtrans. Kemudian, pada baris ke-69 kita menggunakan snap token yang dihasilkan oleh controller.

Membuat Callback

Untuk membuat callback, buatkan controller baru.

php artisan make:controller PaymentController
class PaymentController extends Controller
{
    public function midtransCallback(Request $request, MidtransService $midtransService)
    {
        if ($midtransService->isSignatureKeyVerified()) {
            $order = $midtransService->getOrder();

            if ($midtransService->getStatus() == 'success') {
                $order->update([
                    'status' => 'processing',
                    'payment_status' => 'paid',
                ]);

                $lastPayment = $order->payments()->latest()->first();
                $lastPayment->update([
                    'status' => 'PAID',
                    'paid_at' => now(),
                ]);
            }

            if ($midtransService->getStatus() == 'pending') {
                // lakukan sesuatu jika pembayaran masih pending, seperti mengirim notifikasi ke customer
                // bahwa pembayaran masih pending dan harap selesai pembayarannya
            }

            if ($midtransService->getStatus() == 'expire') {
                // lakukan sesuatu jika pembayaran expired, seperti mengirim notifikasi ke customer
                // bahwa pembayaran expired dan harap melakukan pembayaran ulang
            }

            if ($midtransService->getStatus() == 'cancel') {
                // lakukan sesuatu jika pembayaran dibatalkan
            }

            if ($midtransService->getStatus() == 'failed') {
                // lakukan sesuatu jika pembayaran gagal
            }

            return response()
                ->json([
                    'success' => true,
                    'message' => 'Notifikasi berhasil diproses',
                ]);
        } else {
            return response()->json([
                'message' => 'Unauthorized',
            ], 401);
        }
    }
}

Pada callback ini, kita bisa menangani status seperti berhasil, pending, expire, batal dan gagal. Method ini seharusnya hanya bisa diakses oleh Midtrans, karena kita memberikan validasi berupa pemeriksaan isSignatureKeyVerified() sehingga jika seseorang tidak tahu signature key nya, maka tidak akan bisa mengakses callback ini.

Routes

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/customer/orders', [App\Http\Controllers\Customer\OrderController::class, 'index'])->name('customer.orders.index');
Route::get('/customer/orders/{order}', [App\Http\Controllers\Customer\OrderController::class, 'show'])->name('customer.orders.show');

Route::post('/payment/midtrans-callback', [App\Http\Controllers\PaymentController::class, 'midtransCallback']);

Silahkan sesuaikan route nya dengan kebutuhan kamu, misalnya memberikan middleware pada route order. Harap diingat, route untuk callback tidak boleh memiliki authentikasi apapun.

Uji Coba

Jika kamu perhatikan, saat ini kita masih melakukan development di localhost. Oleh karena itu, Laravel kita juga masih berada di komputer lokal kita dan belum bisa diakses dari internet.

Masalahnya dimana? Perhatikan pada route untuk callback Midtrans, yang akan menghasilkan: http://localhost:8000/payment/midtrans-callback. Route ini, tentu tidak bisa diakses oleh Midtrans karena masih berada di localhost. Kita perlu mengupload kode kita ke server supaya bisa diakses dari internet dan Midtrans bisa mengirimkan callback.

Namun, selama tahap development, kita bisa menggunakan aplikasi tunnel seperti localhost.run, atau Ngrok.

Ngrok

Ngrok adalah aplikasi dimana kita bisa membuat localhost kita menjadi online dapat diakses siapapun dari internet. Silahkan install Ngrok di komputer kamu. Ngrok: https://ngrok.com/

Membuat local server

Pertama, jalankan php artisan serve untuk membuat Laravel kita berjalan pada port 8000. Jika kamu menggunakan Laragon di Windows, kamu tetap perlu menjalankan perintah ini.

Menjalankan Ngrok

Jika Ngrok sudah terinstal, jalankan perintah: ngrok http 8000, maka Ngrok akan memberikan URL yang bisa diakses dari internet. Untuk membuka dasbor Ngrok, buka localhost:4040 di browser kamu. Berikut tampilan dasbor Ngrok jika belum ada request yang masuk.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 4. Dasbor Ngrok

Setting URL Notifikasi di Midtrans

Selanjutnya, kita perlu mengatur URL Notifikasi di dasbor Midtrans.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 5. Setting URL Notifikasi di dasbor Midtrans

Jika web kamu online, jangan lupa domain yang diberikan Ngrok menjadi domain kamu.

Uji coba

Pertama, akses localhost:8000/customer/orders untuk melihat semua data order. Kemudian pilih salah satu order.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 6. Status Order Belum Dibayar

Disini, terdapat order dengan ID ORD11641731018669 yang belum dibayar. Klik tombol Bayar Sekarang untuk menampilkan interface SNAP API.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 7. Interface SNAP API
Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 8. Pembayaran melalui BCA VA

Pada gambar 8, saya memilih pembayaran melalui BCA VA.

Melakukan simulasi pembayaran

Karena masih dalam tahap development, tentu kita tidak perlu benar-benar melakukan pembayaran. Untuk melakukan pembayaran, kita bisa menggunakan simulator pembayaran yang disediakan Midtrans. Silahkan buka di: https://simulator.sandbox.midtrans.com/bca/va/payment

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 9. Midtrans Payment Simulator

Pada kolom nomor VA, silahkan paste nomor VA yang didapatkan dari SNAP API. Kamu juga bebas memilih channel pembayaran manapun. Kemudian klik Inquire. Pada halaman konfirmasi selanjutnya, klik saya Pay untuk melakukan pembayaran.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 10. Callback Midtrans

Silahkan buka dasbor Ngrok kamu. Perhatikan bahwa Midtrans mengirimkan callback dengan data-data seperti channel pembayaran, status, dan lain sebagainya. Data-data itulah yang diproses di PaymentController tadi.

Selanjutnya, refresh halaman order tadi, dan lihatlah bahwa status pembayaran sudah berubah menjadi paid secara otomatis.

Mengintegrasikan Midtrans ke Laravel 11 dengan SNAP API
Gambar 11. Status order setelah pembayaran

Untuk diperhatikan

Ada beberapa hal yang perlu kamu ingat. Pertama, jika kamu mematikan tunnel Ngrok atau mematikan komputer, dan kamu perlu melakukan pembayaran lagi, kamu perlu membuat tunnel baru dengan perintah yang sama, yaitu: ngrok http 80008000 ini dapat diganti dengan port berapapun Laravel kamu berjalan. Jika sudah mendapatkan URL tunnel baru, jangan lupa update URL Notifikasinya di dasbor Midtrans.

Kedua, jika sudah dalam tahap produksi, jangan lupa untuk mengubah access key di file environment.

Selesai…

Sampai disini, Midtrans seharusnya sudah bisa diintegrasikan ke Laravel. Implementasi tersebut cukup bahkan sampai ke tahap produksi sekalipun untuk menerima pembayaran nyata.

Jika kamu paham konsepnya, Midtrans bisa diimplementasikan untuk kasus lain misal website donasi, spp sekolah, hingga iuran warga.

Jika terdapat error atau ada hal yang belum dipahami, silahkan tanyakan di kolom komentar.

Tinggalkan Balasan