Integrate Midtrans in Laravel with Snap API: Part 1
Tags: midtrans, payment, gateway
Published: 2021-08-25 10:28:05

## 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](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.

{% youtube 8e5LmNLAWg0 %}

## 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
```

```php
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();
});
```

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
```

Then in the `databases/factories/OrderFactory.php` file change the definition() method to be as follows.
```php
public function definition()
    {
        return [
            'number' => $this->faker->randomNumber(8),
            'total_price' => $this->faker->numberBetween(25000, 200000),
            'payment_status' => 1,
        ];
    }
```

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();
    }
```

Next, in the terminal run the seeder command.
```
php artisan db:seed
```

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
```

Then in the `web.php` route add the following route resource:
```php
Route::resource('orders', OrderController::class)->only(['index', 'show']);
```

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epgj2uu0a5ki808tc4fm.png)

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
```

Then create a new configuration file with the name `midtrans.php` in the `config/` folder and fill it with the following array.
```php
<?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,
];
```

## 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
```

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvj6wepqt95kbautiksd.png)

In the `Midtrans.php` file, write the following code.
```php
<?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;
    }
}
```

Then, in the `CreateSnapTokenService.php` file write the following code.
```php
<?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' => 'mulyosyahidin95@gmail.com',
                'phone' => '081234567890',
            ]
        ];
 
        $snapToken = Snap::getSnapToken($params);
 
        return $snapToken;
    }
}
```

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.
```php
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'));
     }
```

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.
```php
@if ($order->payment_status == 1)
     <button class="btn btn-primary" id="pay-button">Pay Now</button>
@else
     Payment successful
@endif
```

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.
```php
<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>
```

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](https://github.com/mulyosyahidin/laravel-midtrans)

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/](MMS Journal blog).