~ We are changing the world with technology. ~

Matt Mullenweg

Aplikasi E-Commerce Laravel 6 #10: Multiple Authentication

Aplikasi E-Commerce Laravel 6 #10: Multiple Authentication

588 Dilihat

Pendahuluan

Laravel memulai debutnya dengan menghadirkan fitur yang sangat praktis dalam meng-handle proses authentication. Hal ini menyebabkan penggunanya mendapatkan sedikit kenyamanan karena tidak perlu lagi secara repot memikirkan dan mengerjakan authentication.

Belakangan, muncul sebuah masalah, karena terlalu nyaman dengan hal praktis, banyak pengguna Laravel yang tidak tahu bagaimana cara membuat manual authentication. Hal ini meliputi bagaimana membuat multiple authentication dalam sebuah aplikasi yang sedang dikerjakannya.

Baca Juga: Aplikasi E-Commerce Laravel 6 #9: Customer Transactions

Set Customer Password

Pada seri sebelumnya terdapat sebuah schema dimana customer akan secara otomatis didaftarkan ketika mereka melakukan transaksi, hanya saja kita melupakan satu hal, yakni men-generate password untuk customer tersebut. Field untuk menampung password pada table customers juga tidak tersedia, maka langkah pertama adalah membuat field tersebut. Pada command line jalankan

php artisan make:migration add_field_password_to_customers_table

Buka file migration yang baru saja di-generate dan modifikasi menjadi.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddFieldPasswordToCustomersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('customers', function (Blueprint $table) {
            $table->string('password')->after('email');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('customers', function (Blueprint $table) {
            $table->dropColumn('password');
        });
    }
}

Migration lainnya adalah untuk menampung token aktivasi pendaftaran user, pada command line, jalankan command

php artisan make:migration add_field_active_token_to_customers_table

Sebenarnya bisa saja disatukan dengan migration sebelumnya, hanya saja saya terlanjut membuatnya maka kita lanjutkan saja, buka file migration yang baru saja di-generate dan modifikasi menjadi

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddFieldActiveTokenToCustomersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('customers', function (Blueprint $table) {
            $table->string('activate_token')->after('district_id')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('customers', function (Blueprint $table) {
            $table->dropColumn('activate_token');
        });
    }
}

Selanjutnya adalah menambahkan random password dan activate token ke dalam data customer yang akan disimpan, buka file CartController.php dan modifikasi query untuk menambahkan data customer yang berada pada method processCheckout()

$password = Str::random(8); //TAMBAHKAN LINE INI
$customer = Customer::create([
    'name' => $request->customer_name,
    'email' => $request->email,
    'password' => $password, //TAMBAHKAN LINE INI
    'phone_number' => $request->customer_phone,
    'address' => $request->customer_address,
    'district_id' => $request->district_id,
    'activate_token' => Str::random(30), //TAMBAKAN LINE INI
    'status' => false
]);

Kemudian kita buat mutator untuk meng-encrypt password tersebut sebelum disimpan ke database, buka file Customer.php dan tambahkan method berikut

public function setPasswordAttribute($value)
{
    $this->attributes['password'] = bcrypt($value);
}

Note: Mutator memiliki peran untuk mengubah value sebelum menyimpannya ke dalam database.

Send Email Verification

Tugas selanjutnya adalah mengirimkan informasi customer melalui email, tapi sebelum melanjutkan pembahasan ini, sediakan mail server yang bisa digunakan untuk mengirim email melalui smtp. Sebagai contoh, kamu bisa membuat akun di Mailgun.

Saya asumsikan bahwa kamu sudah memiliki smtp account, maka kita akan melanjutkan tugas kita yakni membuat service untuk mengirimkan email. Pada command line, jalankan command

php artisan make:mail CustomerRegisterMail

Sebuah file baru akan di-generate dan disimpan di dalam folder app/Mail, buka file CustomerRegisterMail.php yang ada di dalamnya dan modifikasi menjadi.

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Customer; //USE STATEMENT MODEL CUSTOMER

