Laravel E-Commerce Application Development – Payment Processing

Laravel E-Commerce Application Development – Payment Processing

Laravel E-Commerce Application Development ( 27 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 – Order Management
  27. Laravel E-Commerce Application Development – Wrap Up

This is part 24 of the Laravel E-Commerce Application Development series. In this part, we will complete payment processing for orders using PayPal payment provider.

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.

Let me explain how the PayPal Payment Processing works before jumping right into the coding.

We will start from where we left in the previous post where we saved the order details into the database. After saving the order into the database, we will send the order to another payment service class where we will create the PayPal transaction request. After creating the transaction through PayPal API we will get an approval link. Then we head over the user to that approval link.

On the PayPal page, the user will be asked to log in or use a credit/debit card to make their purchase. When the purchase will be completed, PayPal will redirect the user to a URL which we will provide while creating the PayPal transaction. On our side, we have to implement the logic to get the transaction id if the status is approved from the PayPal and then save those details in the orders table. Finally, we will show the customer a success page where we will display the order number for user record. During the whole process, we will also catch the exceptions if anything goes wrong.

Here is how it should look like after completing this Laravel tutorial.

PayPal Payment Processing in Laravel
PayPal Payment Processing in Laravel

Updating User Model for Order Relationship

Ohh we forgot to add the relationship between a User and a Order. Open the user model class and add below.

public function orders()
{
    return $this->hasMany(Order::class);
}

Installing PayPal PHP SDK

To use PayPal API in our application we will need to install the PayPal SDK for PHP. PayPal provides many different types of payment solutions so you will find many Laravel packages but I prefer to use the official PayPal PHP SDK.

To install the PayPal SDK, head over to your command line terminal and run the below command.

composer install paypal/rest-api-sdk-php

Before proceeding make sure you have saved the PayPal API keys in your settings section.

Before proceeding, please make sure you have a PayPal developer account and have generated the PayPal API Client ID and PayPal API Client Secret Key in your developer account. After generating the keys, store them in you settings table using admin section.

Creating PayPal Payment Processing Class

We will start by creating a seprate payment processing class, so create a new folder in app/ folder and name it Services. Create a new PHP class file in this folder and name it PayPalService.php.

Add the below code in this newly created file.

namespace App\Services;

use PayPal\Api\Payer;
use PayPal\Api\Item;
use Mockery\Exception;
use PayPal\Api\Amount;
use PayPal\Api\Payment;
use PayPal\Api\Details;
use PayPal\Api\ItemList;
use PayPal\Rest\ApiContext;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\PaymentExecution;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;

class PayPalService
{
    //
}

As you can see this class is empty but I have included all the required classes for making a PayPal payment work.

I included all the classes beforehand because in the past I received many queries. The solution was the required class was missing.

Updating placeOrder Method in Checkout Controller

Now we have an empty Payment Processing class, before making any implementation I would like to add this class in our CheckoutController class. Open the CheckoutController controller and include the PayPalService class like below.

use App\Services\PayPalService;

Next declare a new protected property and name it $payPal.

protected $payPal;

Now we will update our constructor to inject the PayPalService class in it.

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

Next, update the placeOrder() method with the below one.

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());

    // You can add more control here to handle if the order
    // is not stored properly
    if ($order) {
        $this->payPal->processPayment($order);
    }

    return redirect()->back()->with('message','Order not placed');
}

After storing the order in the database, we send the order to the PayPalService class’s processPayment() method which will implement in the coming section.

Setting Up PayPal API Keys in Constructor

In this section, we will update our PayPalService class and firstly set up our API keys using the constructor method of this class.

I will share the snippet by snippet code for explaination, later in this post you will find the full PayPalService class. Also I will be adding some inline comments for seprating each section.

We will start by declaring a protected property for PayPal API.

protected $payPal;

Now we will create a constructor method and set the PayPal API connection.

public function __construct()
{
    if (config('settings.paypal_client_id') == '' || config('settings.paypal_secret_id') == '') {
        return redirect()->back()->with('error', 'No PayPal settings found.');
    }

    $this->payPal = new ApiContext(
        new OAuthTokenCredential(
            config('settings.paypal_client_id'),
            config('settings.paypal_secret_id')
        )
    );

    // To use PayPal in live mode you have to add
    // the below, I prefer to use the sandbox mode only.

    //$this->payPal->setConfig(
    //    array('mode'  =>  'live')
    //);
}

