Building the Checkout and Order Management System in Laravel

Step 7 : Building the Checkout and Order Management System in Laravel

At this point, your Laravel e-commerce app can:

  • Show products on the storefront
  • Allow users to add items to their cart
  • Update quantities or remove products

But without a checkout system, users can’t actually place an order.

This step is crucial because it turns a simple catalog into a real store where customers can submit their purchase information, and you can manage those orders in the admin panel.

We’ll build this carefully with clean logic, proper database structure, and real-world best practices.


🧭 What We’ll Build in This Step

By the end of Step 7, your application will be able to:

  • Collect customer shipping and billing details during checkout
  • Convert cart items into an order stored in the database
  • Create Order and OrderItem models with proper relationships
  • Show a confirmation page to the customer
  • Display orders list in the admin dashboard

🧩 Step 1: Designing the Orders Database Structure

We need two tables:

  • orders → stores general info (customer name, email, total, status)
  • order_items → stores each product in that order (product_id, quantity, price)

📦 Create Migrations

Run these Artisan commands:

php artisan make:model Order -m
php artisan make:model OrderItem -m

Then open database/migrations/xxxx_create_orders_table.php and add:

public function up()
{
    Schema::create('orders', function (Blueprint $table) {
        $table->id();
        $table->string('customer_name');
        $table->string('customer_email');
        $table->string('customer_phone')->nullable();
        $table->text('customer_address');
        $table->decimal('total', 10, 2);
        $table->string('status')->default('pending'); // pending, processing, completed
        $table->timestamps();
    });
}

And xxxx_create_order_items_table.php:

public function up()
{
    Schema::create('order_items', function (Blueprint $table) {
        $table->id();
        $table->foreignId('order_id')->constrained()->onDelete('cascade');
        $table->foreignId('product_id')->constrained()->onDelete('cascade');
        $table->integer('quantity');
        $table->decimal('price', 10, 2);
        $table->timestamps();
    });
}

Finally run:

php artisan migrate

🧩 Step 2 : Setting Up Model Relationships

Open app/Models/Order.php:

class Order extends Model
{
    protected $fillable = [
        'customer_name','customer_email','customer_phone',
        'customer_address','total','status'
    ];

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

And app/Models/OrderItem.php:

class OrderItem extends Model
{
    protected $fillable = ['order_id','product_id','quantity','price'];

    public function order()
    {
        return $this->belongsTo(Order::class);
    }

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

These relationships let you easily access $order->items and $item->product.


🧩 Step 3 : Building the Checkout Form

Create a CheckoutController:

php artisan make:controller CheckoutController

Open app/Http/Controllers/CheckoutController.php and add:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use Illuminate\Support\Facades\Session;

class CheckoutController extends Controller
{
    public function showForm()
    {
        $cart = Session::get('cart', []);
        if (empty($cart)) {
            return redirect()->route('shop.index')->with('error','Your cart is empty');
        }
        return view('checkout.form', compact('cart'));
    }

    public function placeOrder(Request $request)
    {
        $request->validate([
            'customer_name' => 'required',
            'customer_email' => 'required|email',
            'customer_address' => 'required'
        ]);

        $cart = Session::get('cart', []);
        if (empty($cart)) {
            return redirect()->route('shop.index');
        }

        $total = collect($cart)->sum(fn($item) => $item['price'] * $item['quantity']);

        $order = Order::create([
            'customer_name' => $request->customer_name,
            'customer_email' => $request->customer_email,
            'customer_phone' => $request->customer_phone,
            'customer_address' => $request->customer_address,
            'total' => $total,
            'status' => 'pending'
        ]);

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

        Session::forget('cart');

        return redirect()->route('checkout.thankyou', $order->id);
    }

    public function thankYou($id)
    {
        $order = Order::findOrFail($id);
        return view('checkout.thankyou', compact('order'));
    }
}

🧩 Step 4 : Routes for Checkout

Add these to routes/web.php:

use App\Http\Controllers\CheckoutController;

Route::get('/checkout', [CheckoutController::class, 'showForm'])->name('checkout.form');
Route::post('/checkout', [CheckoutController::class, 'placeOrder'])->name('checkout.place');
Route::get('/thank-you/{id}', [CheckoutController::class, 'thankYou'])->name('checkout.thankyou');

🧩 Step 5 : Checkout Blade Views

resources/views/checkout/form.blade.php

@extends('layouts.app')

@section('content')
<div class="container my-5">
  <h2>Checkout</h2>
  <form method="POST" action="{{ route('checkout.place') }}">
    @csrf
    <div class="mb-3">
      <label>Name</label>
      <input type="text" name="customer_name" class="form-control" required>
    </div>
    <div class="mb-3">
      <label>Email</label>
      <input type="email" name="customer_email" class="form-control" required>
    </div>
    <div class="mb-3">
      <label>Phone</label>
      <input type="text" name="customer_phone" class="form-control">
    </div>
    <div class="mb-3">
      <label>Address</label>
      <textarea name="customer_address" class="form-control" required></textarea>
    </div>
    <button class="btn btn-success">Place Order</button>
  </form>
</div>
@endsection

resources/views/checkout/thankyou.blade.php

@extends('layouts.app')

@section('content')
<div class="container my-5 text-center">
  <h2>Thank you for your order!</h2>
  <p>Your order ID is <strong>#{{ $order->id }}</strong></p>
  <p>We’ve sent a confirmation email to <strong>{{ $order->customer_email }}</strong>.</p>
  <a href="{{ route('shop.index') }}" class="btn btn-primary mt-4">Back to Shop</a>
</div>
@endsection

🧩 Step 6 Viewing Orders in Admin Panel

Create an OrderController for admins:

php artisan make:controller Admin/OrderController

Example method:

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Order;

class OrderController extends Controller
{
    public function index()
    {
        $orders = Order::latest()->paginate(20);
        return view('admin.orders.index', compact('orders'));
    }

    public function show($id)
    {
        $order = Order::with('items.product')->findOrFail($id);
        return view('admin.orders.show', compact('order'));
    }
}

Then create Blade views (admin/orders/index.blade.php and admin/orders/show.blade.php) to display them like a simple table.


✅ Summary of Step 7

You have now added:

  • Orders & OrderItems tables
  • Checkout form for customer details
  • Logic to convert cart into a real order
  • Order confirmation page
  • Admin order management section

This step officially transforms your Laravel project from just a product catalog into a working e-commerce application where people can buy products.

Leave a Comment