class CustomerRegisterMail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    protected $customer;
    protected $randomPassword;
  
    //MEMINTA DATA BERUPA INFORMASI CUSTOMER DAN RANDOM PASSWORD YANG BELUM DI-ENCRYPT
    public function __construct(Customer $customer, $randomPassword)
    {
        $this->customer = $customer;
        $this->randomPassword = $randomPassword;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        //MENGESET SUBJECT EMAIL, VIEW MANA YANG AKAN DI-LOAD DAN DATA APA YANG AKAN DIPASSING KE VIEW
        return $this->subject('Verifikasi Pendaftaran Anda')
            ->view('emails.register')
            ->with([
                'customer' => $this->customer,
                'password' => $this->randomPassword
            ]);
    }
}

Kemudian kita sediakan file register.blade.php di dalam folder resources/views/email dan tambahkan code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Verifikasi Pendaftaran Anda</title>
</head>
<body>
    <h2>Hai, {{ $customer->name }}</h2>
    <p>Terima kasih telah melakukan transaksi pada aplikasi kami, berikut password anda: <strong>{{ $password }}</strong></p>
    <p>Jangan lupa untuk melakukan verifikasi pendaftaran <a href="{{ route('customer.verify', $customer->activate_token) }}">DISINI</a></p>
</body>
</html>

Jika kita lihat pada code di atas, terdapat route name yang digunakan. Jadi kita harus menyediakan route name tersebut untuk menghindari error. Buka file Ecommerce/FrontController.php dan tambahkan method

public function verifyCustomerRegistration($token)
{
    //JADI KITA BUAT QUERY UNTUK MENGMABIL DATA USER BERDASARKAN TOKEN YANG DITERIMA
    $customer = Customer::where('activate_token', $token)->first();
    if ($customer) {
        //JIKA ADA MAKA DATANYA DIUPDATE DENGNA MENGOSONGKAN TOKENNYA DAN STATUSNYA JADI AKTIF
        $customer->update([
            'activate_token' => null,
            'status' => 1
        ]);
        //REDIRECT KE HALAMAN LOGIN DENGAN MENGIRIMKAN FLASH SESSION SUCCESS
        return redirect(route('customer.login'))->with(['success' => 'Verifikasi Berhasil, Silahkan Login']);
    }
    //JIKA TIDAK ADA, MAKA REDIRECT KE HALAMAN LOGIN
    //DENGAN MENGIRIMKAN FLASH SESSION ERROR
    return redirect(route('customer.login'))->with(['error' => 'Invalid Verifikasi Token']);
}

Jangan lupa untuk menambahkan use statement

use App\Customer;

Definisikan route-nya, buka file routes/web.php dan tambahkan code

Route::group(['prefix' => 'member', 'namespace' => 'Ecommerce'], function() {
    Route::get('verify/{token}', '[email protected]')->name('customer.verify');
});

Penjelasan: Route baru ini kita group dengan menambahkan prefix /member, jadi semua route yang ada di dalamnya url-nya akan diawali dengan prefix tersebut.

Eh ada lagi nih route name yang dipanggil tapi belum didefinisikan sebelumnya, yakni customer.login. Buat controller baru dengan command

php artisan make:controller Ecommerce/LoginController

Kemudian buka file tersebut dan modifikasi menjadi

<?php

namespace App\Http\Controllers\Ecommerce;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    public function loginForm()
    {
        return view('ecommerce.login');
    }
}

Perlu dong untuk kita definisikan route-nya lagi, buka file routes/web.php dan tambahkan route berikut tepat di dalam group route diatas.

Route::group(['prefix' => 'member', 'namespace' => 'Ecommerce'], function() {
    Route::get('login', '[email protected]')->name('customer.login'); //TAMBAHKAN ROUTE INI
    Route::get('verify/{token}', '[email protected]')->name('customer.verify');
});

Buat file login.blade.php di dalam folder resources/views/ecommerce dan tambahkan code

@extends('layouts.ecommerce')

