Laravel E-Commerce Application Development – Checkout

Laravel E-Commerce Application Development – Checkout

Laravel E-Commerce Application Development ( 26 Lessons )

In this course, you’ll learn how to create an E-Commerce Website from scratch in Laravel. The process has never been easier I’ll take you from the very beginning stages of setting up Laravel till the last steps of adding products to the cart. If you’ve good understanding & experience in PHP & MySQL then this course is for you.

see full series
  1. Laravel E-Commerce Application Development – Introduction
  2. Laravel E-Commerce Application Development – Initial Project Setup
  3. Laravel E-Commerce Application Development – Assets Setup Using Laravel Mix
  4. Laravel E-Commerce Application Development – Admin Model and Migration
  5. Laravel E-Commerce Application Development – Backend Admin Authentication
  6. Laravel E-Commerce Application Development – Base Controller and Repository
  7. Laravel E-Commerce Application Development – Settings Section Part 1
  8. Laravel E-Commerce Application Development – Settings Section Part 2
  9. Laravel E-Commerce Application Development – Categories Section Part 1
  10. Laravel E-Commerce Application Development – Categories Section Part 2
  11. Laravel E-Commerce Application Development – Attributes Section Part 1
  12. Laravel E-Commerce Application Development – Attributes Section Part 2
  13. Laravel E-Commerce Application Development – Attributes Section Part 3
  14. Laravel E-Commerce Application Development – Brands Section
  15. Laravel E-Commerce Application Development – Products Section Part 1
  16. Laravel E-Commerce Application Development – Products Section Part 2
  17. Laravel E-Commerce Application Development – Products Section Part 3
  18. Laravel E-Commerce Application Development – Products Section Part 4
  19. Laravel E-Commerce Application Development – Frontend Login & Registration
  20. Laravel E-Commerce Application Development – Categories Navigation
  21. Laravel E-Commerce Application Development – Catalog Listing
  22. Laravel E-Commerce Application Development – Product Details Page
  23. Laravel E-Commerce Application Development – Shopping Cart
  24. Laravel E-Commerce Application Development – Checkout
  25. Laravel E-Commerce Application Development – Payment Processing
  26. Laravel E-Commerce Application Development – Wrap Up

This is part 23 of the Laravel E-Commerce Application Development series. In this part, we will start implementing the checkout functionality in our application.

I assume you should have the e-commerce application project on your machine or you can grab it from Laravel E-Commerce Application repository, we will start from where we left it in the last part.

Checkout Process Implementation

Before jumping into the checkout process, let me explain what I have planned to do this. The first thing a registered user can place an order so from now on we will protect all routes using the auth middleware. When a user signin they can proceed for the checkout and fill in their address details for shipment purpose.

Next, when they hit place order, we will store the order and items in the shopping cart to our database.

After storing the order will redirect the customer to the PayPal page for payment processing. On successful payment, we show the customer the order number on the confirmation page.

So let’s start.

Creating Order Model and Migration

Let’s create the model and migration for the order. Open the command line terminal and run below command.

php artisan make:model Models\Order -m

Above command will generate a model and migration file. Open the migration file for order and update with the below content.

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

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('order_number')->unique();
            $table->unsignedInteger('user_id');
            $table->foreign('user_id')->references('id')->on('users');

            $table->enum('status', ['pending', 'processing', 'completed', 'decline'])->default('pending');
            $table->decimal('grand_total', 20, 6);
            $table->unsignedInteger('item_count');

            $table->boolean('payment_status')->default(1);
            $table->string('payment_method')->nullable();

            $table->string('first_name');
            $table->string('last_name');
            $table->text('address');
            $table->string('city');
            $table->string('country');
            $table->string('post_code');
            $table->string('phone_number');
            $table->text('notes')->nullable();

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('orders');
    }
}

Now open the Order model from app/Models folder and update with the below one.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected $table = 'orders';

    protected $fillable = [
        'order_number', 'user_id', 'status', 'grand_total', 'item_count', 'payment_status', 'payment_method',
        'first_name', 'last_name', 'address', 'city', 'country', 'post_code', 'phone_number', 'notes'
    ];

    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function items()
    {
        return $this->hasMany(OrderItem::class);
    }
}

In our order migration file, we added the user_id because every order will belongs to a user. In the model file you can see we have declared the user() method which establish the relationship with the User model class.

Also, we have established a One To Many relationship for the OrderItem model which we will create in the next section.

Creating Order Item Model and Migration

Open the command line terminal and run below command to generate the OrderItem model and migration file.

php artisan make:model Models\OrderItem -m

Now open the migration file for OrderItem and update the content with the below one.

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

class CreateOrderItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('order_items', function (Blueprint $table) {
            $table->bigIncrements('id');

            $table->unsignedBigInteger('order_id')->index();
            $table->unsignedBigInteger('product_id')->index();
            $table->unsignedInteger('quantity');
            $table->decimal('price', 20, 6);

            $table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade');
            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('order_items');
    }
}

