DEV Community

Cover image for Midtrans and Laravel 8 Integration Using Snap: Part 2
Martin Mulyo Syahidin
Martin Mulyo Syahidin

Posted on

Midtrans and Laravel 8 Integration Using Snap: Part 2

Before continuing reading, please read part 1: Integration of Midtrans and Laravel 8 Using Snap: Part 1

In part 1, we have successfully integrated the Midtrans payment interface using Snap. Until there, customers can make payments through the various payment channels available. In this post, we will continue to create payment callbacks.

What are callbacks? In part 1, the customer is able to make a payment. For example using a virtual account. After the customer has made a successful payment, Midtrans will send a notification to our website that the customer has made a payment. Or, if the customer does not make a payment within the specified time limit, Midtrans will send a notification that the transaction has expired. Our job, is to receive the notification and process the notification.

For example, after the customer has successfully paid, we will receive a successful notification. From the notification, we can change the payment status of the order from previously waiting for payment to already paid. With this system, there is no need to manually confirm and change the payment status.

Create a Service Class

Sama seperti sebelumnya, logic utama akan kita pisahkan di service layer. Sebelumnya, kita sudah membuat file Midtrans.php, CreateSnapTokenService.php dan CallbackService.php, untuk callback akan kita tulis di file CallbackService.php

<?php

namespace App\Services\Midtrans;

use App\Models\Order;
use App\Services\Midtrans\Midtrans;
use Midtrans\Notification;

class CallbackService extends Midtrans
{
    protected $notification;
    protected $order;
    protected $serverKey;

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

        $this->serverKey = config('midtrans.server_key');
        $this->_handleNotification();
    }

    public function isSignatureKeyVerified()
    {
        return ($this->_createLocalSignatureKey() == $this->notification->signature_key);
    }

    public function isSuccess()
    {
        $statusCode = $this->notification->status_code;
        $transactionStatus = $this->notification->transaction_status;
        $fraudStatus = !empty($this->notification->fraud_status) ? ($this->notification->fraud_status == 'accept') : true;

        return ($statusCode == 200 && $fraudStatus && ($transactionStatus == 'capture' || $transactionStatus == 'settlement'));
    }

    public function isExpire()
    {
        return ($this->notification->transaction_status == 'expire');
    }

    public function isCancelled()
    {
        return ($this->notification->transaction_status == 'cancel');
    }

    public function getNotification()
    {
        return $this->notification;
    }

    public function getOrder()
    {
        return $this->order;
    }

    protected function _createLocalSignatureKey()
    {
        $orderId = $this->order->number;
        $statusCode = $this->notification->status_code;
        $grossAmount = $this->order->total_price;
        $serverKey = $this->serverKey;
        $input = $orderId . $statusCode . $grossAmount . $serverKey;
        $signature = openssl_digest($input, 'sha512');

        return $signature;
    }

    protected function _handleNotification()
    {
        $notification = new Notification();

        $orderNumber = $notification->order_id;
        $order = Order::where('number', $orderNumber)->first();

        $this->notification = $notification;
        $this->order = $order;
    }
}
Enter fullscreen mode Exit fullscreen mode

In that service, we capture the notification sent by Midtrans with the _handleNotification() method, then the notification result will be processed again. Here we also need to create a local signature key. This key is a local key so that notifications can be received. When sending notifications, Midtrans will also send a signature key, the key will be compared with the local signature key to verify whether the request is valid or not.

Creating a Callback Controller

To receive notifications, we will create a custom controller named PaymentCallbackController. In the terminal, type the following command:

php artisan make:controller PaymentCallbackController
Enter fullscreen mode Exit fullscreen mode

Then write the following code on the controller earlier.

<?php

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Http\Request;
use App\Services\Midtrans\CallbackService;

