Integrasi Midtrans dan Laravel 8 Menggunakan Snap: Bagian 1

By | 25 Agustus 2021

Midtrans adalah salah satu payment gateway populer yang memiliki banyak pengguna. Midtrans dapat membantu penerimaan pembayaran menjadi lebih mudah, terintegrasi, otomatis, dan memiliki banyak pilihan pembayaran. Dengan menggunakan Midtrans, customer dapat memilih pembayaran melalui banyak channel, seperti kartu kredit, rekening bank (virtual account), gerai Alfamart, hingga menggunakan aplikasi GoPay. Dengan pilihan sebanyak itu, tentu akan memudahkan customer dalam pembayaran.

Integrasi Midtrans juga sangat mudah. Katakanlah di website toko online kita, pada data order terdapat flag payment_status dengan isi: menunggu pembayaran, sudah dibayar, kadaluarsa (dan status pembayaran lain). Biasanya, customer akan mengkonfirmasi pembayaran kepada admin dengan mengupload data pembayaran, kemudian admin akan memperbarui status pembayaran dan melanjutkan proses order. Tetapi dengan Midtrans, ketika customer sudah melakukan pembayaran, Midtrans akan memberikan notifikasi kepada website kita bahwasannya customer telah melakukan pembayaran. Kemudian, status pembayaran akan otomatis berubah menjadi sudah dibayar.

Sangat menarik bukan? Tutorial ini akan dibagi menjadi beberapa bagian sehingga tidak terlalu panjang. Setiap perubahan kode saya simpan di GitHub dan bisa dilihat di repository berikut: https://github.com/mulyosyahidin/laravel-midtrans. Silahkan lakukan clone repository tersebut.

Integrasi yang akan kita lakukan adalah menggunakan SNAP, sehingga customer tidak perlu pergi dari website kita. Berikut contoh tampilan jika menggunakan SNAP.

Membuat Tabel Order

Disini saya tidak akan menjelaskan sistem toko onlinenya secara keseluruhan. Tetapi langsung ke bagian yang terpentingnya. Disini, saya membuat sebuah tabel orders yang tentu akan berisi data order. Kuncinya adalah, pada tabel orders kita perlu membuat kolom khusus dengan nama snap_token yang nantinya akan berisi token yang dihasilkan snap. Snap token adalah sebuah UUID (36 karakter) yang dihasilkan oleh Midtrans yang digunakan sebagai penanda order. Berikut struktur tabel orders.

php artisan make:model Order -m
Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->string('number', 16);
    $table->decimal('total_price', 10, 2);
    $table->enum('payment_status', ['1', '2', '3', '4'])->comment('1=menunggu pembayaran, 2=sudah dibayar, 3=kadaluarsa, 4=batal');
    $table->string('snap_token', 36)->nullable();
    $table->timestamps();
});

Silahkan tambahkan kolom lain sesuai kebutuhan, disini kolom yang wajib ada adalah total harga, status pembayaran dan snap token. Kamu bisa menambah kolom lain seperti user_id, order_status dan lain-lain.

Membuat Data Dummy di Tabel Order

Untuk bahan percobaan, saya akan membuat dummy menggunakan factory, silahkan jalankan perintah berikut.

php artisan make:factory OrderFactory

Kemudian pada file databases/factories/OrderFactory.php ubah method definition() menjadi seperti berikut

public function definition()
    {
        return [
            'number' => $this->faker->randomNumber(8),
            'total_price' => $this->faker->numberBetween(25000, 200000),
            'payment_status' => 1,
        ];
    }

Kemudian pada file databases/seeders/DatabaseSeeder.php tambahkan factory tadi ke method run(), sehingga akan menjadi seperti berikut:

public function run()
    {
        // \App\Models\User::factory(10)->create();
        \App\Models\Order::factory(10)->create();
    }

Selanjutnya, pada terminal jalankan perintah seeder.

php artisan db:seed

Sampai disini, kita sudah punya 10 data dummy yang akan digunakan untuk percobaan.

Membuat Controller

Selanjutnya adalah membuat controller untuk mengelola order. Supaya mudah, saya menggunakan resource controller dengan binding ke model Order yang tadi sudah dibuat.

