My Course — Membangun Website Food Store Dengan Laravel Filament, Livewire dan Payment Gateway

Menghitung Biaya Ongkos Kirim


Setelah provinsi dan kota berhasil ditampilkan, maka kita akan lanjutkan menampilkan pilihan kurir, yaitu JNE, POS dan TIKI (ini adalah kurir gratis yang tersedia di RajaOngkir). Pilihan kurir ini nantinya akan ditampilkan ketika customer sudah memilih data provinsi dan kota.

Langkah 1 - Membuat Fungsi Check Ongkir

Silahkan teman-teman buka file app/Livewire/Web/Checkout/Index.php, kemudian ubah semua kode-nya menjadi seperti berikut ini.

php
<?php namespace App\Livewire\Web\Checkout; use App\Models\Cart; use Livewire\Component; use App\Models\Province; use Illuminate\Support\Facades\Http; class Index extends Component { public $address; public $province_id; public $city_id; public $loading = false; public $showCost = false; public $costs; public $selectCourier = ''; public $selectService = ''; public $selectCost = 0; public $grandTotal = 0; /** * getCartsData * * @return void */ public function getCartsData() { //get carts by customer $carts = Cart::query() ->with('product') ->where('customer_id', auth()->guard('customer')->user()->id) ->latest() ->get(); // Menghitung total berat $totalWeight = $carts->sum(function ($cart) { return $cart->product->weight * $cart->qty; }); // Menghitung total harga $totalPrice = $carts->sum(function ($cart) { return $cart->product->price * $cart->qty; }); // Return as an array return [ 'totalWeight' => $totalWeight, 'totalPrice' => $totalPrice, ]; } /** * changeCourier * * @param mixed $value * @return void */ public function changeCourier($value) { if (!empty($value)) { //set courier $this->selectCourier = $value; //set loading $this->loading = true; //set show cost false $this->showCost = false; //call method CheckOngkir $this->CheckOngkir(); } } /** * Ongkir function: calculate shipping cost or any logic. * * @return void */ public function CheckOngkir() { try { // Ambil data cart $cartData = $this->getCartsData(); // Fetch Rest API $response = Http::withHeaders([ 'key' => config('rajaongkir.api_key') ])->post('https://api.rajaongkir.com/starter/cost', [ 'origin' => 113, // ID kota Demak 'destination' => $this->city_id, 'weight' => $cartData['totalWeight'], 'courier' => $this->selectCourier, ]); // Process costs (optional: store in a variable) $this->costs = $response['rajaongkir']['results'][0]['costs']; } catch (\Exception $e) { // Handle error (optional: set an error message) session()->flash('error', 'Gagal mengambil ongkir.'); } finally { // Always update loading and cost visibility $this->loading = false; $this->showCost = true; } } /** * getServiceAndCost * * @param mixed $data * @return void */ public function getServiceAndCost($data) { // Pecah data menjadi nilai cost dan service [$cost, $service] = explode('|', $data); // Set nilai cost dan service $this->selectCost = (int) $cost; $this->selectService = $service; // Ambil total harga dari cart $cartData = $this->getCartsData(); // Hitung grand total $this->grandTotal = $cartData['totalPrice'] + $this->selectCost; } public function render() { //get provinces $provinces = Province::query()->get(); //get total cart price $cartData = $this->getCartsData(); $totalPrice = $cartData['totalPrice']; $totalWeight = $cartData['totalWeight']; return view('livewire.web.checkout.index', compact('provinces', 'totalPrice', 'totalWeight')); } }

Dari perubahan kode di atas, pertama kita import Model Cart.

perl
use App\Models\Cart;

Kemudian kita import Facade Http dari Laravel.

perl
use Illuminate\Support\Facades\Http;

Setelah itu kita menambahkan beberapa public properti, yaitu:

php
public $loading = false; // untuk menampilkan loading public $showCost = false; // untuk menampilkan pilihan kurir public $costs; // untuk menampilkan biaya ongkos kirim dari RajaOngkir public $selectCourier = ''; // untuk menyimpan jenis kurir public $selectService = ''; // untuk menyimpan service / layanan kurir public $selectCost = 0; // untuk menyimpan biaya ongkos kirim public $grandTotal = 0; // untuk menyimpan grand total

Karena kita butuh data cart untuk mengambil data total price dan weight (berat), maka disini kita membuat method yang bernama getCartsData.

csharp
public function getCartsData() { //... }

Di dalam method di atas, kita melakukan get data cart berdasarkan customer id yang login.

php
//get carts by customer $carts = Cart::query() ->with('product') ->where('customer_id', auth()->guard('customer')->user()->id) ->latest() ->get();

Kemudian kita juga get data total price dan weight dari keseluruhan data cart.

php
// Menghitung total berat $totalWeight = $carts->sum(function ($cart) { return $cart->product->weight * $cart->qty; }); // Menghitung total harga $totalPrice = $carts->sum(function ($cart) { return $cart->product->price * $cart->qty; });

Selanjutnya kita return, agar nanti bisa digunakan atau dipanggil pada method lain.

php
// Return as an array return [ 'totalWeight' => $totalWeight, 'totalPrice' => $totalPrice, ];

Berikutnya, kita membuat method baru dengan nama changeCourier, method ini nantinya akan ditrigger ketika customer memilih jenis kurir, seperti JNE, POS dan TIKI.

php
public function changeCourier($value) { //... }

Di dalamnya, kita melakukan set kurir yang dipilih ke dalam properti selectCourier dengan value yang dikirimkan dari form.

php
//set courier $this->selectCourier = $value;

Kemudian kita set laoding menjadi true dan show cost menjadi false.

kotlin
//set loading $this->loading = true; //set show cost false $this->showCost = false;

Selanjutnya, kita panggil method CheckOngkir untuk menghitung biaya ongkos kirim.

scss
//call method CheckOngkir $this->CheckOngkir();

Nah sekarang kita masuk pada method CheckOngkir.

csharp
public function CheckOngkir() { //... }

Karena kita akan butuh data berat (weight) pada cart untuk menghitung biaya ongkos kirim, maka kita panggil method getCartsData.

php
$cartData = $this->getCartsData();

Selanjutnya, kita akan melakukan perhitungan biaya ongkos kirim menggunakan Http Laravel, dimana kita akan melakukan request ke API rajaongkir.

bash
https://api.rajaongkir.com/starter/cost

Di dalamnya kita mengirimkan 4 data, yaitu:

KEY VALUE DESCRIPTION
origin 113 ID kota asal (silahkan disesuaikan dengan ID kota kalian).
destination $this->city_id ID kota tujuan.
weight $cartData['totalWeight'] Berat data cart (dalam satuan GRAM).
courier $this->selectCourier Jenis kurir yang dipilih (JNE, POS, TIKI).
php
// Fetch Rest API $response = Http::withHeaders([ 'key' => config('rajaongkir.api_key') ])->post('https://api.rajaongkir.com/starter/cost', [ 'origin' => 113, // ID kota Demak 'destination' => $this->city_id, 'weight' => $cartData['totalWeight'], 'courier' => $this->selectCourier, ]);

Jika data berhasil didapatkan, maka kita akan masukkan ke dalam properti $costs.

php
$this->costs = $response['rajaongkir']['results'][0]['costs'];

Selanjutnya kita buat method lagi dengan nama getServiceAndCost. Method ini berfungsi untuk mengambil data biaya ongkos kirim dan service kurir yang dipilih.

php
public function getServiceAndCost($data) { //... }

Di dalamnya, kita melakukan explode untuk memisahkan data yang dikirmkan oleh form.

php
// Pecah data menjadi nilai cost dan service [$cost, $service] = explode('|', $data);

Kemudian kita tinggal set nilainya ke dalam properti selectCost dan selectService.

php
// Set nilai cost dan service $this->selectCost = (int) $cost; $this->selectService = $service;

selectCost berisi biaya ongkos kirim dan selectService berisi layanan kurir, misalnya kalau di JNE ada seperti REG, JTR dan lain sebagainya.

Kemudian kita akan menghitung grand total, yaitu biaya ongkos kirim ditambahkan total data cart.

php
// Ambil total harga dari cart $cartData = $this->getCartsData(); // Hitung grand total $this->grandTotal = $cartData['totalPrice'] + $this->selectCost;

Terakhir, di dalam method render kita akan mengirimkan data cart, yaitu totalPrice dan totalWeight ke view component.

php
//get total cart price $cartData = $this->getCartsData(); $totalPrice = $cartData['totalPrice']; $totalWeight = $cartData['totalWeight']; return view('livewire.web.checkout.index', compact('provinces', 'totalPrice', 'totalWeight'));

Langkah 2 - Menampilkan Check Ongkir

Setelah berhasil menambahkan banyak sekali properti, method, dan lain sebagainya, maka sekarang kita lanjutkan menampilkan proses check ongkir pada halaman view component.

Silahkan teman-teman buka file resources/views/livewire/web/checkout/index.blade.php, kemudian ubah semua kode-nya menjadi seperti berikut ini.

xml
@section('title') Checkout - Eat Your Favorite Foods @stop @section('keywords') Food Store, Eat Your Favorite Foods @stop @section('description') Checkout - Food Store - Eat Your Favorite Foods @stop @section('image') {{ asset('images/logo.png') }} @stop <div> <div class="container"> <div class="row justify-content-center mt-0" style="margin-bottom: 320px;"> <div class="col-md-6"> <div class="bg-white rounded-bottom-custom shadow-sm p-3 sticky-top mb-3"> <div class="d-flex justify-content-start"> <div> <x-buttons.back /> </div> </div> </div> <div class="card rounded shadow-sm border-0 mb-4"> <div class="card-body"> <h6> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-geo-alt mb-1" viewBox="0 0 16 16"> <path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A32 32 0 0 1 8 14.58a32 32 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10" /> <path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4m0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6" /> </svg> Shipping Information </h6> <hr /> <select class="form-select rounded mb-3" wire:model.live="province_id"> <option value="">-- Select Province --</option> @foreach ($provinces as $province) <option value="{{ $province->id }}"> {{ $province->name }} </option> @endforeach </select> <select class="form-select rounded mb-3" wire:model.live="city_id" wire:key="{{ $province_id }}"> <option value="">-- Select City --</option> @foreach (\App\Models\City::where('province_id', $province_id)->get() as $city) <option value="{{ $city->id }}">{{ $city->name }}</option> @endforeach </select> <div class="mb-3"> <textarea class="form-control rounded" wire:model.live="address" rows="3" placeholder="Address: Jl. Kebon Jeruk No. 1, Jakarta Barat"></textarea> </div> </div> </div> @if($city_id) <div class="card rounded shadow-sm border-0 mb-5"> <div class="card-body"> <h6> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-truck mb-1" viewBox="0 0 16 16"> <path d="M0 3.5A1.5 1.5 0 0 1 1.5 2h9A1.5 1.5 0 0 1 12 3.5V5h1.02a1.5 1.5 0 0 1 1.17.563l1.481 1.85a1.5 1.5 0 0 1 .329.938V10.5a1.5 1.5 0 0 1-1.5 1.5H14a2 2 0 1 1-4 0H5a2 2 0 1 1-3.998-.085A1.5 1.5 0 0 1 0 10.5zm1.294 7.456A2 2 0 0 1 4.732 11h5.536a2 2 0 0 1 .732-.732V3.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .294.456M12 10a2 2 0 0 1 1.732 1h.768a.5.5 0 0 0 .5-.5V8.35a.5.5 0 0 0-.11-.312l-1.48-1.85A.5.5 0 0 0 13.02 6H12zm-9 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2m9 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2" /> </svg> Courier Delivery </h6> <hr /> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="courier" id="inlineRadio1" wire:click="changeCourier('jne')"> <label class="form-check-label" for="inlineRadio1">JNE</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="courier" id="inlineRadio2" wire:click="changeCourier('pos')"> <label class="form-check-label" for="inlineRadio2">POS</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="courier" id="inlineRadio3" wire:click="changeCourier('tiki')"> <label class="form-check-label" for="inlineRadio3">TIKI</label> </div> <div class="justify-content-center mt-3 mb-3 text-center"> <div wire:loading wire:target="changeCourier"> <div class="spinner-border text-orange" role="status"> <span class="visually-hidden">Loading...</span> </div> <h6 class="mt-2 text-orange">Loading...</h6> </div> </div> {{-- Cost options --}} @if($showCost) <hr> @endif @if($showCost) <div class="mb-3"> @foreach($costs ?? [] as $cost) <div class="form-check form-check-inline"> <label class="form-check-label font-weight-normal me-2"> <input class="form-check-input" type="radio" name="cost" wire:click="getServiceAndCost('{{ $cost['cost'][0]['value'] }}|{{ $cost['service'] }}')" /> <span class="ms-1">{{ $cost['service'] }} - Rp {{ number_format($cost['cost'][0]['value'], 0, ',', '.') }}</span> </label> </div> @endforeach </div> @endif </div> </div> @endif </div> </div> </div> <div class="container fixed-total"> <div class="row justify-content-center"> <div class="col-12 col-md-6"> <div class="card rounded shadow-sm border-0 mb-5"> <div class="card-body"> <div class="d-flex justify-content-between"> <div> <h6 class="mb-0">Total</h6> </div> <div class="ms-auto"> <h6 class="mb-0">Rp. {{ number_format($totalPrice) }}</h6> </div> </div> <div class="d-flex justify-content-between mt-3"> <div> <h6 class="mb-0">Ongkos Kirim</h6> </div> <div class="ms-auto"> <h6 class="mb-0">Rp. {{ number_format($selectCost) }}</h6> </div> </div> <div class="d-flex justify-content-between mt-3"> <div> <h5 class="fw-bold mb-0">Grand Total</h5> </div> <div class="ms-auto"> <h5 class="fw-bold mb-0">Rp. {{ number_format($grandTotal) }}</h5> </div> </div> </div> </div> </div> </div> </div> </div>

Dari perubahan kode di atas, kita akan menampilkan data kurir jika properti $city_id ada value-nya atau sudah dipilih.

less
@if($city_id) //.... @endif

Di dalamnya, kita menampilkan pilihan kurir, yaitu JNE, POS dan TIKI.

xml
<div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="courier" id="inlineRadio1" wire:click="changeCourier('jne')"> <label class="form-check-label" for="inlineRadio1">JNE</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="courier" id="inlineRadio2" wire:click="changeCourier('pos')"> <label class="form-check-label" for="inlineRadio2">POS</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="courier" id="inlineRadio3" wire:click="changeCourier('tiki')"> <label class="form-check-label" for="inlineRadio3">TIKI</label> </div>

Di atas, pada bagian input kita berikan event were:click dan mengarah ke method changeCourier yang sudah kita buat sebelumnya pada class component dan di dalamnya mengirimkan parameter jenis kurir.

vbnet
wire:click="changeCourier('jne')"
vbnet
wire:click="changeCourier('pos')"
vbnet
wire:click="changeCourier('tiki')"

Selanjutnya, jika salah satu pilihan kurir di atas diklik, maka akan menampilkan halaman loading, selama loading ditampilkan proses pengambilan data biaya ongkos kirim dilakukan.

xml
<div class="justify-content-center mt-3 mb-3 text-center"> <div wire:loading wire:target="changeCourier"> <div class="spinner-border text-orange" role="status"> <span class="visually-hidden">Loading...</span> </div> <h6 class="mt-2 text-orange">Loading...</h6> </div> </div>

Jika data biaya ongkos kirim sudah berhasil didapatkan, maka kita akan looping data-nya menggunakan perulangan @foreach.

less
@foreach($costs ?? [] as $cost) //... @endforeach

Jika biaya ongkos kirim dan service sudah ditampilkan, maka kita berikan event wire:click yang mengarah ke method getServiceAndCost.

bash
<input class="form-check-input" type="radio" name="cost" wire:click="getServiceAndCost('{{ $cost['cost'][0]['value'] }}|{{ $cost['service'] }}')" />

Dimana pada method tersebut akan memisahkan data layanan (service) dan biaya ongkos kirim yang dipilih.

Kemudian kita juga menampilkan total price (cart), biaya ongkos kirim (yang dipilih) dan grand total.

scss
Rp. {{ number_format($totalPrice) }}
scss
Rp. {{ number_format($selectCost) }}
scss
Rp. {{ number_format($grandTotal) }}

Langkah 3 - Uji Coba Check Ongkir

Sekarang silahkan reload halaman checkout, kemudian silahkan teman-teman coba untuk menghitung biaya ongkos kirim, kurang lebih seperti berikut ini.