Pendahuluan
Mencatat data pesanan pelanggan yang sebelumnya telah disimpan ke dalam cookie, kini akan kita lengkapi dengan data pribadi pelanggan terkait untuk kemudian disimpan secara pasti ke dalam database. Proses transaksi ini membutuhkan effort berlebih karena kita akan berinteraksi dengan beberapa table sehingga proses ini terbilang cukup kompleks.
Schema yang diinginkan adalah ketika user telah menambahkan produk yang diinginkan ke dalam keranjang, maka pada halaman keranjang terdapat tombol untuk mengarahkannya ke halaman form transaksi sehingga calon pelanggan bisa memasukkan informasi terkait data dirinya.
Baca Juga: Aplikasi E-commerce Laravel 6 #8: Manage Carts
Fix Carts Issue
Terdapat sedikit kendala ketika data di cookie kosong yang berarti hasilnya akan null, maka akan terjadi error pada halaman keranjang belanja. Untuk mengatasi masalah ini sebenarnya cukup dengan menggunakan ternary operator pada php untuk mengecek jika datanya tidak sama dengan null (dan atau kosong) maka data tersebut akan disimpan kedalam variable, tapi jika berlaku sebaliknya maka kita berikan array kosong.
Karena fungsi ini cukup sering digunakan, maka akan kita pisahkan ke dalam sebuah fungsi tersendiri agar code dengan logic yang sama tidak dituliskan secara berulang. Buka file CartController.php
dan tambahkan method berikut.
private function getCarts()
{
$carts = json_decode(request()->cookie('dw-carts'), true);
$carts = $carts != '' ? $carts:[];
return $carts;
}
Masih dengan file yang sama, replace semua code $carts = json_decode(request()->cookie('dw-carts'), true);
menjadi
$carts = $this->getCarts();
Form Transactions
Menyiapkan form transaksi adalah langkah selanjutnya dari proses membuat aplikasi e-commerce menggunakan Laravel 6, dimana fungsinya adalah untuk mencatat pesanan yang akan dilakukan oleh pelanggan. Adapun data produk yang akan dipesan telah disimpan ke dalam Cookie yang selanjutnya akan dimasukkan ke dalam database, kemudian data yang ada pada cookie akan dibersihkan.
Langkah pertama adalah membuat tampilannya, buka file CartController.php
dan tambahkan method berikut.
public function checkout()
{
//QUERY UNTUK MENGAMBIL SEMUA DATA PROPINSI
$provinces = Province::orderBy('created_at', 'DESC')->get();
$carts = $this->getCarts(); //MENGAMBIL DATA CART
//MENGHITUNG SUBTOTAL DARI KERANJANG BELANJA (CART)
$subtotal = collect($carts)->sum(function($q) {
return $q['qty'] * $q['product_price'];
});
//ME-LOAD VIEW CHECKOUT.BLADE.PHP DAN PASSING DATA PROVINCES, CARTS DAN SUBTOTAL
return view('ecommerce.checkout', compact('provinces', 'carts', 'subtotal'));
}
Jangan lupa untuk menambahkan use statement.
use App\Province;
Kemudian buat sebuah file bernama checkout.blade.php
dan letakkan ke dalam folder resources/views/ecommerce
. Masukkan code berikut untuk meng-handle form transaksi
@extends('layouts.ecommerce')
@section('title')
<title>Checkout - Dw Ecommerce</title>
@endsection
@section('content')
<!--================Home Banner Area =================-->
<section class="banner_area">
<div class="banner_inner d-flex align-items-center">
<div class="overlay"></div>
<div class="container">
<div class="banner_content text-center">
<h2>Informasi Pengiriman</h2>
<div class="page_link">
<a href="{{ url('/') }}">Home</a>
<a href="#">Checkout</a>
</div>
</div>
</div>
</div>
</section>
<!--================End Home Banner Area =================-->
<!--================Checkout Area =================-->
<section class="checkout_area section_gap">
<div class="container">
<div class="billing_details">
<div class="row">
<div class="col-lg-8">
<h3>Informasi Pengiriman</h3>
@if (session('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
<!-- REMOVE DULU VALUE ACTION-NYA JIKA INGIN MELIHATNYA DI BROWSER -->
<!-- KARENA ROUTE NAME front.store_checkout BELUM DIBUAT -->
<form class="row contact_form" action="{{ route('front.store_checkout') }}" method="post" novalidate="novalidate">
@csrf
<div class="col-md-12 form-group p_star">
<label for="">Nama Lengkap</label>
<input type="text" class="form-control" id="first" name="customer_name" required>
<!-- UNTUK MENAMPILKAN JIKA TERDAPAT ERROR VALIDASI -->
<p class="text-danger">{{ $errors->first('customer_name') }}</p>
</div>
<div class="col-md-6 form-group p_star">
<label for="">No Telp</label>
<input type="text" class="form-control" id="number" name="customer_phone" required>
<p class="text-danger">{{ $errors->first('customer_phone') }}</p>
</div>
<div class="col-md-6 form-group p_star">
<label for="">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
<p class="text-danger">{{ $errors->first('email') }}</p>
</div>
<div class="col-md-12 form-group p_star">
<label for="">Alamat Lengkap</label>
<input type="text" class="form-control" id="add1" name="customer_address" required>
<p class="text-danger">{{ $errors->first('customer_address') }}</p>
</div>
<div class="col-md-12 form-group p_star">
<label for="">Propinsi</label>
<select class="form-control" name="province_id" id="province_id" required>
<option value="">Pilih Propinsi</option>
<!-- LOOPING DATA PROVINCE UNTUK DIPILIH OLEH CUSTOMER -->
@foreach ($provinces as $row)
<option value="{{ $row->id }}">{{ $row->name }}</option>
@endforeach
</select>
<p class="text-danger">{{ $errors->first('province_id') }}</p>
</div>
<!-- ADAPUN DATA KOTA DAN KECAMATAN AKAN DI RENDER SETELAH PROVINSI DIPILIH -->
<div class="col-md-12 form-group p_star">
<label for="">Kabupaten / Kota</label>
<select class="form-control" name="city_id" id="city_id" required>
<option value="">Pilih Kabupaten/Kota</option>
</select>
<p class="text-danger">{{ $errors->first('city_id') }}</p>
</div>
<div class="col-md-12 form-group p_star">
<label for="">Kecamatan</label>
<select class="form-control" name="district_id" id="district_id" required>
<option value="">Pilih Kecamatan</option>
</select>
<p class="text-danger">{{ $errors->first('district_id') }}</p>
</div>
<!-- ADAPUN DATA KOTA DAN KECAMATAN AKAN DI RENDER SETELAH PROVINSI DIPILIH -->
</div>
<div class="col-lg-4">
<div class="order_box">
<h2>Ringkasan Pesanan</h2>
<ul class="list">
<li>
<a href="#">Product
<span>Total</span>
</a>
</li>
@foreach ($carts as $cart)
<li>
<a href="#">{{ \Str::limit($cart['product_name'], 10) }}
<span class="middle">x {{ $cart['qty'] }}</span>
<span class="last">Rp {{ number_format($cart['product_price']) }}</span>
</a>
</li>
@endforeach
</ul>
<ul class="list list_2">
<li>
<a href="#">Subtotal
<span>Rp {{ number_format($subtotal) }}</span>
</a>
</li>
<li>
<a href="#">Pengiriman
<span>Rp 0</span>
</a>
</li>
<li>
<a href="#">Total
<span>Rp {{ number_format($subtotal) }}</span>
</a>
</li>
</ul>
<button class="main_btn">Bayar Pesanan</button>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<!--================End Checkout Area =================-->
@endsection
Definisikan route-nya agar bisa diakses, buka file routes/web.php
dan tambahkan code berikut
Route::get('/checkout', 'Ecommerce\CartController@checkout')->name('front.checkout');
Langkah terakhir pada sub-bab ini adalah dengan menghubungkan tombol Proceed to checkout ke halaman checkout. Buka file cart.blade.php
dan cari kalimat Proceed to checkout
kemudian modifikasi tag-nya menjadi.
<a class="gray_btn" href="{{ route('front.product') }}">Continue Shopping</a>
<a class="main_btn" href="{{ route('front.checkout') }}">Proceed to checkout</a>
Dynamic Option With Ajax
Ketika select box province dipilih maka secara otomatis akan me-render data kabupaten atau kota pada select box berikutnya. Tugas kita kali ini adalah membuat fitur tersebut dengan memanfaatkan ajax pada jquery.
Buka file resources/views/layouts/ecommerce.blade.php
dan tambahkan code di bawah ini tepat sebelum tag </body>
@yield('js')
Gunanya adalah view yang me-load layouts diatas bisa menambahkan custom javascript khusus pada halaman yang sedang menggunakannya, cara kerja-nya sama dengan cara kerja @yield('content')
dan sebagainya.
Kembali pada file checkout.blade.php
, tambahkan code berikut pada line atau baris terakhir
@section('js')
<script>
//KETIKA SELECT BOX DENGAN ID province_id DIPILIH
$('#province_id').on('change', function() {
//MAKA AKAN MELAKUKAN REQUEST KE URL /API/CITY
//DAN MENGIRIMKAN DATA PROVINCE_ID
$.ajax({
url: "{{ url('/api/city') }}",
type: "GET",
data: { province_id: $(this).val() },
success: function(html){
//SETELAH DATA DITERIMA, SELEBOX DENGAN ID CITY_ID DI KOSONGKAN
$('#city_id').empty()
//KEMUDIAN APPEND DATA BARU YANG DIDAPATKAN DARI HASIL REQUEST VIA AJAX
//UNTUK MENAMPILKAN DATA KABUPATEN / KOTA
$('#city_id').append('<option value="">Pilih Kabupaten/Kota</option>')
$.each(html.data, function(key, item) {
$('#city_id').append('<option value="'+item.id+'">'+item.name+'</option>')
})
}
});
})
//LOGICNYA SAMA DENGAN CODE DIATAS HANYA BERBEDA OBJEKNYA SAJA
$('#city_id').on('change', function() {
$.ajax({
url: "{{ url('/api/district') }}",
type: "GET",
data: { city_id: $(this).val() },
success: function(html){
$('#district_id').empty()
$('#district_id').append('<option value="">Pilih Kecamatan</option>')
$.each(html.data, function(key, item) {
$('#district_id').append('<option value="'+item.id+'">'+item.name+'</option>')
})
}
});
})
</script>
@endsection
Ada dua request dari code di atas, yakni: /api/city
dan /api/district
. Kita lengkapi satu persatu, buka file CartController.php
dan tambahkan method
public function getCity()
{
//QUERY UNTUK MENGAMBIL DATA KOTA / KABUPATEN BERDASARKAN PROVINCE_ID
$cities = City::where('province_id', request()->province_id)->get();
//KEMBALIKAN DATANYA DALAM BENTUK JSON
return response()->json(['status' => 'success', 'data' => $cities]);
}
public function getDistrict()
{
//QUERY UNTUK MENGAMBIL DATA KECAMATAN BERDASARKAN CITY_ID
$districts = District::where('city_id', request()->city_id)->get();
//KEMUDIAN KEMBALIKAN DATANYA DALAM BENTUK JSON
return response()->json(['status' => 'success', 'data' => $districts]);
}
Jangan lupa untuk menambahkan use statement.
use App\City;
use App\District;
Kemudian definisikan routing-nya, buka file routes/api.php
dan tambahkan code
Route::get('city', 'Ecommerce\CartController@getCity'); //ROUTE API UNTUK /CITY
Route::get('district', 'Ecommerce\CartController@getDistrict'); //ROUTE API UNTUK /DISTRICT
Bagian terakhir ada memodifikasi file public/ecommerce/js/theme.js
dan hapus code.
$('select').niceSelect();
Make Transactions
Form transaksi telah tersedia, dimana tampilan tersebut memungkinkan user untuk memasukkan informasi pesanannya yang meliputi nama pelanggan, alamat dan sebagainya. Pada bagian ini tugas kita adalah meng-handle data yang dikirimkan dari form tersebut. Buka file CartController.php
dan tambahkan method berikut
public function processCheckout(Request $request)
{
//VALIDASI DATANYA
$this->validate($request, [
'customer_name' => 'required|string|max:100',
'customer_phone' => 'required',
'email' => 'required|email',
'customer_address' => 'required|string',
'province_id' => 'required|exists:provinces,id',
'city_id' => 'required|exists:cities,id',
'district_id' => 'required|exists:districts,id'
]);
//INISIASI DATABASE TRANSACTION
//DATABASE TRANSACTION BERFUNGSI UNTUK MEMASTIKAN SEMUA PROSES SUKSES UNTUK KEMUDIAN DI COMMIT AGAR DATA BENAR BENAR DISIMPAN, JIKA TERJADI ERROR MAKA KITA ROLLBACK AGAR DATANYA SELARAS
DB::beginTransaction();
try {
//CHECK DATA CUSTOMER BERDASARKAN EMAIL
$customer = Customer::where('email', $request->email)->first();
//JIKA DIA TIDAK LOGIN DAN DATA CUSTOMERNYA ADA
if (!auth()->check() && $customer) {
//MAKA REDIRECT DAN TAMPILKAN INSTRUKSI UNTUK LOGIN
return redirect()->back()->with(['error' => 'Silahkan Login Terlebih Dahulu']);
}
//AMBIL DATA KERANJANG
$carts = $this->getCarts();
//HITUNG SUBTOTAL BELANJAAN
$subtotal = collect($carts)->sum(function($q) {
return $q['qty'] * $q['product_price'];
});
//SIMPAN DATA CUSTOMER BARU
$customer = Customer::create([
'name' => $request->customer_name,
'email' => $request->email,
'phone_number' => $request->customer_phone,
'address' => $request->customer_address,
'district_id' => $request->district_id,
'status' => false
]);
//SIMPAN DATA ORDER
$order = Order::create([
'invoice' => Str::random(4) . '-' . time(), //INVOICENYA KITA BUAT DARI STRING RANDOM DAN WAKTU
'customer_id' => $customer->id,
'customer_name' => $customer->name,
'customer_phone' => $request->customer_phone,
'customer_address' => $request->customer_address,
'district_id' => $request->district_id,
'subtotal' => $subtotal
]);
//LOOPING DATA DI CARTS
foreach ($carts as $row) {
//AMBIL DATA PRODUK BERDASARKAN PRODUCT_ID
$product = Product::find($row['product_id']);
//SIMPAN DETAIL ORDER
OrderDetail::create([
'order_id' => $order->id,
'product_id' => $row['product_id'],
'price' => $row['product_price'],
'qty' => $row['qty'],
'weight' => $product->weight
]);
}
//TIDAK TERJADI ERROR, MAKA COMMIT DATANYA UNTUK MENINFORMASIKAN BAHWA DATA SUDAH FIX UNTUK DISIMPAN
DB::commit();
$carts = [];
//KOSONGKAN DATA KERANJANG DI COOKIE
$cookie = cookie('dw-carts', json_encode($carts), 2880);
//REDIRECT KE HALAMAN FINISH TRANSAKSI
return redirect(route('front.finish_checkout', $order->invoice))->cookie($cookie);
} catch (\Exception $e) {
//JIKA TERJADI ERROR, MAKA ROLLBACK DATANYA
DB::rollback();
//DAN KEMBALI KE FORM TRANSAKSI SERTA MENAMPILKAN ERROR
return redirect()->back()->with(['error' => $e->getMessage()]);
}
}
Jangan lupa untuk menambahkan use statement
use App\Customer;
use App\Order;
use App\OrderDetail;
use Illuminate\Support\Str;
use DB;
Definisikan routing-nya, buka file routes/web.php
dan tambahkan route berikut
Route::post('/checkout', 'Ecommerce\CartController@processCheckout')->name('front.store_checkout');
Pastikan untuk mengizinkan penyimpanan data melalui Eloquent, buka file Customer.php
dan modifikasi menjadi
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
protected $guarded = [];
}
Buka file Order.php
dan modifikasi juga menjadi.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $guarded = [];
//MEMBUAT RELASI KE MODEL DISTRICT.PHP
public function district()
{
return $this->belongsTo(District::class);
}
}
Selanjutnya buka file OrderDetail.php
dan modifikasi menjadi
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class OrderDetail extends Model
{
protected $guarded = [];
}
Bagian terakhir dari seri modifikasi model adalah file District.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class District extends Model
{
//BUAT RELASI KE MODEL CITY.PHP
public function city()
{
return $this->belongsTo(City::class);
}
}
Setelah proses penyimpanan berhasil, maka prosesnya akan diarahkan ke halaman yang berfungsi untuk menampilkan summary pesanan. Buka file CartController.php
dan tambahkan method
public function checkoutFinish($invoice)
{
//AMBIL DATA PESANAN BERDASARKAN INVOICE
$order = Order::with(['district.city'])->where('invoice', $invoice)->first();
//LOAD VIEW checkout_finish.blade.php DAN PASSING DATA ORDER
return view('ecommerce.checkout_finish', compact('order'));
}
Buat file checkout_finish.blade.php
dan tambahkan code berikut yang berisi informasi pesanan yang baru saja dilakukan.
@extends('layouts.ecommerce')
@section('title')
<title>Keranjang Belanja - Dw Ecommerce</title>
@endsection
@section('content')
<!--================Home Banner Area =================-->
<section class="banner_area">
<div class="banner_inner d-flex align-items-center">
<div class="container">
<div class="banner_content text-center">
<h2>Pesanan Diterima</h2>
<div class="page_link">
<a href="{{ url('/') }}">Home</a>
<a href="">Invoice</a>
</div>
</div>
</div>
</div>
</section>
<!--================End Home Banner Area =================-->
<!--================Order Details Area =================-->
<section class="order_details p_120">
<div class="container">
<h3 class="title_confirmation">Terima kasih, pesanan anda telah kami terima.</h3>
<div class="row order_d_inner">
<div class="col-lg-6">
<div class="details_item">
<h4>Informasi Pesanan</h4>
<ul class="list">
<li>
<a href="#">
<span>Invoice</span> : {{ $order->invoice }}</a>
</li>
<li>
<a href="#">
<span>Tanggal</span> : {{ $order->created_at }}</a>
</li>
<li>
<a href="#">
<span>Total</span> : Rp {{ number_format($order->subtotal) }}</a>
</li>
</ul>
</div>
</div>
<div class="col-lg-6">
<div class="details_item">
<h4>Informasi Pemesan</h4>
<ul class="list">
<li>
<a href="#">
<span>Alamat</span> : {{ $order->customer_address }}</a>
</li>
<li>
<a href="#">
<span>Kota</span> : {{ $order->district->city->name }}</a>
</li>
<li>
<a href="#">
<span>Country</span> : Indonesia</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!--================End Order Details Area =================-->
@endsection
Definsikan route yang terakhir, buka file routes/web.php
dan tambahkan code
Route::get('/checkout/{invoice}', 'Ecommerce\CartController@checkoutFinish')->name('front.finish_checkout');
Baca Juga: Aplikasi E-commerce Laravel 6 #7: Filter Product Detail
Kesimpulan
Sepanjang artikel ini kita telah belajar banyak hal diantaranya bagaimana cara menyimpan data yang ada di Cookie ke dalam database, selain itu kita juga belajar bagaimana menggunakan database transaction untuk meminimalisir kesamaan data jika terjadi error ketika proses penyimpanan dilakukan pada lebih dari satu table yang saling terakait.
Adapun dokumentasi code dari artikel ini bisa dilihat di Github.
Comments