php artisan make:controller OrderController --model=Order

Kemudian pada route web.php tambahkan route resource berikut:

Route::resource('orders', OrderController::class)->only(['index', 'show']);

Membuat Snap Token untuk Order

Pada method show(), kita harus memeriksa apakah kolom snap_token sudah memiliki isi atau belum (masih NULL). Jika masih NULL, kita harus membuat token tersebut dengan berkomunikasi dengan API yang disediakan Midtrans.

Menyiapkan Konfigurasi

Sebelum melanjutkan, pastikan sudah memiliki access key yang diberikan Midtrans. Access Key ini bisa didapatkan di dasbor Midtrans pada bagian Pengaturan > Access Key. Disini, saya masih menggunakan environment Sandbox.

Access key pada dasbor Midtrans
Access key Midtrans dengan environment Sandbox

Setelah mendapatkan ketiga data tersebut, selanjutnya tambahkan key baru di file .env. Pada ujung file .env, tambahkan key berikut.

MIDTRANS_MERCHAT_ID=xxxxxxxxxx
MIDTRANS_CLIENT_KEY=SB-Mid-client-xxxxxxxxxx
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxxxxxxxx

Kemudian buat sebuah file konfigurasi baru dengan nama midtrans.php di folder config/ dan isikan dengan array berikut.

<?php

return [
	'mercant_id' => env('MIDTRANS_MERCHAT_ID'),
	'client_key' => env('MIDTRANS_CLIENT_KEY'),
	'server_key' => env('MIDTRANS_SERVER_KEY'),

	'is_production' => false,
	'is_sanitized' => false,
	'is_3ds' => false,
];

Menginstal package midtrans/midtrans-php

Package midtrans/midtrans-php merupakan package yang disediakan oleh Midtrans untuk menggunakan API Midtrans. Untuk menginstal, jalankan perintah berikut di terminal.

composer require midtrans/midtrans-php

Membuat Service Layer

Selanjutnya, kita harus membuat sebuah service layer baru untuk memisahkan logic aplikasi dengan logic Midtrans, sehingga nantinya akan memudahkan untuk pemeliharaan kode. Pada folder app/, buatlah folder baru dengan nama Services, dan buat lagi folder baru dengan nama Midtrans, buat juga 3 file yang diperlukan, yaitu Midtrans.php, CreateSnapTokenService.php dan CallbackService.php

Service layer untuk Midtrans

Pada file Midtrans.php, tuliskan kode berikut.

<?php

namespace App\Services\Midtrans;

use Midtrans\Config;

class Midtrans {
	protected $serverKey;
	protected $isProduction;
	protected $isSanitized;
	protected $is3ds;

	public function __construct()
	{
		$this->serverKey = config('midtrans.server_key');
		$this->isProduction = config('midtrans.is_production');
		$this->isSanitized = config('midtrans.is_sanitized');
		$this->is3ds = config('midtrans.is_3ds');

		$this->_configureMidtrans();
	}

	public function _configureMidtrans()
	{
		Config::$serverKey = $this->serverKey;
		Config::$isProduction = $this->isProduction;
		Config::$isSanitized = $this->isSanitized;
		Config::$is3ds = $this->is3ds;
	}
}

Kemudian, pada file CreateSnapTokenService.php tulis kode berikut.

<?php

namespace App\Services\Midtrans;

use Midtrans\Snap;

class CreateSnapTokenService extends Midtrans
{
	protected $order;

	public function __construct($order)
	{
		parent::__construct();

		$this->order = $order;
	}

	public function getSnapToken()
	{
		$params = [
			'transaction_details' => [
				'order_id' => $this->order->number,
				'gross_amount' => $this->order->total_price,
			],
			'item_details' => [
				[
					'id' => 1,
					'price' => '150000',
					'quantity' => 1,
					'name' => 'Flashdisk Toshiba 32GB',
				],
				[
					'id' => 2,
					'price' => '60000',
					'quantity' => 2,
					'name' => 'Memory Card VGEN 4GB',
				],
			],
			'customer_details' => [
				'first_name' => 'Martin Mulyo Syahidin',
				'email' => '[email protected]',
				'phone' => '081234567890',
			]
		];

		$snapToken = Snap::getSnapToken($params);

		return $snapToken;
	}
}

