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
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.
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:
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.
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
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
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;
}
}
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' => '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.
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.
@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.
<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
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/.