In this migration file we have added the order_id and producr_id fields with their foreign keys.

Now open the OrderItem model and update the model class with the below.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class OrderItem extends Model
{
    protected $table = 'order_items';

    protected $fillable = [
        'order_id', 'product_id', 'quantity', 'price'
    ];

    public function product()
    {
        return $this->belongsTo(Product::class, 'product_id');
    }
}

As you can see we have added the products() relationship which means every ordered item will belong to a product.

Pretty simple, nothing fancy.

Creating Order Contract and Repository

Now we will create the OrderContract and OrderRepository classes.

In the future, we will also add the orders section to our admin area so it’s good practice to create a repository and use it everywhere.

Create a new file in app/Contracts folder and name it OrderContract.php.

Now open this file and add below code in it.

namespace App\Contracts;

interface OrderContract
{
    public function storeOrderDetails($params);
}

Next, create a new file in the app/Repositories folder with the name OrderRepository.php. Add the below in it.

namespace App\Repositories;

use Cart;
use App\Models\Order;
use App\Models\Product;
use App\Models\OrderItem;
use App\Contracts\OrderContract;

class OrderRepository extends BaseRepository implements OrderContract
{
    public function __construct(Order $model)
    {
        parent::__construct($model);
        $this->model = $model;
    }

    public function storeOrderDetails($params)
    {

    }
}

Now open the RepositoryServiceProvidr from the app/Providers folder and include the OrderContract and OrderRepository classes in it.

use App\Contracts\OrderContract;
use App\Repositories\OrderRepository;

Next update the $repositories array with the below one.

protected $repositories = [
    CategoryContract::class         =>          CategoryRepository::class,
    AttributeContract::class        =>          AttributeRepository::class,
    BrandContract::class            =>          BrandRepository::class,
    ProductContract::class          =>          ProductRepository::class,
    OrderContract::class            =>          OrderRepository::class,
];

Now we have order repository class which we can use on the multiple places.

Adding Checkout Routes

As I explained earlier from now on our all routes will be for only authenticated users, so we will use the auth middleware in our routes group.

Open the routes/web.php file and add the below routes defination in it.

Route::group(['middleware' => ['auth']], function () {
    Route::get('/checkout', 'Site\[email protected]')->name('checkout.index');
    Route::post('/checkout/order', 'Site\[email protected]')->name('checkout.place.order');
});

Creating Checkout Controller

Now our above routes are pointing to CheckoutController so we will create it and inject the OrderContract in it.

php artisan make:controller Site\CheckoutController

Open the CheckoutController class and update with the below one.

namespace App\Http\Controllers\Site;

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

class CheckoutController extends Controller
{
    protected $orderRepository;

    public function __construct(OrderContract $orderRepository)
    {
        $this->orderRepository = $orderRepository;
    }

    public function getCheckout()
    {
        return view('site.pages.checkout');
    }

    public function placeOrder(Request $request)
    {
        // Before storing the order we should implement the
        // request validation which I leave it to you
        $order = $this->orderRepository->storeOrderDetails($request->all());

        dd($order);
    }
}

In the above controller class we have two methods, first will load the checkout view when we will click on the proceed to checkout button and the second one will be called when we will submit the checkout form.

Updating Proceed to Checkout Button

Before creating the checkout view file, let’s add the checkout.index route which we created earlier to our button.

Open the resources/views/site/pages/cart.blade.php file and replace the below:

<a href="#" class="btn btn-success btn-lg btn-block">Proceed To Checkout</a>

with this:

<a href="{{ route('checkout.index') }}" class="btn btn-success btn-lg btn-block">Proceed To Checkout</a>

When a user will click this button, he/she will be redirected to the login page if not authenticated. After authentication user will be presented with the checkout view.

Adding Checkout Blade View

For checkout view, create a new file in resources/views/site/pages folder and name it checkout.blade.php. Now, open this file and add the below markup in it.

