Posted on Leave a comment

How To Create A Point Of Sales System With Laravel and Vue Pt. 2

Expanding Upon Our Point Of Sales App

In part one we created a point of sales system using Laravel and Vue (if you haven’t done part 1 here is the source code). It had very basic functionality but it got the job done, you could charge debit/credit cards. The basic design looked like this
Point of sales part 1 screenshot
In part two we will add email invoice functionality so that we can email our customers and they can pay without us having to know their account information. For this we will be using Laravel’s notification framework on the backend and utilizing the web payment request API with Vue.js on the front end.
 

The Invoice Model

In the command line we will create a new model to represent our invoices type in the following command to generate your model and migration

php artisan make:model -m Invoice

 
Once the model and migration are made, open up migration and edit accordingly:

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateInvoicesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('invoices', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->longText('description');
            $table->integer('amount');
            $table->string('email');
            $table->string('charge_id');
            $table->boolean('is_paid')->default(false);
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('invoices');
    }
}

As you can see there are only a few fields

  • name of person being invoiced
  • description of the invoice
  • amount to be invoiced
  • email address for invoice to be mailed to
  • stripe charge id
  • boolean indicating whether or not the invoice has been paid

Open up the Invoice.php model and add the following to the $fillable array

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Invoice extends Model
{
    use Notifiable;
    //
    protected $fillable = ['name','amount','email','description','charge_id'];
}

Next we need to create some api routes and a RESTful controller. Let’s start with the routes, open up routes/api.php and add the following routes

Route::middleware('auth:api')->resource('/invoice','InvoiceController');
Route::post('/invoice/pay/{id}','InvoiceController@payInvoice');

Open up routes/web.php and add the following

Route::get('/invoice/pay/{id}','InvoiceController@getPayInvoice');

Now create a new controller

php artisan make:controller --resource InvoiceController

Open up the controller and replace with the following

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Invoice;
class InvoiceController extends Controller
{
  public function __construct(){
    \Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
  }
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
        $invoices = Invoice::where('is_paid',true)->get();
        return response()->json([
          'invoices' => $invoices
        ]);
    }
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
        $invoice = Invoice::Create($request->all());
        $invoice->notify(new \App\Notifications\InvoiceCreated());
        return response()->json([
          'invoice' => $invoice
        ]);
    }
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }
    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
        $invoice = Invoice::findOrFail($id);
        $invoice->fill($request->all())->save();
        return response()->json([
          'invoice' => $invoice
        ]);
    }
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
        return response()->json([
          'success' => Invoice::findOrFail($id)->delete()
        ]);
    }
    function getPayInvoice($id){
      return view('invoice')->with([
        'invoice' => Invoice::findOrFail($id)
      ]);
    }
    function payInvoice(Request $request,$id){
      $invoice = Invoice::findOrFail($id);
      try {
        // Use Stripe's library to make requests...
        $token = \Stripe\Token::create(array(
          "card" => array(
            "number" => $request->details['cardNumber'],
            "exp_month" => $request->details['expiryMonth'],
            "exp_year" => $request->details['expiryYear'],
            "cvc" => $request->details['cardSecurityCode']
          )
        ));
        \Stripe\Charge::create(array(
          "amount" => $invoice->amount,
          "currency" => "usd",
          "source" => $token, // obtained with Stripe.js
          "description" => $invoice->description,
          "receipt_email" => $invoice->email
        ));
        return response()->json([
          'success' => true
        ]);
      } catch(\Stripe\Error\Card $e) {
        // Since it's a decline, \Stripe\Error\Card will be caught
        return response()->json($e->getJsonBody());
      } catch (\Stripe\Error\RateLimit $e) {
        // Too many requests made to the API too quickly
        return response()->json($e->getJsonBody());
      } catch (\Stripe\Error\InvalidRequest $e) {
        // Invalid parameters were supplied to Stripe's API
        return response()->json($e->getJsonBody());
      } catch (\Stripe\Error\Authentication $e) {
        // Authentication with Stripe's API failed
        // (maybe you changed API keys recently)
        return response()->json($e->getJsonBody());
      } catch (\Stripe\Error\ApiConnection $e) {
        // Network communication with Stripe failed
        return response()->json($e->getJsonBody());
      } catch (\Stripe\Error\Base $e) {
        // Display a very generic error to the user, and maybe send
        // yourself an email
        return response()->json($e->getJsonBody());
      } catch (Exception $e) {
        // Something else happened, completely unrelated to Stripe
        return response()->json($e->getJsonBody());
      }
    }
}