In this constructor method, firstly we will check if the PayPal API keys are set up in the settings config, if not then we send the user back with a message.

Next, we are setting the payPal property to the new APIContext class which takes a new object of the OAuthTokenCredential class and pass the API keys to it.

Creating processPayment Method

In your CheckoutController you can see we are calling a processPayment() method. Let’s add that to our PayPalService class.

public function processPayment($order)
{

}

Now we will start to fill this function with PayPal payment logic.

Add below code snippet in the processPayment() method.

// Add shipping amount if you want to charge for shipping
$shipping = sprintf('%0.2f', 0);
// Add any tax amount if you want to apply any tax rule
$tax = sprintf('%0.2f', 0);

In the above code, we are setting up the shipping and tax values for PayPal.

If you are building a real application then you can have shipping methods feature in your application which will provide you the shipping values, same for the tax rates.

Next, add the below code snippet.

// Create a new instance of Payer class
$payer = new Payer();
$payer->setPaymentMethod("paypal");

In this we are making a new instance of Payer class and setting the payment method to paypal.

Next, we will add the order items to our PayPal transaction.

 // Adding items to the list
$items = array();
foreach ($order->items as $item)
{
    $orderItems[$item->id] = new Item();
    $orderItems[$item->id]->setName($item->product->name)
        ->setCurrency(config('settings.currency_code'))
        ->setQuantity($item->quantity)
        ->setPrice(sprintf('%0.2f', $item->price));

    array_push($items, $orderItems[$item->id]);
}

$itemList = new ItemList();
$itemList->setItems($items);

In this code block, we are looping through the $order items and adding them to the $items array. Note that each item will be an instance of the PayPal\Api\Item class.

Next we are creating a new instance of the PayPal\Api\ItemList class and setting items by passing the $items array we just created.

Now we will create details for this PayPal transaction by creating an instance of the PayPal\Api\Details class.

// Setting Shipping Details
$details = new Details();
$details->setShipping($shipping)
    ->setTax($tax)
    ->setSubtotal(sprintf('%0.2f', $order->grand_total));

As you can see we have passed the $shipping and $tax rates to this instance class along with the total price of this order.

Now we will set up the currency details by creating a new instance of the PayPal\Api\Amount class.

// Create chargeable amount
$amount = new Amount();
$amount->setCurrency(config('settings.currency_code'))
        ->setTotal(sprintf('%0.2f', $order->grand_total))
        ->setDetails($details);

Firstly setting the currency which we have in our settings config and then passing the total amount of the order. Lastly we are setting the details by passing $details.

Now we have the items list and the amount for PayPal API, we will now create a new instance of PayPal\Api\Transaction class.

// Creating a transaction
$transaction = new Transaction();
$transaction->setAmount($amount)
        ->setItemList($itemList)
        ->setDescription($order->user->full_name)
        ->setInvoiceNumber($order->order_number);

Starting by creating a new instance of Transaction class and then setting the amount to $amount and in description I am passing on the user name. Next set the invoice number to our order’s order number.

Next, we will create new instance of the PayPal\Api\RedirectUrls. This class takes two values, return URL (after successful completion where PayPal will redirect the user) and the cancel URL (if the user cancels the payment where he should be redirected).

// Setting up redirection urls
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl(route('checkout.payment.complete'))
             ->setCancelUrl(route('checkout.index'));

As you can see for return URL, I am passing a new route which we will add in the next section and for cancel URL I am returning the user back to the checkout page.

Now we will create the payment using the PayPal\Api\Payment class.

// Creating payment instance
$payment = new Payment();
$payment->setIntent("sale")
    ->setPayer($payer)
    ->setRedirectUrls($redirectUrls)
    ->setTransactions(array($transaction));

As you can see that we created the new instance of Payment class and set the intent to sale. Next, we pass the redirect URLs and the transaction of our order.

So far we have created the payment using the PayPal API, now we will call the create() method which will create the PayPal payment and return us an approval URL where we can redirect our user.

Add the below code right after the above code block.

try {

    $payment->create($this->payPal);

} catch (PayPalConnectionException $exception) {
    echo $exception->getCode(); // Prints the Error Code
    echo $exception->getData(); // Prints the detailed error message
    exit(1);
} catch (Exception $e) {
    echo $e->getMessage();
    exit(1);
}

$approvalUrl = $payment->getApprovalLink();

header("Location: {$approvalUrl}");
exit;

Let me go through line by line for explanation, we are trying to create a payment using the $payment passing the payPal property which actually holds our API keys. If the payment is not created, we are catching the exceptions and printing the errors.

In case we don’t get any errors and payment created successfully, we get the approval link by calling the getApprovalLink() on $payment.

Finally, we are setting the browser header to load the approval link and exit the function.

Adding Payment Complete Route

In the last section, we add the route to the return URL for PayPal payment let’s add that in our application.

Open the routes/web.php file and right after the place order route add below.

Route::get('checkout/payment/complete', 'Site\[email protected]')->name('checkout.payment.complete');

Adding complete Method in Checkout Controller

Now we will add the complete() method to our CheckoutController.

public function complete(Request $request)
{
    $paymentId = $request->input('paymentId');
    $payerId = $request->input('PayerID');

    $status = $this->payPal->completePayment($paymentId, $payerId);

    $order = Order::where('order_number', $status['invoiceId'])->first();
    $order->status = 'processing';
    $order->payment_status = 1;
    $order->payment_method = 'PayPal -'.$status['salesId'];
    $order->save();

    Cart::clear();
    return view('site.pages.success', compact('order'));
}

When a user make the payment on PayPal, PayPal will send the user back to this method long with paymentId and PayerID. Firstly, we will grab that using the Laravel’s Request class then we will send the both values to the our PayPalService class’s completePayment method (we will implement this in the next section).

Once, we get the sales id and invoice id from the PayPal, we open the order by finding it the order_number.

PayPal’s invoice id will be same as our order number because that’s what we sent in the payment object.

We save the order to processing, set the payment status to 1 (completed) and then set the sales id generated by the PayPal to the payment_method.

After all this process, we will create the shopping cart and return the user to the success page with the order details.

Adding completePayment Method in Payment Processing Class

In the previous section, we are calling completePayment() so let’s implement that.

Add the below method in your PayPalService class.

public function completePayment($paymentId, $payerId)
{
    $payment = Payment::get($paymentId, $this->payPal);
    $execute = new PaymentExecution();
    $execute->setPayerId($payerId);

    try {
        $result = $payment->execute($execute, $this->payPal);
    } catch (PayPalConnectionException $exception) {
        $data = json_decode($exception->getData());
        $_SESSION['message'] = 'Error, '. $data->message;
        // implement your own logic here to show errors from paypal
        exit;
    }

    if ($result->state === 'approved') {
        $transactions = $result->getTransactions();
        $transaction = $transactions[0];
        $invoiceId = $transaction->invoice_number;

        $relatedResources = $transactions[0]->getRelatedResources();
        $sale = $relatedResources[0]->getSale();
        $saleId = $sale->getId();

        $transactionData = ['salesId' => $saleId, 'invoiceId' => $invoiceId];

        return $transactionData;
    } else {
        echo "<h3>".$result->state."</h3>";
        var_dump($result);
        exit(1);
    }
}

In the above method, we are getting the payment from the PayPal API and creating a new instance of the PayPal/Api/PaymentExecution class by setting the payer id. Then we are calling the execute() method on our $payment by pssing the $execute and the $this->payPal which holds the API keys.

Upon successfull call to PayPal API, we get the state of the order if it’s approved then we grab the invoice_number and salesId form the transaction of the current payment.

Above procedure in simple word is loading payment details from PayPal and checking its status to grab the further more information. You can read more about PayPal API on the PayPal’s Developer Documentation site.

After getting the required information, we send it back to the CheckoutController class.

Creating Order Success Page

In the section before the last one, we finally displayed the success page for order completion. Let’s create a new view in resources/views/site/pages folder and name it success.blade.php.

