Tutorial selanjutnya adalah mengintegrasikan Payment Gateway di Laravel, menggunakan Midtrans sebagai contoh (salah satu payment gateway populer di Indonesia).

Langkah 1: Buat Akun di Midtrans

Buka Midtrans Dashboard dan daftar akun

Setelah login, buat Project Baru untuk aplikasi Anda.

Di dashboard pilih enviroment sandbox, kemudian di menu pengaturan, Anda akan mendapatkan Server Key dan Client Key. Simpan ini untuk konfigurasi Laravel nanti.Midtrans Akses Key

Langkah 2: Instalasi Library Midtrans

Install Library Midtrans

Jalankan perintah berikut untuk menginstal library Midtrans menggunakan Composer:

composer require midtrans/midtrans-php

Langkah 3: Konfigurasi Midtrans di Laravel

Tambah Midtrans Config File

Buat file konfigurasi baru di config/midtrans.php dan tambahkan config server dan client sesuai di dashboard:

<?php

return [
    'server_key' => env('MIDTRANS_SERVER_KEY', ''),
    'client_key' => env('MIDTRANS_CLIENT_KEY', ''),
    'is_production' => env('MIDTRANS_IS_PRODUCTION', false),
    'is_sanitized' => env('MIDTRANS_IS_SANITIZED', true),
    'is_3ds' => env('MIDTRANS_IS_3DS', true),
];

Tambah Key di .env

Tambahkan server key dan client key dari dashboard Midtrans ke file .env:

MIDTRANS_SERVER_KEY=your_server_key
MIDTRANS_CLIENT_KEY=your_client_key
MIDTRANS_IS_PRODUCTION=false
MIDTRANS_IS_SANITIZED=true
MIDTRANS_IS_3DS=true

Tambahkan Service Provider

Di AppServiceProvider tambahkan inisialisasi Midtrans di dalam method boot:

use Midtrans\Config;

public function boot()
{
    Config::$serverKey = config('midtrans.server_key');
    Config::$isProduction = config('midtrans.is_production');
    Config::$isSanitized = config('midtrans.is_sanitized');
    Config::$is3ds = config('midtrans.is_3ds');
}

Langkah 4: Edit Controller untuk Checkout

1. Tambahkan method Proses Pembayaran

Tambahkan method untuk memproses pembayaran:

use Midtrans\Snap;
use App\Models\Order;

public function process(Request $request)
{
    $categories = Category::all();
    $request->validate([
        'address' => 'required|string',
        'city' => 'required|string',
        'state' => 'required|string',
        'zip' => 'required|string',
        'country' => 'required|string',
    ]);

    $cart = session()->get('cart', []);
    if (empty($cart)) {
        return redirect()->route('cart.index')->withErrors('Cart is empty');
    }

    $totalAmount = array_reduce($cart, function ($sum, $item) {
        return $sum + ($item['price'] * $item['quantity']);
    }, 0);

    Address::create([
        'user_id' => Auth::id(),
        'type' => 'billing', // Bisa ditambah pengaturan untuk shipping
        'address' => $request->address,
        'city' => $request->city,
        'state' => $request->state,
        'zip' => $request->zip,
        'country' => $request->country,
    ]);

    $order = Order::create([
        'user_id' => Auth::id(),
        'total' => $totalAmount,
        'status' => 'pending',
    ]);

    $orderId = $order->id; // ID yang baru saja di-insert
    $grossAmount = $totalAmount; // Total harga

    foreach ($cart as $productId => $item) {
        OrderItem::create([
            'order_id' => $order->id,
            'product_id' => $productId,
            'quantity' => $item['quantity'],
            'price' => $item['price'],
        ]);
    }

    $params = [
        'transaction_details' => [
            'order_id' => $orderId,
            'gross_amount' => $grossAmount,
        ],
        'customer_details' => [
            'first_name' => auth()->user()->name,
            'email' => auth()->user()->email,
        ],
    ];

    // Generate Snap Token
    $snapToken = Snap::getSnapToken($params);
    return view('frontend.payment', compact('snapToken','categories'));

    //session()->forget('cart');

    //return redirect()->route('checkout.success')->with('status', 'Order placed successfully');
}

2. Callback untuk Status Pembayaran

Tambahkan method untuk menangani callback dari Midtrans:

use Midtrans\Notification;

public function callback(Request $request)
{
    $notification = new Notification();

    $orderId = $notification->order_id;
    $transactionStatus = $notification->transaction_status;

    $order = Order::where('id', $orderId)->first();

    if ($transactionStatus == 'capture' || $transactionStatus == 'settlement') {
        $order->status = 'paid';
    } elseif ($transactionStatus == 'pending') {
        $order->status = 'pending';
    } elseif ($transactionStatus == 'deny' || $transactionStatus == 'expire') {
        $order->status = 'failed';
    }

    $order->save();
    return response()->json(['status' => 'success']);
}