@section('title')
    <title>Login - 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>Login/Register</h2>
					<div class="page_link">
                        <a href="{{ url('/') }}">Home</a>
                        <a href="{{ route('customer.login') }}">Login</a>
					</div>
				</div>
			</div>
		</div>
	</section>
	<!--================End Home Banner Area =================-->

	<!--================Login Box Area =================-->
	<section class="login_box_area p_120">
		<div class="container">
			<div class="row">
				<div class="offset-md-3 col-lg-6">
                    @if (session('success'))
                        <div class="alert alert-success">{{ session('success') }}</div>
                    @endif

                    @if (session('error'))
                        <div class="alert alert-danger">{{ session('error') }}</div>
                    @endif

					<div class="login_form_inner">
						<h3>Log in to enter</h3>
						<form class="row login_form" action="contact_process.php" method="post" id="contactForm" novalidate="novalidate">
							<div class="col-md-12 form-group">
								<input type="email" class="form-control" id="email" name="email" placeholder="Email Address">
							</div>
							<div class="col-md-12 form-group">
								<input type="password" class="form-control" id="password" name="password" placeholder="******">
							</div>
							<div class="col-md-12 form-group">
								<div class="creat_account">
									<input type="checkbox" id="f-option2" name="remember">
									<label for="f-option2">Keep me logged in</label>
								</div>
							</div>
							<div class="col-md-12 form-group">
								<button type="submit" value="submit" class="btn submit_btn">Log In</button>
								<a href="#">Forgot Password?</a>
							</div>
						</form>
					</div>
				</div>
			</div>
		</div>
	</section>
@endsection

Semua kebutuhan dan yang saling terkait sudah tersedia, maka tugas kita yang terakhir pada sub-bab ini adalah menggunakan Mail service untuk mengirimkan email pada customer terkait. Buka file CartController dan tambahkan code dibawah ini di dalam method processCheckout() , tepatnya sebelum fungsi return.

DB::commit();

$carts = [];
$cookie = cookie('dw-carts', json_encode($carts), 2880);

Mail::to($request->email)->send(new CustomerRegisterMail($customer, $password)); //TAMBAHKAN CODE INI SAJA 
return redirect(route('front.finish_checkout', $order->invoice))->cookie($cookie);

Jangan lupa menambahkan use statement

use App\Mail\CustomerRegisterMail;
use Mail;

Agar email pengirim dan nama pengirim lebih jelas, buka file config/mail.php dan modifikasi code di bawah ini

'from' => [
    'address' => env('MAIL_FROM_ADDRESS', '[email protected]'),
    'name' => env('MAIL_FROM_NAME', 'DW Ecommerce'),
],

Eits, ada yang ketinggalan, buka file .env dan masukkan informasi smtp mail kamu pada konfigurasi berikut

MAIL_DRIVER=smtp
MAIL_HOST=HOST SMTP ANDA
MAIL_PORT=PORT NYA BERAPA
MAIL_USERNAME=USERNAME SMTP ANDA
MAIL_PASSWORD=PASSWORD SMTP ANDA
MAIL_ENCRYPTION=tls

Agar halaman login lebih mudah diakses dari menu navigasi, buka file /layouts/ecommerce.blade.php dan cari code di bawah ini

<ul class="right_side">
  
  <!-- MODIFIKASI BAGIAN INI -->
  <li><a href="{{ route('customer.login') }}">Login</a></li>
  <!-- MODIFIKASI BAGIAN INI -->
  
  <li><a href="#">My Account</a></li>
  <li><a href="contact.html">Contact Us</a></li>
</ul>

Customer Guard Authentication

Laravel memperkenalkan istilah guard untuk proses otentikasinya, secara default guard yang digunakan adalah web yang terhubung dengan model User.php atau table users. Karena kita akan menggunakan table customers untuk mengenali user yang akan melakukan proses otentikasi, maka diperlukan sebuah guard baru.

Guard diatur di dalam sebuah konfigurasi, buka file config/auth.php dan modifikasi menjadi

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
				
      	/* TAMBAHKAN CODE INI */
        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

      	/* TAMBAHKAN CODE INI */
        'customers' => [
            'driver' => 'eloquent',
            'model' => App\Customer::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];