Add the below markup in this file, which is simply showing the order number to the user.

@extends('site.app')
@section('title', 'Order Completed')
@section('content')
    <section class="section-pagetop bg-dark">
        <div class="container clearfix">
            <h2 class="title-page">Order Completed</h2>
        </div>
    </section>
    <section class="section-content bg padding-y border-top">
        <div class="container">
            <div class="row">
                <main class="col-sm-12">
                    <p class="alert alert-success">Your order placed successfully. Your order number is : {{ $order->order_number }}.</p></main>
            </div>
        </div>
    </section>
@stop

Great. We have completed the payment processing using PayPal for our eCommerce application. Before heading our to website and try it, I would like to create a page for authenticated users where we can show them their orders.

Adding Account Orders Route

Open the routes/web.php file and add the below route after the order complete route.

Route::get('account/orders', 'Site\[email protected]')->name('account.orders');

Adding Dropdown Link to Header

Now open the resources/views/site/partials/header.blade.php file and add the below link just before the logout link.

<a class="dropdown-item" href="{{ route('account.orders') }}">Orders</a>

Creating Account Controller

Now open the command line terminal and run the below command to generate the AccountController class.

php artisan make:controller Site\AccountController

Adding getOrders Method to Account Controller

Open the AccountController class and add the below method in it.

namespace App\Http\Controllers\Site;

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

class AccountController extends Controller
{
    public function getOrders()
    {
        $orders = auth()->user()->orders;

        return view('site.pages.account.orders', compact('orders'));
    }
}

In this method, we are loading the orders for an authenticated user and passing on to a new view.

Creating Account Orders View

Create the orders.blade.php file in resources/views/site/pages/account folder and add the below markup in it.

@extends('site.app')
@section('title', 'Orders')
@section('content')
    <section class="section-pagetop bg-dark">
        <div class="container clearfix">
            <h2 class="title-page">My Account - Orders</h2>
        </div>
    </section>
    <section class="section-content bg padding-y border-top">
        <div class="container">
            <div class="row">
            </div>
            <div class="row">
                <main class="col-sm-12">
                    <table class="table table-hover">
                        <thead>
                            <tr>
                                <th scope="col">Order No.</th>
                                <th scope="col">First Name</th>
                                <th scope="col">Last Name</th>
                                <th scope="col">Order Amount</th>
                                <th scope="col">Qty.</th>
                                <th scope="col">Status</th>
                            </tr>
                        </thead>
                        <tbody>
                            @forelse ($orders as $order)
                                <tr>
                                    <th scope="row">{{ $order->order_number }}</th>
                                    <td>{{ $order->first_name }}</td>
                                    <td>{{ $order->last_name }}</td>
                                    <td>{{ config('settings.currency_symbol') }}{{ round($order->grand_total, 2) }}</td>
                                    <td>{{ $order->item_count }}</td>
                                    <td><span class="badge badge-success">{{ strtoupper($order->status) }}</span></td>
                                </tr>
                            @empty
                                <div class="col-sm-12">
                                    <p class="alert alert-warning">No orders to display.</p>
                                </div>
                            @endforelse
                        </tbody>
                    </table>
                </main>
            </div>
        </div>
    </section>
@stop

This view consists of a simple table which shows the order’s information. Now head over to your site and try to make an order then make a payment on PayPal using the sandbox account, you will be redirected back to the success page everything goes right.

I know you will face some problems if you are doing all this first time, most issues will be regarding the PayPal developer account or API keys. Make sure you have a developer account and generate the keys for sandbox mode.

PayPalService Class File

Here is the content of the full class.

namespace App\Services;

use PayPal\Api\Payer;
use PayPal\Api\Item;
use Mockery\Exception;
use PayPal\Api\Amount;
use PayPal\Api\Payment;
use PayPal\Api\Details;
use PayPal\Api\ItemList;
use PayPal\Rest\ApiContext;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\PaymentExecution;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;

class PayPalService
{
    protected $payPal;