class PaymentCallbackController extends Controller
{
    public function receive()
    {
        $callback = new CallbackService;

        if ($callback->isSignatureKeyVerified()) {
            $notification = $callback->getNotification();
            $order = $callback->getOrder();

            if ($callback->isSuccess()) {
                Order::where('id', $order->id)->update([
                    'payment_status' => 2,
                ]);
            }

            if ($callback->isExpire()) {
                Order::where('id', $order->id)->update([
                    'payment_status' => 3,
                ]);
            }

            if ($callback->isCancelled()) {
                Order::where('id', $order->id)->update([
                    'payment_status' => 4,
                ]);
            }

            return response()
                ->json([
                    'success' => true,
                    'message' => 'Notification successfully processed',
                ]);
        } else {
            return response()
                ->json([
                    'error' => true,
                    'message' => 'Signature key not verified',
                ], 403);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this controller, we have to check whether the signature key is verified or not. Furthermore, in the service callback we have 3 methods, namely isSuccess() to check whether the transaction was successful, isExpire() to check whether the transaction has expired, isCancelled() to check whether the transaction was aborted. If successful, then update order status data to 2 (already paid), if expired, update order status data to 3 (expired), and if expired then update order status data to 3. You can also add other logic as needed.

Safety note
The program flow that I created is quite safe to use. Even if anyone knows the callback URL, that person will not be able to "shoot" it directly, because it needs to include an authentication header in the form of a signature key which is a combination of Order ID, status code, overall price and server key. So, don't let anyone know your Midtrans servey key.

Creating Routes

After creating the controller, we have to create a new route. Later Midtrans will send a post request to that route. In the web.php route, add the following route.

use App\Http\Controllers\PaymentCallbackController;

Route::post('payments/midtrans-notification', [PaymentCallbackController::class, 'receive']);
Enter fullscreen mode Exit fullscreen mode

Midtrans will send notifications to the address: http://mydomain.com/payments/midtrans-notification. But here we will not deploy to test, but will use tunel.

Creating a CSRF exception

Because it is on a web route, every post request will be protected by CSRF. This means that the route that we created above will not be accessible by Midtrans. For that, we have to exclude the route from CSRF protection. In the file app/Http/Middleware/VerifyCsrfToken.php add the above route to be excluded, so it will be like this.

protected $except = [
    'payments/midtrans-notification',
];
Enter fullscreen mode Exit fullscreen mode

If so, the next step is to test.

Installing Ngrok for Tunneling

Because our Laravel is still on localhost (still offline), of course Midtrans can't send notifications. For that, we have to online the Laravel. You can do this by uploading it to the server so that it can be online and accessible. But here I will not upload to the server, and still use localhost. The trick is to use Ngrok.

What is Ngrok? Ngrok is a tunelling application that can "online localhost". Ngrok can make our Laravel on localhost (offline) be online and can be accessed by anyone on the internet, just by typing the URL provided by Ngrok. Therefore, we will use Ngrok to test the Midtrans callback.

Notes
Ngrok is only used for offline testing, if it is ready to use and uploaded to the server, no configuration needs to be changed. Just replace the ngrok tunel address with your domain.

Download and install Ngrok

To download, please go to the ngrok.com webiste and navigate to the download page. Then download according to your operating system. For installation, please follow the guide on each operating system. If in Windows, just click-click as usual.
Ngrok download

If so, please register to get an auth token. If you have already registered, go to the Ngrok dashboard to get the auth token.
Setup Ngrok auth token

Please note down the token. Then in CMD / terminal type the following command

ngrok authtoken NGROK_AUTH_TOKEN
Enter fullscreen mode Exit fullscreen mode

Setup ngrok auth token

If successful, we will proceed to the next stage.

Creating Local Servers and Tunnels

To create a local server, we will use the command artisan serve from laravel with port 8080 (not 8000).

php artisan serve --port=8080
Enter fullscreen mode Exit fullscreen mode

Then a new local server will be created with port 8080. (http://localhost:8080)

Make tunel ngrok

To online the localhost that was previously created, in cmd type the following command:

ngrok http 8080
Enter fullscreen mode Exit fullscreen mode

This command will forward all requests to port 8080, and the Laravel port that was created earlier. If successful it will be as follows.

Ngrok server

Please note the forwarding URL provided by Ngrok, just one of them is enough. Here I get the URL: http://b674-110-137-75-3.ngrok.io/. This URL can be accessed by anyone on the internet. This URL will be given to Midtrans to send notifications. To view the history of incoming requests, please go to: http://localhost:4040/

Configure Notification URL on Midtrans Dashboard

Open the Midtrans dashboard, in the Settings > Configuration section, in the Payment Notification URL field, fill in as follows.

Configure Notification URL at Midtrans Dashboard

Please adjust it with your ngrok URL. If so, it should now be ready to be tested.

Notes
Ngrok is only used for offline testing, if it is ready to use and uploaded to the server, no configuration needs to be changed. Just replace the ngrok tunel address with your domain. For example, if uploaded on the https://jurnalmms.web.id domain, just change to: https://jurnalmms.web.id/payments/midtrans-notification

Doing Test

To test the payment, we can use the simulator provided by Midtrans. With the simulator, we can make payments without having to make real payments. Midtrans Mock can be accessed here.

Get the destination account number

First, we have to get the destination account number. The trick is to open the order page, then click the “Pay now” button, select the payment channel and note the account number displayed.
Get back account number for testing

Because the number is a BRI Virtual Account, on the Mock Payment Midtrans website select "BRI Virtual Account" in the Payment Page dropdown.
BRIVA Mock

Then in the input field enter the account number that was obtained earlier. Then click the Inquire button. On the next page click the Pay button to pay. If you have successfully entered the success page, it means that the payment simulation has been successfully carried out.

To view the history, please go to http://localhost:4040 and view the history of incoming requests via Ngrok.
Ngrok incoming request stats

Ngrok incoming request stats

So far, we have successfully received notifications from Midtrans. The payment status in the database will also change automatically.

When you start testing, there may be an issue with the signature key being unverified. One way to solve this is to make sure the price paid by the customer is the same as the total_price column in the database.

All program code can be seen in the following GitHub repository:
https://github.com/mulyosyahidin/laravel-midtrans

Read this post in Indonesian: https://jurnalmms.web.id/laravel/integrasi-midtrans-dan-laravel-8-menggunakan-snap-bagian-2/

Top comments (1)

Collapse
 
clovinlee profile image
Clovinlee

Hello, i encountered an error on notification part. It says :

Trying to access array offset on value of type null
on
\Midtrans\Notification
-> $status_response = Transaction::status($raw_notification['transaction_id']); (line 25 returned null, so do line 26)

What should I do? i implemented everything correctly