Pada service diatas, pada key order_id harus berisi kunci unik order, bisa primary key atau UUID, key gross_amount berisi total jumlah yang harus dibayar customer.

Detail item dalam order juga bisa diubah pada key item_details. Key ini berisi asosiatif array yang bisa diisi dengan data per item (produk). Kamu bisa mengubah id (primary key), price (harga satuan), quantity (jumlah order) dan name (nama produk). Ingat, total harga yang akan ditampilkan kepada customer adalah hasil penjumlahan key price * quantity. Pada key item_details, bisa juga ditambahkan misalnya biaya pengiriman, pajak, atau biaya lain yang dibebankan kepada customer.

Kamu juga bisa mengubah data customer pada key customer_details, data ini bisa didapatkan melalui relasi pada model Order.

Selanjutnya, pada controller OrderController.php, ubah method show() dan tambahkan kode berikut.

use App\Services\Midtrans\CreateSnapTokenService; // => letakkan pada bagian atas class

public function show(Order $order)
    {
        $snapToken = $order->snap_token;
        if (empty($snapToken)) {
            // Jika snap token masih NULL, buat token snap dan simpan ke database

            $midtrans = new CreateSnapTokenService($order);
            $snapToken = $midtrans->getSnapToken();

            $order->snap_token = $snapToken;
            $order->save();
        }

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

Method show() tersebut dapat disesuaikan dengan logic aplikasi kamu. Potongan kode diatas adalah untuk membuat snap token dengan memanfaatkan service yang sebelumnya sudah dibuat. Selanjutnya, buka file view detail order.

Pada view, kita perlu membuat sebuah tombol eksekusi pembayaran (lihat video). Ketika tombol tersebut diklik, akan menampilkan pop up pembayaran Midtrans. Berikut contoh tombol yang saya buat dalam video diatas.

@if ($order->payment_status == 1)
    <button class="btn btn-primary" id="pay-button">Bayar Sekarang</button>
@else
    Pembayaran berhasil
@endif

Perhatikan, pada tombol tersebut saya memberikan atribut id dengan nilai pay-button. Atribut ini nantinya akan digunakan untuk binding oleh Javascript. Selanjutnya, masih pada view tambahkan Javascript berikut.

<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="{{ config('midtrans.client_key') }}"></script>
    <script>
        const payButton = document.querySelector('#pay-button');
        payButton.addEventListener('click', function(e) {
            e.preventDefault();

            snap.pay('{{ $snapToken }}', {
                // Optional
                onSuccess: function(result) {
                    /* You may add your own js here, this is just example */
                    // document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
                    console.log(result)
                },
                // Optional
                onPending: function(result) {
                    /* You may add your own js here, this is just example */
                    // document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
                    console.log(result)
                },
                // Optional
                onError: function(result) {
                    /* You may add your own js here, this is just example */
                    // document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
                    console.log(result)
                }
            });
        });
    </script>

Pada method pay() diatas, terdapat event onSuccess, onPending dan onError. Event tersebut bisa digunakan untuk mengelola data response dari Midtrans ketika customer menutup pop up pembayaran.

Sampai disini, kita sudah berhasil membuat integrasi Snap API pada website kita. Disini, customer sudah bisa melakukan pembayaran melalui channel pembayaran yang dipilih. Data pembayaran dapat kita lihat di dasbor Midtrans.

Untuk melihat source code sampai saat ini, silahkan buka repository berikut: https://github.com/mulyosyahidin/laravel-midtrans

Selanjutnya, kita akan membuat callback. Jadi, setelah customer berhasil melakukan pembayaran, Midtrans akan mengirimkan notifikasi (callback) ke web kita. Dari notifikasi tersebut, kita bisa mengubah data order. Misalnya jika pembayaran berhasil, kita bisa mengubah status pembayaran menjadi sudah dibayar. Atau jika customer belum melakukan pembayaran hingga batas waktu yang ditentukan, Midtrans juga akan mengirim notifikasi kadaluarsa. Pada posting bagian 2, kita akan membahas tentang callback tersebut.

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *