Laravel: Implementing a Shopping Cart for Your Website

Laravel: Implementing a Shopping Cart for Your Website

Introduction

In this article we will show how to implement a simple Store page and a Shopping Cart page for your Laravel E-commerce website.

The shopping cart will allow users to choose products to buy and make a payment via the PayPal payment gateway.

PayPal is the world’s most popular system for processing payments on e-commerce websites.

Creating Database Tables

Before reading this article, we recommend to refer to our previous post Laravel: Using Pagination, Sorting and Filtering with Your Tables where we described how to implement a simple product catalog. In this article we will use the Product and Category models from the previous post and we assume that you have several products and categories in your database.

Now let’s add two new database tables: orders and order_items. The orders table will contain orders the user makes in the Store. The order_items table will contain items (products) for the given order.

Create a migration file database/migrations/2021_05_29_151804_create_order_tables.php and put the following code into it:

<?php

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class CreateOrderTables extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('user_id')->unsigned();
            $table->decimal('amount');
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });

        Schema::create('order_items', function (Blueprint $table) {
            $table->increments('id');
            $table->bigInteger('order_id')->unsigned();
            $table->bigInteger('product_id')->unsigned();
            $table->tinyInteger('quantity');
            $table->decimal('amount');
            $table->timestamps();
            $table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade');
            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
        });
    }

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

Next, apply the migration to your database:

php artisan migrate

Finally, create the Order and OrderItem model classes inside of your app directory:

<?php
// app/Order.php

namespace App;

use IlluminateDatabaseEloquentModel;

class Order extends Model
{   
    protected $table = 'orders';
}
<?php
// app/OrderItem.php

namespace App;

use IlluminateDatabaseEloquentModel;

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

Implementing Simple Store

Now we are going to implement a simple Store page where the user will be able to choose products for purchase and add them into their Shopping Cart.

Create the StoreController class inside of your app/Http/Controllers directory:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use AppProduct;

class StoreController extends Controller
{
    public function index()
    {
        $products = Product::get();

        $cart = session()->get('cart');
        if ($cart == null)
            $cart = [];

        return view('store.index')->with('products', $products)->with('cart', $cart);
    }

    public function addToCart(Request $request)
    {
        session()->put('cart', $request->post('cart'));

        return response()->json([
            'status' => 'added'
        ]);
    }
}

The StoreController class has two actions:

  • the index action method selects all products from the database. It also extracts the cart items from session (if the user already added some items into their Shopping Cart.

  • the addToCart action is an AJAX method which updates the Shopping Cart session variable when the user adds a product to the cart

Let’s now implement the view template for the index action. It is inspired by the Album example from Bootstrap 4 examples collection.

app/resources/views/cart/index.blade.php:

@extends('layout')

@push('head')
<script>

</script>
@endpush
@section('content')

<br>
<div class="float-right">
<a class="btn btn-success" href="{{ action('CartController@index') }}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cart" viewBox="0 0 16 16">
  <path d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .491.592l-1.5 8A.5.5 0 0 1 13 12H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5zM3.102 4l1.313 7h8.17l1.313-7H3.102zM5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</svg>
    <span id="items-in-cart">0</span> items in cart
</a>
</div>

<h1>Store</h1>

@if ($products->count() == 0)
<tr>
    <td colspan="5">No products to display.</td>
</tr>
@endif

<?php $count = 0; ?>

@foreach ($products as $product)

@if ($count % 3 == 0) 
<div class="row">
@endif

<div class="col-md-4">
  <div class="card mb-4 box-shadow">
    <img class="card-img-top" data-src="holder.js/100px225?theme=thumb&bg=55595c&fg=eceeef&text=Thumbnail" alt="Card image cap">
    <div class="card-body">
      <p class="card-text">{{ $product->category->name }} / {{$product->name}}</p>
      <div class="d-flex justify-content-between align-items-center">
        <small class="text-muted">Only ${{$product->price}}</small>
        <div class="btn-group">
            <input type="number" value="1" min="1" max="100">
            <button class="add-to-cart" type="button" class="btn btn-sm btn-outline-secondary" 
                    data-id="{{$product->id}}" data-name="{{$product->name}}" data-price="{{$product->price}}">Add to Cart</button>
        </div>

      </div>
    </div>
  </div>
</div>

@if ($count % 3 == 2) 
</div>
@endif

<?php $count++; ?>

@endforeach

@endsection

@section('footer-scripts')
<script>
    $(document).ready(function() {

        window.cart = <?php echo json_encode($cart) ?>;

        updateCartButton();

        $('.add-to-cart').on('click', function(event){

            var cart = window.cart || [];
            cart.push({'id':$(this)data('id'), 'name':$(this).data('name'), 'price':$(this).data('price'), 'qty':$(this).prev('input').val()});
            window.cart = cart;

            $.ajax('/store/add-to-cart', {
                type: 'POST',
                data: {"_token": "{{ csrf_token() }}", "cart":cart},
                success: function (data, status, xhr) {

                }
            });

            updateCartButton();
        });
    })

    function updateCartButton() {

        var count = 0;
        window.cart.forEach(function (item, i) {

            count += Number(item.qty);
        });

        $('#items-in-cart').html(count);
    }
</script>
@endsection

In the view template above, we render the products we selected. We also implement some JavaScript event handlers, so when the user
clicks the Add to Cart button, the nadler updates the cart.

Next modify the layout template app/resources/views/layout.blade.php as follows:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">

        <title>Store</title>

        <!-- Bootstrap core CSS -->
        <link href="/css/bootstrap.css" rel="stylesheet">

        <!-- Custom styles for this template -->
        <link href="/css/style.css" rel="stylesheet">
    </head>
    <body>
        <header>
            <div class="collapse bg-dark" id="navbarHeader">
              <div class="container">
                <div class="row">
                  <div class="col-sm-8 col-md-7 py-4">
                    <h4 class="text-white">About</h4>
                    <p class="text-muted">Add some information about the album below, the author, or any other background context. Make it a few sentences long so folks can pick up some informative tidbits. Then, link them off to some social networking sites or contact information.</p>
                  </div>
                  <div class="col-sm-4 offset-md-1 py-4">
                    <h4 class="text-white">Contact</h4>
                    <ul class="list-unstyled">
                      <li><a href="#" class="text-white">Follow on Twitter</a></li>
                      <li><a href="#" class="text-white">Like on Facebook</a></li>
                      <li><a href="#" class="text-white">Email me</a></li>
                    </ul>
                  </div>
                </div>
              </div>
            </div>
            <div class="navbar navbar-dark bg-dark box-shadow">
              <div class="container d-flex justify-content-between">
                <a href="#" class="navbar-brand d-flex align-items-center">
                  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>
                  <strong>Store</strong>
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation">
                  <span class="navbar-toggler-icon"></span>
                </button>
              </div>
            </div>
          </header>
          <main role="main" class="container">
                @yield('content')
          </main><!-- /.container -->

    <footer class="text-muted">
      <div class="container">
        <p class="float-right">
          <a href="#">Back to top</a>
        </p>
      </div>
    </footer>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="/js/jquery.min.js"></script>
    <script src="/js/popper.min.js"></script>
    <script src="/js/bootstrap.js"></script>
    <script src="/js/holder.min.js"></script>
    @yield('footer-scripts')
    </body>
</html>

To make this layout work you need to download the jQuery and Bootstrap 4 CSS and JS files and put them into your public/css and public/js directories, respectively.

Finally add two routes into your app/routes/web.php file:

Route::get('store', 'StoreController@index');
Route::post('store/add-to-cart', 'StoreController@addToCart');

Now, if you open http://localhost/store URL in your web browser, you should be able to see the Store page:

Implementing Cart

Now lets implement the Shopping Cart page. Add the CartController class into your app/Http/Controllers directory:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

class CartController extends Controller
{
    public function index()
    {
        $cart = session()->get('cart');
        if ($cart == null)
            $cart = [];

        return view('cart.index')->with('cart', $cart);
    }    
}

Add the app/resources/views/cart/index.blade.php file for the view template:

@extends('layout')

@section('content')

<h1>Cart</h1>

<table class="table">
  <thead class="thead-dark">
    <tr>
      <th scope="col">Product</th>
      <th scope="col">Price</th>
      <th scope="col">Qty</th>
    </tr>
  </thead>
  <tbody>
    <?php $total = 0; ?>
    @foreach ($cart as $item)
    <?php $total += $item['price'] * $item['qty']; ?>
    <tr>
      <td>{{ $item['name'] }}</td>
      <td>${{ $item['price'] }}</td>
      <td>{{ $item['qty'] }}</td>
    </tr>
    @endforeach
  </tbody>
</table>

<p>
    <strong>Total: ${{ $total}}</strong>
</p>

<p>
    <a class="btn btn-primary btn-lg" href="/cart/pay-with-paypal">

    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-wallet" viewBox="0 0 16 16">
        <path d="M0 3a2 2 0 0 1 2-2h13.5a.5.5 0 0 1 0 1H15v2a1 1 0 0 1 1 1v8.5a1.5 1.5 0 0 1-1.5 1.5h-12A2.5 2.5 0 0 1 0 12.5V3zm1 1.732V12.5A1.5 1.5 0 0 0 2.5 14h12a.5.5 0 0 0 .5-.5V5H2a1.99 1.99 0 0 1-1-.268zM1 3a1 1 0 0 0 1 1h12V2H2a1 1 0 0 0-1 1z"/>
    </svg>
    Pay with PayPal</a>
</p>

@endsection

Add a route for the Cart page into your app/routes/web.php file:

Route::get('cart', 'CartController@index');

Now if you open the http://localhost/cart URL in your web browser, you should see the Shopping Cart page as follows:

PayPal Integration

To add PayPal support to your Shopping Cart, we will use a package called srmklive/paypal. Run the following command from your shell:

composer require srmklive/paypal ^1.0

Then add the provider to your config/app.php file:

'providers' => [

    SrmklivePayPalProvidersPayPalServiceProvider::class

]

The following command will create config/paypal.php config file for you:

php artisan vendor:publish --provider "SrmklivePayPalProvidersPayPalServiceProvider"

The config file looks like below:

return [
    'mode'    => env('PAYPAL_MODE', 'sandbox')
    'sandbox' => [
        'username'    => env('PAYPAL_SANDBOX_API_USERNAME', ''),
        'password'    => env('PAYPAL_SANDBOX_API_PASSWORD', ''),
        'secret'      => env('PAYPAL_SANDBOX_API_SECRET', ''),
        'certificate' => env('PAYPAL_SANDBOX_API_CERTIFICATE', ''),
        'app_id'      => '<app_id>',
    ],
    'live' => [
        'username'    => env('PAYPAL_LIVE_API_USERNAME', ''),
        'password'    => env('PAYPAL_LIVE_API_PASSWORD', ''),
        'secret'      => env('PAYPAL_LIVE_API_SECRET', ''),
        'certificate' => env('PAYPAL_LIVE_API_CERTIFICATE', ''),
        'app_id'      => '',
    ],
    'payment_action' => 'Sale',
    'currency'       => env('PAYPAL_CURRENCY', 'USD'),
    'billing_type'   => 'MerchantInitiatedBilling',
    'notify_url'     => '',
    'locale'         => '',
    'validate_ssl'   => false,
];

Add the following parameters to .env (you can take the API username, API password and API secret from your PayPal account settings page):

PAYPAL_MODE=sandbox
PAYPAL_SANDBOX_API_USERNAME=<your_username>
PAYPAL_SANDBOX_API_PASSWORD=<your_api_password>
PAYPAL_SANDBOX_API_SECRET=<your_api_secret>
PAYPAL_CURRENCY=USD
PAYPAL_SANDBOX_API_CERTIFICATE=

Then you need to modify the CartController and add several actions to it:

// Add this line to the beginning
use SrmklivePayPalServicesExpressCheckout;

class CartController extends Controller
{
    // ...

    public function payWithPaypal()
    {
        $cart = session()->get('cart');

        $totalAmount = 0;

        foreach ($cart as $item) {
            $totalAmount += $item['price'] * $item['qty'];
        }

        $order = new Order();
        $order->user_id = Auth::user()->id;
        $order->amount = $totalAmount;
        $order->save();

        $data = [];

        foreach ($cart as $item) {
            $data['items'] = [
                [
                    'name' => $item['name'],
                    'price' => $item['price'],
                    'desc'  => $item['name'],
                    'qty' => $item['qty'],
                ]
            ];

            $orderItem = new OrderItem();
            $orderItem->order_id = $order->id;
            $orderItem->product_id = $item['id'];
            $orderItem->quantity = $item['qty'];
            $orderItem->amount = $item['price'];
            $orderItem->save();
        }

        $data['invoice_id'] = $order->id;
        $data['invoice_description'] = "Your Order #{$order->id}";
        $data['return_url'] = route('paypal-success');
        $data['cancel_url'] = route('paypal-cancel');
        $data['total'] = $totalAmount;

        $provider = new ExpressCheckout;

        $response = $provider->setExpressCheckout($data);

        $response = $provider->setExpressCheckout($data, true);

        return redirect($response['paypal_link']);
    }

    public function paypalSuccess()
    {
        return 'Payment success!';
    }

    public function paypalCancel()
    {
        return 'Payment canceled!';
    }
}

The action payWithPaypal submits the order to PayPal. The paypalSuccess and paypalCancel actions
receive the payment status from PayPal.

Add the routes for the actions above:

Route::get('cart/pay-with-paypal', 'CartController@payWithPaypal');
Route::get('cart/paypal-success', 'CartController@paypalSuccess')->name('paypal-success');
Route::get('cart/paypal-cancel', 'CartController@paypalCancel')->name('paypal-cancel');

Now you can make payments via PayPal.

Conclusion

In this article, we’ve implemented a simple e-commerce Store page and a Shopping Cart page where the user can pay via the PayPal payment gateway. You can also consider using the module Laravel Cashier if you want to integrate with the Stripe payment gateway instead of PayPal.

Are you looking for Tech Jobs?
Discover more on Meritocracy.is!

Leave a Reply

Your email address will not be published.