Penjelasan: Ada dua bagian yang perlu diubah, pertama pada bagian guards kita tambahkan sebuah array dengan key customer dimana provider-nya merujuk pada customers. Jadi pada bagian kedua di dalam providers kita perlu menambahkan data array dengan key customers. Perhatikan value dari model adalah class Customer.php, sehingga sampai pada tahap ini kita sudah berhasil menghubungkan antara konfigurasi di atas dengan model Customer.php.

Kemudian buka model Customer.php dan modifikasi menjadi

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class Customer extends Authenticatable
{
    use Notifiable;
    protected $guarded = [];

    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = bcrypt($value);
    }
}

Note: Kita perlu meng-extends Authenticable agar bisa menggunakan semua fitur otentikasi yang dimilikinya.

Selanjutnya buka file Ecommerce/LoginController.php dan tambahkan method berikut untuk meng-handle proses otentikasi

public function login(Request $request)
{
    //VALIDASI DATA YANG DITERIMA
    $this->validate($request, [
        'email' => 'required|email|exists:customers,email',
        'password' => 'required|string'
    ]);

    //CUKUP MENGAMBIL EMAIL DAN PASSWORD SAJA DARI REQUEST
    //KARENA JUGA DISERTAKAN TOKEN
    $auth = $request->only('email', 'password');
    $auth['status'] = 1; //TAMBAHKAN JUGA STATUS YANG BISA LOGIN HARUS 1
  
    //CHECK UNTUK PROSES OTENTIKASI
    //DARI GUARD CUSTOMER, KITA ATTEMPT PROSESNYA DARI DATA $AUTH
    if (auth()->guard('customer')->attempt($auth)) {
        //JIKA BERHASIL MAKA AKAN DIREDIRECT KE DASHBOARD
        return redirect()->intended(route('customer.dashboard'));
    }
    //JIKA GAGAL MAKA REDIRECT KEMBALI BERSERTA NOTIFIKASI
    return redirect()->back()->with(['error' => 'Email / Password Salah']);
}

Definisikan route-nya, buka file routes/web.php dan tambahkan route berikut di dalam group dengan prefix member.

Route::post('login', '[email protected]')->name('customer.post_login');

Buka file resources/views/ecommerce/login.blade.php dan modifikasi tepat dibagian tag form dengan menambahkan action route-nya menjadi

<form class="row login_form" action="{{ route('customer.post_login') }}" method="post" id="contactForm" novalidate="novalidate">
  @csrf

Sebelum melakukan proses testing, kita perlu menyediakan halaman dashboard terlebih dahulu. Buka file Ecommerce/LoginController.php dan tambahkan method

public function dashboard()
{
    return view('ecommerce.dashboard');
}

Kemudian buat file dashboard.blade.php di dalam folder resources/views/ecommerce dan tambahkan code berikut

@extends('layouts.ecommerce')

@section('title')
    <title>Dashboard - 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>Dashboard</h2>
					<div class="page_link">
                        <a href="{{ url('/') }}">Home</a>
                        <a href="{{ route('customer.dashboard') }}">Dashboard</a>
					</div>
				</div>
			</div>
		</div>
	</section>
	<!--================End Home Banner Area =================-->

	<!--================Login Box Area =================-->
	<section class="login_box_area p_120">
		<div class="container">
			<div class="row">
				<div class="col-lg-12">
                    <h1 class="text-center">Dashboard Sementara</h1>
				</div>
			</div>
		</div>
	</section>
@endsection

Terakhir definisikan route-nya dengan menambahkan line code berikut di dalam route group dengan prefix member

Route::group(['middleware' => 'customer'], function() {
    Route::get('dashboard', '[email protected]')->name('customer.dashboard');
});

Note: Kita menggunakan group lagi di dalam group, bedanya group ini akan menerapkan middleware customer untuk mengecek apakah customer terkait sudah login atau belum.

Middleware customer didapatkan dari mana? Dia adalah sebuah middleware baru, maka buat middleware dengan command

php artisan make:middleware CustomerAuthenticate

File tersebut akan disimpan ke dalam folder app/Http/Middleware, buka file CustomerAuthenticate.php dan modifikasi menjadi

<?php

namespace App\Http\Middleware;

use Closure;