@extends('site.app')
@section('title', 'Checkout')
@section('content')
    <section class="section-pagetop bg-dark">
        <div class="container clearfix">
            <h2 class="title-page">Checkout</h2>
        </div>
    </section>
    <section class="section-content bg padding-y">
        <div class="container">
            <div class="row">
                <div class="col-sm-12">
                    @if (Session::has('error'))
                        <p class="alert alert-danger">{{ Session::get('error') }}</p>
                    @endif
                </div>
            </div>
            <form action="{{ route('checkout.place.order') }}" method="POST" role="form">
                @csrf
                <div class="row">
                    <div class="col-md-8">
                        <div class="card">
                            <header class="card-header">
                                <h4 class="card-title mt-2">Billing Details</h4>
                            </header>
                            <article class="card-body">
                                <div class="form-row">
                                    <div class="col form-group">
                                        <label>First name</label>
                                        <input type="text" class="form-control" name="first_name">
                                    </div>
                                    <div class="col form-group">
                                        <label>Last name</label>
                                        <input type="text" class="form-control" name="last_name">
                                    </div>
                                </div>
                                <div class="form-group">
                                    <label>Address</label>
                                    <input type="text" class="form-control" name="address">
                                </div>
                                <div class="form-row">
                                    <div class="form-group col-md-6">
                                        <label>City</label>
                                        <input type="text" class="form-control" name="city">
                                    </div>
                                    <div class="form-group col-md-6">
                                        <label>Country</label>
                                        <input type="text" class="form-control" name="country">
                                    </div>
                                </div>
                                <div class="form-row">
                                    <div class="form-group  col-md-6">
                                        <label>Post Code</label>
                                        <input type="text" class="form-control" name="post_code">
                                    </div>
                                    <div class="form-group  col-md-6">
                                        <label>Phone Number</label>
                                        <input type="text" class="form-control" name="phone_number">
                                    </div>
                                </div>
                                <div class="form-group">
                                    <label>Email Address</label>
                                    <input type="email" class="form-control" name="email" value="{{ auth()->user()->email }}" disabled>
                                    <small class="form-text text-muted">We'll never share your email with anyone else.</small>
                                </div>
                                <div class="form-group">
                                    <label>Order Notes</label>
                                    <textarea class="form-control" name="notes" rows="6"></textarea>
                                </div>
                            </article>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="row">
                            <div class="col-md-12">
                                <div class="card">
                                    <header class="card-header">
                                        <h4 class="card-title mt-2">Your Order</h4>
                                    </header>
                                    <article class="card-body">
                                        <dl class="dlist-align">
                                            <dt>Total cost: </dt>
                                            <dd class="text-right h5 b"> {{ config('settings.currency_symbol') }}{{ \Cart::getSubTotal() }} </dd>
                                        </dl>
                                    </article>
                                </div>
                            </div>
                            <div class="col-md-12 mt-4">
                                <button type="submit" class="subscribe btn btn-success btn-lg btn-block">Place Order</button>
                            </div>
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </section>
@stop

In this view file, we have a billing information form and a Place Order button. The form is pointing to the checkout.place.order route.

Storing Order Details to Database

After successful submission of the checkout form, we want to store the order and cart items. So let’s add that implementation into the OrderRepository.

Opne the OrderRepository class and update the storeOrderDetails() method with the below one.

public function storeOrderDetails($params)
{
    $order = Order::create([
        'order_number'      =>  'ORD-'.strtoupper(uniqid()),
        'user_id'           => auth()->user()->id,
        'status'            =>  'pending',
        'grand_total'       =>  Cart::getSubTotal(),
        'item_count'        =>  Cart::getTotalQuantity(),
        'payment_status'    =>  0,
        'payment_method'    =>  null,
        'first_name'        =>  $params['first_name'],
        'last_name'         =>  $params['last_name'],
        'address'           =>  $params['address'],
        'city'              =>  $params['city'],
        'country'           =>  $params['country'],
        'post_code'         =>  $params['post_code'],
        'phone_number'      =>  $params['phone_number'],
        'notes'             =>  $params['notes']
    ]);

    if ($order) {

        $items = Cart::getContent();

        foreach ($items as $item)
        {
            // A better way will be to bring the product id with the cart items
            // you can explore the package documentation to send product id with the cart
            $product = Product::where('name', $item->name)->first();

            $orderItem = new OrderItem([
                'product_id'    =>  $product->id,
                'quantity'      =>  $item->quantity,
                'price'         =>  $item->getPriceSum()
            ]);

            $order->items()->save($orderItem);
        }
    }

    return $order;
}

In this method, we are firstly creating an order record with the all required information. Next, if an order is created we are loading the shopping cart items and creating the new order item instances. Then we are using the save() method to save and attach each item to the order. On completion we are returning the $order to our controller.

If you now try in your browser and load the checkout you will have a similar view like below.

Checkout Page
Checkout Page

User Specific Shopping Cart

Currently we are not attaching the shopping cart to authenticated user. The package we have used to add shopping cart functionality does support the user-specific shopping cart. For that, you have to move the add to cart route and all shopping cart route under the route group which is using the auth middleware.
I want to keep this series as simple as possible so won’t be going much deep into each and every detail. Check the package documentation for using user-specific shopping carts.

What’s Next

In the next post, we will start from where we are leaving now and process the payment using PayPal.

Code Repository

You can find the code base of this series on Laravel eCommerce Application repository.

If you have any question about this post, please leave a comment in the comment box below.

5 comments on “Laravel E-Commerce Application Development – Checkout

  1. Hello,

    just a quick question regarding the payment. I see we are going to implement the payment with PayPal, but are we also going to see how to do it with Stripe? I ask this question because in the admin we have 2 choices for payment.

    Thank you in advance!

    1. I like the idea, can you please implement that. You can grab the repository for this series and open a pull request.

      Your contribution will be much appreciated.

      Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

*When sharing a code snippet please wrap you code with pre tag and add a class code-block to it like below.
<pre class="code-block">you code here</pre>

*
*

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Your little help will keep this site alive and help us to produce quality content for you.