    public function __construct()
    {
        if (config('settings.paypal_client_id') == '' || config('settings.paypal_secret_id') == '') {
            return redirect()->back()->with('error', 'No PayPal settings found.');
        }

        $this->payPal = new ApiContext(
            new OAuthTokenCredential(
                config('settings.paypal_client_id'),
                config('settings.paypal_secret_id')
            )
        );

        // To use PayPal in live mode you have to add
        // the below, I prefer to use the sandbox mode only.

        //$this->payPal->setConfig(
        //    array('mode'  =>  'live')
        //);
    }

    public function processPayment($order)
    {
        // Add shipping amount if you want to charge for shipping
        $shipping = sprintf('%0.2f', 0);
        // Add any tax amount if you want to apply any tax rule
        $tax = sprintf('%0.2f', 0);

        // Create a new instance of Payer class
        $payer = new Payer();
        $payer->setPaymentMethod("paypal");

        // Adding items to the list
        $items = array();
        foreach ($order->items as $item)
        {
            $orderItems[$item->id] = new Item();
            $orderItems[$item->id]->setName($item->product->name)
                ->setCurrency(config('settings.currency_code'))
                ->setQuantity($item->quantity)
                ->setPrice(sprintf('%0.2f', $item->price));

            array_push($items, $orderItems[$item->id]);
        }

        $itemList = new ItemList();
        $itemList->setItems($items);

        // Setting Shipping Details
        $details = new Details();
        $details->setShipping($shipping)
                ->setTax($tax)
                ->setSubtotal(sprintf('%0.2f', $order->grand_total));

        // Create chargeable amount
        $amount = new Amount();
        $amount->setCurrency(config('settings.currency_code'))
                ->setTotal(sprintf('%0.2f', $order->grand_total))
                ->setDetails($details);

        // Creating a transaction
        $transaction = new Transaction();
        $transaction->setAmount($amount)
                ->setItemList($itemList)
                ->setDescription($order->user->full_name)
                ->setInvoiceNumber($order->order_number);

        // Setting up redirection urls
        $redirectUrls = new RedirectUrls();
        $redirectUrls->setReturnUrl(route('checkout.payment.complete'))
                     ->setCancelUrl(route('checkout.index'));

        // Creating payment instance
        $payment = new Payment();
        $payment->setIntent("sale")
                ->setPayer($payer)
                ->setRedirectUrls($redirectUrls)
                ->setTransactions(array($transaction));

        try {

            $payment->create($this->payPal);

        } catch (PayPalConnectionException $exception) {
            echo $exception->getCode(); // Prints the Error Code
            echo $exception->getData(); // Prints the detailed error message
            exit(1);
        } catch (Exception $e) {
            echo $e->getMessage();
            exit(1);
        }

        $approvalUrl = $payment->getApprovalLink();

        header("Location: {$approvalUrl}");
        exit;
    }

    public function completePayment($paymentId, $payerId)
    {
        $payment = Payment::get($paymentId, $this->payPal);
        $execute = new PaymentExecution();
        $execute->setPayerId($payerId);

        try {
            $result = $payment->execute($execute, $this->payPal);
        } catch (PayPalConnectionException $exception) {
            $data = json_decode($exception->getData());
            $_SESSION['message'] = 'Error, '. $data->message;
            // implement your own logic here to show errors from paypal
            exit;
        }

        if ($result->state === 'approved') {
            $transactions = $result->getTransactions();
            $transaction = $transactions[0];
            $invoiceId = $transaction->invoice_number;

            $relatedResources = $transactions[0]->getRelatedResources();
            $sale = $relatedResources[0]->getSale();
            $saleId = $sale->getId();

            $transactionData = ['salesId' => $saleId, 'invoiceId' => $invoiceId];

            return $transactionData;
        } else {
            echo "<h3>".$result->state."</h3>";
            var_dump($result);
            exit(1);
        }
    }
}

What’s Next

In the next post, we will back to the admin section and add a orders section from where we will be able to manage all incoming orders.

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.

8 comments on “Laravel E-Commerce Application Development – Payment Processing

  1. Hi, I hope you are doing awesome!

    Thank you for this tutorial series, I have learnt a lot.

    One question is, I downloaded your repository but when I try to access the product index in the admin page, it gives this error

    Invalid argument supplied for foreach()

    can you tell me why?

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.