class CustomerAuthenticate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        //JADI KITA CEK, JIKA GUARD CUSTOMER BELUM LOGIN
        if (!auth()->guard('customer')->check()) {
            //MAKA REDIRECT KE HALAMAN LOGIN
            return redirect(route('customer.login'));
        }
        //JIKA SUDAH MAKA REQUEST YANG DIMINTA AKAN DISEDIAKAN
        return $next($request);
    }
}

Note: Jadi middleware bekerja ketika request diberikan oleh user, maka prosesnya akan dijalankan.

Daftarkan middleware baru tersebut agar bisa digunakan, buka file app/Http/Kernel.php dan tambahkan line berikut di dalam protected $routeMiddleware

'customer' => \App\Http\Middleware\CustomerAuthenticate::class,

Sebagai penutup, kita akan membuat tombol logout. Buka file Ecommerce/LoginController.php dan tambahkan method berikut

public function logout()
{
    auth()->guard('customer')->logout(); //JADI KITA LOGOUT SESSION DARI GUARD CUSTOMER
    return redirect(route('customer.login'));
}

Definisikan route-nya dengan menambahkan code berikut di dalam group yang menggunakan middleware customer, karena fungsi ini hanya bisa diakses ketika user sudah login.

Route::group(['middleware' => 'customer'], function() {
    Route::get('dashboard', '[email protected]')->name('customer.dashboard');
    Route::get('logout', '[email protected]')->name('customer.logout'); //TAMBAHKAN BARIS INI
});

Masih ingat dengan tombol Login yang kita modifikasi pada header halaman? Mari kita modifikasi lagi. Buka file resources/views/layouts/ecommerce.blade.php dah ubah code-nya menjadi

<ul class="right_side">
  @if (auth()->guard('customer')->check())
    <li><a href="{{ route('customer.logout') }}">Logout</a></li>
  @else
    <li><a href="{{ route('customer.login') }}">Login</a></li>
  @endif
  <li><a href="#">My Account</a></li>
  <li><a href="contact.html">Contact Us</a></li>
</ul>

Penjelasan: Jadi tombol logout yang akan ditampilkan jika user terkait sudah login, jika belum maka yang akan ditampilkan adalah tombol login.

Agar user tidak bisa mengakses halaman login ketika dia sudah login, maka buka file Ecommerce/LoginController.php dan modifikasi method berikut menjadi

public function loginForm()
{
    if (auth()->guard('customer')->check()) return redirect(route('customer.dashboard'));
    return view('ecommerce.login');
}

Baca Juga: Aplikasi E-Commerce Laravel 6 #8: Manage Carts

Kesimpulan

Otentikasi adalah gerbang masuk dan keluar dari sebuah batasan yang diterapkan untuk para pengguna. Pada seri ini kita telah belajar membuat multiple authentication di Laravel atau lebih tepatnya kita membuat fungsi otentikasi secara custom tanpa menggunakan bawaan Laravel. Jadi sekarang kita memiliki dua buah guard yakni web secara default dan customer.

Dokumentasi code dari artikel ini bisa dilihat di Github.

Full Stack Developer & Remote Worker salah satu perusahaan asal Australia. Senang dengan teknologi baru. Laravel addict yang tidak fanatik. Merekam jejak dengan menulis

[TIPS] Membuat Routing Maintenable di Laravel 6 Laravel

[TIPS] Membuat Routing Maintenable di La...

Satu dari sekian hal yang menjadi problematika Programmer adalah bagaimana cara menuliskan code yang maintenable, sebab aplikasi yang terus dikembangkan dari waktu ke waktu akan menemukan kompleksitas...

Aplikasi E-Commerce Laravel 6 #9: Customer Transactions Laravel

Aplikasi E-Commerce Laravel 6 #9: Custom...

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. Pr...

Aplikasi E-Commerce Laravel 6 #8: Manage Carts Laravel

Aplikasi E-Commerce Laravel 6 #8: Manage...

Transaksi menjadi bagian yang penting dalam aplikasi berbasis penjualan dan mengelola data keranjang menjadi salah satu bagian yang akan melengkapi proses pendataan transaksi yang dilakukan oleh pelan...

Komentar