DEV Community

Cover image for Integrate Midtrans in Laravel with Snap API: Part 1
Martin Mulyo Syahidin
Martin Mulyo Syahidin

Posted on • Edited on

Integrate Midtrans in Laravel with Snap API: Part 1

How to Integrate Midtrans Payment Gateway in Laravel 8

Midtrans is one of the popular payment gateways in Indonesia that has many users. Midtrans can help make payment receipts easier, integrated, automated, and has many payment options. By using Midtrans, customers can choose to pay through many channels, such as credit cards, bank accounts (virtual accounts), Alfamart outlets, to using the GoPay application. With that many choices, it will certainly make it easier for customers to make payments.

Midtrans integration is also very easy. Let's say on our online store website, on the order data there is a payment_status flag with the contents: waiting for payment, already paid, expired (and other payment status). Usually, the customer will confirm the payment to the admin by uploading the payment data, then the admin will update the payment status and continue the order process. But with Midtrans, when the customer has made a payment, Midtrans will notify our website that the customer has made a payment. Then, the payment status will automatically change to already paid.

Very interesting isn't it? This tutorial will be divided into sections so it won't be too long. Every code change I save on GitHub and can be seen in the following repository: https://github.com/mulyosyahidin/laravel-midtrans. Please do clone the repository.

The integration that we will do is using SNAP, so customers don't need to leave our website. Here's an example of what it looks like when using SNAP.

Creating an Order Table

Here I will not explain the whole online shop system. But straight to the point. Here, I created an orders table which of course will contain order data. The key is, in the orders table we need to create a special column with the name snap_token which will contain the token generated by the snap. The snap token is a UUID (36 characters) generated by Midtrans that is used as an order marker. The following is the structure of the orders table.

php artisan make:model Order -m
Enter fullscreen mode Exit fullscreen mode
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'])->comment('1=menunggu pembayaran, 2=sudah dibayar, 3=kadaluarsa');
    $table->string('snap_token', 36)->nullable();
    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

Please add another column as needed, here the mandatory fields are the total price, payment status and snap token. You can add other columns such as user_id, order_status and others.

Creating Dummy Data in the Order Table

For experimental material, I will create a dummy using the factory, please run the following command.

php artisan make:factory OrderFactory
Enter fullscreen mode Exit fullscreen mode

Then in the databases/factories/OrderFactory.php file change the definition() method to be as follows.

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

Then in the databases/seeders/DatabaseSeeder.php file add the factory earlier to the run() method, so it will look like this:

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

Next, in the terminal run the seeder command.

php artisan db:seed
Enter fullscreen mode Exit fullscreen mode

Up to this point, we already have 10 dummy data that will be used for the experiment.

Creating Controllers

Next is to create a controller to manage orders. For convenience, I use a resource controller with bindings to the Order model that was created earlier.

php artisan make:controller OrderController --model=Order
Enter fullscreen mode Exit fullscreen mode

Then in the web.php route add the following route resource:

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

Create Snap Token for Order

In the show() method, we have to check whether the snap_token column already has contents or not (still NULL). If it is still NULL, we must generate the token by communicating with the API provided by Midtrans.

Setting Up Configuration

Before continuing, make sure you have the access key that Midtrans provided. This Access Key can be found on the Midtrans dashboard under Settings > Access Key. Here, I'm still using the Sandbox environment.
Midtrans Access Key

After getting the three data, then add a new key in the .env file. At the end of the .env file, add the following key.

MIDTRANS_MERCHAT_ID=xxxxxxxxxx
MIDTRANS_CLIENT_KEY=SB-Mid-client-xxxxxxxxxx
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxxxxxxxx
Enter fullscreen mode Exit fullscreen mode

Then create a new configuration file with the name midtrans.php in the config/ folder and fill it with the following array.

<?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,
];
Enter fullscreen mode Exit fullscreen mode

Installing package midtrans/midtrans-php

Package midtrans/midtrans-php is a package provided by Midtrans to use the Midtrans API. To install, run the following command in terminal.

composer require midtrans/midtrans-php
Enter fullscreen mode Exit fullscreen mode

Creating Service Layers

Next, we have to create a new service layer to separate the application logic from the Midtrans logic, so that later it will be easier for code maintenance. In the app/ folder, create a new folder with the name Services, and create another new folder with the name Midtrans, also create the 3 necessary files, namely Midtrans.php, CreateSnapTokenService.php and CallbackService.php
Laravel Midtrans Service Layer

In the Midtrans.php file, write the following code.

<?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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, in the CreateSnapTokenService.php file write the following code.

<?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' => 'Nama',
                'email' => 'Email',
                'phone' => '081234567890',
            ]
        ];

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

        return $snapToken;
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above service, the order_id key must contain a unique order key, it can be a primary key or UUID, the gross_amount key contains the total amount to be paid by the customer.

The item details in the order can also be changed in the item_details key. This key contains an associative array that can be filled with data per item (product). You can change the id (primary key), price (unit price), quantity (order quantity) and name (product name). Remember, the total price that will be displayed to the customer is the result of the sum of the key price * quantity. In the item_details key, you can also add for example shipping costs, taxes, or other fees charged to the customer.

You can also change customer data in the customer_details key, this data can be obtained through relations in the Order model.

Next, in the OrderController.php controller, change the show() method and add the following code.

use App\Services\Midtrans\CreateSnapTokenService; // => put it at the top of the class

public function show(Order $order)
     {
         $snapToken = $order->snap_token;
         if (is_null($snapToken)) {
             // If snap token is still NULL, generate snap token and save it to database

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

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

         return view('orders.show', compact('order', 'snapToken'));
     }
Enter fullscreen mode Exit fullscreen mode

The show() method can be adapted to your application logic. The code snippet above is to create a snap token by utilizing the service that was previously created. Next, open the order detail view file.

In the view, we need to create a payment execution button (see video). When the button is clicked, it will display a Midtrans payment pop up. Here's an example of the button I made in the video above.

@if ($order->payment_status == 1)
     <button class="btn btn-primary" id="pay-button">Pay Now</button>
@else
     Payment successful
@endif
Enter fullscreen mode Exit fullscreen mode

Notice, on the button I provide an id attribute with a pay-button value. This attribute will later be used for binding by Javascript. Next, still in the view add the following Javascript.

<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>
Enter fullscreen mode Exit fullscreen mode

In the pay() method above, there are onSuccess, onPending and onError events. This event can be used to manage response data from Midtrans when the customer closes the payment pop up.

So far, we have succeeded in making the Snap API integration on our website. Here, customers can make payments through the selected payment channel. Payment data can be seen on the Midtrans dashboard.

To view the source code to date, please open the following repository: https://github.com/mulyosyahidin/laravel-midtrans/tree/7a2f45abfe7c1c00db711713bf8f77231d6c8f18

Next, we will create a callback. So, after the customer has made a successful payment, Midtrans will send a notification (callback) to our website. From the notification, we can change the order data. For example, if the payment is successful, we can change the status of the payment to already paid. Or if the customer has not made a payment until the specified time limit, Midtrans will also send an expiration notification. In part 2 of the post, we will talk about these callbacks.

Read in Indonesian on the https://jurnalmms.web.id/laravel/integrasi-midtrans-dan-laravel-bagian-1/.

Top comments (3)

Collapse
 
rizkytegar profile image
🎉 Rizky Tegar Pratama

where is part 2, part 3 ?

Collapse
 
martinms profile image
Martin Mulyo Syahidin

I'm sorry for it, you can check it here jurnalmms.web.id/laravel/integrasi...

Collapse
 
ndriodna profile image
eeQ

kalo error gini gimana ya

Image description