Secara total controller, checkout akan seperti berikut:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Address;
use App\Models\Product;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Models\Category;
use Midtrans\Snap;
use Midtrans\Notification;

class CheckoutController extends Controller
{
    public function showCheckoutForm()
    {
        $categories = Category::all();

        $cart = session()->get('cart', []);
        $total = array_reduce($cart, function ($sum, $item) {
            return $sum + ($item['price'] * $item['quantity']);
        }, 0);

        return view('frontend.checkout', compact('cart', 'total','categories'));
    }

    public function process(Request $request)
    {
        $categories = Category::all();
        $request->validate([
            'address' => 'required|string',
            'city' => 'required|string',
            'state' => 'required|string',
            'zip' => 'required|string',
            'country' => 'required|string',
        ]);

        $cart = session()->get('cart', []);
        if (empty($cart)) {
            return redirect()->route('cart.index')->withErrors('Cart is empty');
        }

        $totalAmount = array_reduce($cart, function ($sum, $item) {
            return $sum + ($item['price'] * $item['quantity']);
        }, 0);

        Address::create([
            'user_id' => Auth::id(),
            'type' => 'billing', // Bisa ditambah pengaturan untuk shipping
            'address' => $request->address,
            'city' => $request->city,
            'state' => $request->state,
            'zip' => $request->zip,
            'country' => $request->country,
        ]);

        $order = Order::create([
            'user_id' => Auth::id(),
            'total' => $totalAmount,
            'status' => 'pending',
        ]);

        $orderId = $order->id; // ID yang baru saja di-insert
        $grossAmount = $totalAmount; // Total harga

        foreach ($cart as $productId => $item) {
            OrderItem::create([
                'order_id' => $order->id,
                'product_id' => $productId,
                'quantity' => $item['quantity'],
                'price' => $item['price'],
            ]);
        }

        $params = [
            'transaction_details' => [
                'order_id' => $orderId,
                'gross_amount' => $grossAmount,
            ],
            'customer_details' => [
                'first_name' => auth()->user()->name,
                'email' => auth()->user()->email,
            ],
        ];
    
        // Generate Snap Token
        $snapToken = Snap::getSnapToken($params);
        return view('frontend.payment', compact('snapToken','categories'));

        //session()->forget('cart');

        //return redirect()->route('checkout.success')->with('status', 'Order placed successfully');
    }
    public function callback(Request $request)
    {
        $notification = new Notification();

        $orderId = $notification->order_id;
        $transactionStatus = $notification->transaction_status;

        $order = Order::where('order_id', $orderId)->first();

        if ($transactionStatus == 'capture' || $transactionStatus == 'settlement') {
            $order->status = 'paid';
        } elseif ($transactionStatus == 'pending') {
            $order->status = 'pending';
        } elseif ($transactionStatus == 'deny' || $transactionStatus == 'expire') {
            $order->status = 'failed';
        }

        $order->save();
        return response()->json(['status' => 'success']);
    }
    public function successsCheckout(){

        $categories = Category::all();
        return view('frontend.checkout-success', compact('categories'));
    }
}

Langkah 5: Buat Route

Tambahkan route untuk checkout:

Route::middleware('auth')->group(function () {
    Route::get('/checkout', [CheckoutController::class, 'index'])->name('checkout.index');
    Route::post('/checkout/process', [CheckoutController::class, 'process'])->name('checkout.process');
    Route::post('/midtrans/callback', [CheckoutController::class, 'callback'])->name('midtrans.callback');
});

Langkah 6: Buat Views

Halaman Payment (resources/views/frontend/payment.blade.php)

Halaman untuk memuat Snap Midtrans:

@extends('layouts.frontend')

@section('content')
<div class="container">
    <h1>Payment</h1>
    <button id="pay-button" class="btn btn-primary">Pay Now</button>
</div>

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

Langkah 7: Testing Fitur

Jika kita melakukan checkout dan pilih payment, akan muncul seperti berikut:

Payment Midtrans

Selanjutnya kita testing untuk payment sandbox menggunakan simulator. klik link berikut:

Simulator Midtrans

Lalu input kode payment BCA di simulator seperti berikut:
Input payment BCA VA Midtrans

Setelah itu akan muncul validasi seperti berikut:
validasi payment

Lalu klik pay. Jika berhasil, akan muncul seperti berikut:
payment success

Di snap, akan muncul payment success seperti berikut:
snap payment success

Kemudian jika kita check di sanbox dashboard midtrans, akan seperti berikut:
sandbox transaction
Selamat mencoba