Most of the CRUD operations are simple, in the create method we are calling a notification on the invoice (we will create the notification next don’t worry), and we have the charge method which is really just copy and paste from the other charge method except the amount comes from the invoice.
The last thing needed on the back end is the notification. Let’s go ahead and create that.

php artisan make:notification InvoiceCreated

In the notification file we just send an email with a link to pay

<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class InvoiceCreated extends Notification
{
    use Queueable;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }
    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }
    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
                    ->subject('You Have A New Invoice Due')
                    ->line('Pay NOW or suffer the consequences!')
                    ->action('Pay UP!', url('/invoice/pay/'.$notifiable->id))
                    ->line('Thank you for using our application!');
    }
    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

The Front End

We are going to create 2 components and a new page so people can pay their invoice. Create a new component in your vue application name it InvoiceComponent and replace with the following

<template>
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Make A Invoice</div>
                    <div class="panel-body">
                        <fieldset>
                        <div class="form-group">
                        <label class="col-sm-3 control-label" for="amount">Amount</label>
                        <div class="col-sm-9">
                          <input type="number" class="form-control"  min="1.00" max="10000.00" id="amount" placeholder="Amount To Charge" v-model="amount">
                        </div>
                      </div>
                      <div class="form-group">
                      <label class="col-sm-3 control-label" for="name">Name</label>
                      <div class="col-sm-9">
                        <input type="text" class="form-control"  id="name" placeholder="Name To Charge" v-model="name">
                      </div>
                    </div>
                        <div class="form-group">
                        <label class="col-sm-3 control-label" for="email">Email</label>
                        <div class="col-sm-9">
                          <input type="email" class="form-control"  id="email" placeholder="Email Receipt" v-model="email">
                        </div>
                      </div>
                      <div class="form-group">
                      <label class="col-sm-3 control-label" for="description">Description</label>
                      <div class="col-sm-9">
                        <input type="text" class="form-control"  id="description" placeholder="Credit Card Description"  v-model="description">
                      </div>
                    </div>
                      <div class="form-group">
                        <div class="col-sm-offset-3 col-sm-9">
                          <button type="button" class="btn btn-success" v-on:click="createInvoice">Create Invoice</button>
                        </div>
                      </div>
                        </fieldset>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        mounted() {
            console.log('Component mounted.')
        },
        data(){
        return{
        name: null,
        amount: null,
        email: null,
        description: null,
        invoices: []
        }
        },
        created(){
          var that = this;
          axios.get('/api/invoice').then(function(data){
            that.invoices = data.data.invoices;
          });
        },
        methods: {
        createInvoice: function(){
        var that = this;
        axios.post('/api/invoice',{name: this.name, amount: this.amount * 100, description: this.description, email: this.email})
        .then(function(data){
          that.invoices.push(data.data.invoice);
        alert('Success!')
        }).catch(function(error){
        alert(error.message);
        });
        }
        }
    }
</script>

Drop that new component in your home.blade file and your page should look like this

Create another component call it InvoicePayController and place the following

<template>
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Make A Invoice</div>
                    <div class="panel-body">
                        Your total amount due is {{invoice.amount / 100}}
                        <br>
                        {{invoice.description}}
                        <br>
                        <button v-on:click="payInvoice()" class="btn btn-primary">Pay</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        mounted() {
            console.log('Component mounted.')
        },
        data(){
        return{
          invoice: null,
          paymentRequest: null
        }
        },
        created(){
          this.invoice = JSON.parse(this.invoiceObject);
        },
        methods: {
        payInvoice: function(){
          const supportedPaymentMethods = [
            {
              supportedMethods: 'basic-card',
            }
          ];
          const paymentDetails = {
            total: {
              label: this.invoice.description,
              amount:{
                currency: 'USD',
                value: this.invoice.amount/100
              }
            }
          };
          // Options isn't required.
          const options = {};
          this.paymentRequest = new PaymentRequest(
            supportedPaymentMethods,
            paymentDetails,
            options
          );
          var that = this;
          this.paymentRequest.show().then(function(data){
            axios.post('/api/invoice/pay/'+that.invoice.id,data).then(function(data){
              alert('Success');
              return paymentResponse.complete();
            })
          }).catch(function(err){
            console.log(err);
            return paymentResponse.complete();
          });
        },
      },
      props: ['invoiceObject']
        }
</script>

We are making use of the Payment Request API to gather all user card data to simplify the checkout process, you can read up more about it here. Create a new blade view file and call it Invoice.blade.php and put this new component there


@extends('layouts.app')
@section('content')
@endsection


Now your customers can pay invoices sent to them via email! Please don’t forget to subscribe to my Youtube Channel and like the video and post! Also don’t forget if you want to support this blog pre-orders for my new e-book are open now until the end of month!