Sujip Thapa - Blog about Laravel, PHP, Javascript & Web Development https://sujipthapa.co A modern web development blog in PHP, Laravel and other interesting tutorials explaining the real features, tips & other cool stuff. en-us 2023-03-21T16:30:25+05:45 <![CDATA[Stripe Payment Gateway Integration with Laravel]]> https://sujipthapa.co/blog/stripe-payment-gateway-integration-with-laravel c43f30fe-2a3d-418a-a4fe-11f93396273d 2022-04-23T19:35:00+05:45 Mr. Sujip Thapa Before through this blog, I've covered some of the popular payment gateways like PayPal, SecurePay, eSewa, etc.

This article is about the Stripe payment gateway to handle one-time payments with express checkout.

Introduction

Stripe is a payment service provider dealing with online payment processing for businesses around the globe.

Stripe offers different APIs for payment processing, which can be for one-time or subscription-based and recurring payments, and many more.

In this article, we're writing about the steps involved in integrating the express checkout.

Prerequisites

This article is for developers having an experience of mid-level or above with Laravel and PHP, as we won't cover the basics.

So we assume you already have experience with the below points.

  • Having good knowledge of PHP and Laravel, third-party APIs.
  • Understand composer and use packages and libraries with the composer in the Laravel projects.

Setup

We assume you already have a Laravel project installed in your machine, and you are ready to start the stripe integration.

Install official stripe-php-sdk from packagist with composer using the below command.

composer require stripe/stripe-php

Before diving into the code, go to the stripe dashboard and grab the API credentials needed for the integration.

The red border boxes shown in the above picture have the place to reveal the Publishable key and Secret key from the stripe dashboard under the Developers tab.

As of crafting this article, Laravel is at 9.* version.

You can then store the API credentials, depending on how you manage the API credentials via database or put them on the .env file.

Here we are going to use the .env file for this article.

STRIPE_PUBLISHABLE_KEY=YOUR-KEY-HERE
STRIPE_SECRET_KEY=YOUR-SECRET-KEY-HERE

Coding

Let's set up routes in Laravel where the user can access the order and continue through the payment flow.

<?php

use App\Http\Controllers\Payments\StripeController;
use Illuminate\Support\Facades\Route;

Route::get('/checkout/overview', [StripeController::class, 'overview'])
    ->name('checkout.overview');
Route::post('/checkout/payment/{order}/stripe', [StripeController::class, 'payment'])
    ->name('checkout.payment');
Route::get('/checkout/payment/{order}/approved', [StripeController::class, 'approved'])
    ->name('checkout.approved');
Route::get('/checkout/payment/{order}/cancelled', [StripeController::class, 'cancelled'])
    ->name('checkout.cancelled');

Now, we need a controller for handling the incoming request and responses from the route.

<?php
namespace App\Http\Controllers\Payments;

use App\Models\Order;
use App\Payments\Stripe;
use Illuminate\Routing\Controller;

class StripeController extends Controller
{
    public function overview()
    {
        // implement your own order overview here

        $order = Order::query()
            ->whereHas('items')
            ->with(['items'])
            ->paymentPending()
            ->first();

        return view('stripe', ['order' => $order]);
    }

    /**
     * @param $uuid
     */
    public function payment($uuid)
    {
        // implement your own order here

        $order = Order::paymentPending()
            ->where('uuid', $uuid)
            ->firstOrFail();

        return Stripe::initialize($order);
    }

    /**
     * @param $uuid
     */
    public function approved($uuid)
    {
        $order = Order::query()
            ->where('uuid', $uuid)
            ->firstOrFail();

        return Stripe::captured($order);
    }

    /**
     * @param $uuid
     */
    public function cancelled($uuid)
    {
        // render your cancelled page here

        return redirect()->route('checkout.overview')
            ->with('error', 'You have cancelled the payment. Therefore, the order has not been placed yet.');
    }
}

To interact with the database, we need a model. Here we have only created a single Order.php model and used the order items as static values to simplify the integration.

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->uuid();
            $table->string('transaction_id')->nullable();
            $table->float('amount')->unsigned()->nullable();
            $table->integer('payment_status')->unsigned()->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

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

You have to deal with the dynamic order items based on your application architecture.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    use HasFactory;

    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['uuid', 'invoice_number', 'transaction_id', 'total_paid', 'payment_status'];

    /**
     * @return \App\Models\OrderItem
     */
    public function items()
    {
        return $this->hasMany(OrderItem::class);
    }

    /**
     * @param $query
     * @return \Illuminate\Database\Query\Builder
     */
    public function scopePaymentPending($query)
    {
        return $query->where("{$this->table}.payment_status", self::PAYMENT_PENDING);
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class OrderItem extends Model
{
    use HasFactory;

    /**
     * @var string
     */
    protected $table = 'order_items';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['uuid', 'order_id', 'name', 'sku', 'quantity', 'description', 'amount'];

    /**
     * @return \App\Models\Order
     */
    public function order()
    {
        return $this->belongsTo(Order::class);
    }
}

We've got a simple page to display a continue to the payment form.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<title>Payment</title>
</head>
<body>
<section class="container mx-auto mt-4 mb-4">
    <form action="{{ route('checkout.payment', [$order->uuid]) }}" method="POST">
        @csrf

        <button type="submit" class="bg-slate-900 text-white text-sm font-semibold h-10 px-6 rounded-md sm:w-auto">Continue to Payment</button>
    </form>
</section>
</body>
</html>

For an e-commerce website, this page needs to be an order overview with order items and payment gateways choices for users.

Finally, we have a dedicated class created to handle the initialization of stripe checkout.

<?php
namespace App\Payments;

use Exception;
use Stripe\Checkout\Session;
use Stripe\Stripe as Checkout;

class Stripe
{
    /**
     * @param $order
     */
    public static function initialize($order)
    {
        $static = new static;

        $checkout = Checkout::setApiKey(env('STRIPE_SECRET_KEY'));

        try {
            $session = Session::create([
                'client_reference_id' => $order->uuid,
                'billing_address_collection' => 'required',
                'line_items' => $static->toLineItems($order),
                'mode' => 'payment',
                'success_url' => $static->toReturnUrl($order),
                'cancel_url' => $static->toCancelUrl($order),
            ]);
        } catch (Exception $e) {
            return redirect()->route('checkout.overview')
                ->with('error', 'Unknown error occurred, please try again later.');
        }

        $order->update(['transaction_id' => $session->id]);

        if (filter_var($session->url, FILTER_VALIDATE_URL)) {
            return redirect()->to($session->url, 303);
        }

        return redirect()->route('checkout.overview')
            ->with('error', 'Unknown error occurred, please try again later.');
    }

    /**
     * @param $order
     */
    public static function captured($order)
    {
        $checkout = Checkout::setApiKey(env('STRIPE_SECRET_KEY'));

        try {
            $session = Session::retrieve($order->transaction_id);
        } catch (Exception $e) {
            return redirect()->route('checkout.overview')
                ->with('error', 'The payment was not successful, please retry again.');
        }

        if ($approved = $session && $session->payment_status == 'paid') {
            $order->update(['payment_status' => $order::PAYMENT_COMPLETED]);

            // take user to order placed page with message

            return redirect()->route('checkout.overview')
                ->with('success', 'Thank you for placing an order, the payment was captured successfully.');
        }

        return redirect()->route('checkout.overview')
            ->with('error', 'The payment was not successful, please retry again.');
    }

    /**
     * @param $order
     * @return array
     */
    public function toLineItems($order)
    {
        $stack = [];

        $static = new static;

        foreach ($order->items as $item) {
            $stack[] = [
                'price_data' => [
                    'currency' => 'USD',
                    'product_data' => [
                        'name' => $item->name,
                    ],
                    'unit_amount' => $static->toCentAmount($item->amount),
                ],
                'quantity' => $item->quantity,
            ];
        }

        return $stack;
    }

    /**
     * @param $order
     */
    public function toCancelUrl($order)
    {
        return route('checkout.cancelled', $order->uuid);
    }

    /**
     * @param $order
     */
    public function toReturnUrl($order)
    {
        return route('checkout.approved', $order->uuid);
    }

    /**
     * @param $amount
     */
    public function toCentAmount($amount)
    {
        return (int) ($amount * 100);

        return number_format($amount, 2, '.', '');
    }
}


The request parameters are validated, and if everything is accurate, the request will be auto redirected to the payment gateway service to handle the actual payment.

After the payment, the user gets redirected back to the merchant website. The response received needs to be validated again to make sure the transaction was fully captured or not.

Webhook

We highly recommend handling the payment events via webhook. The webhook is a legit way to verify the payment status of an order.

The webhook is a far more secure way to verify the payment status of an order because it happens behind the scene on your server, as no user interaction is involved.

Conclusion

Thanks for following the article up to the end. I hope it was helpful to you.

Feel free to share this article on social media if you wish to share it in your circle.

]]>
<![CDATA[How to Get a Free SSL Certificate for Your Website]]> https://sujipthapa.co/blog/how-to-get-a-free-ssl-certificate-for-your-website d489e43b-cda8-4a86-b63e-6e48d86c6fad 2022-03-20T00:00:00+05:45 Mr. Sujip Thapa Having is an SSL certificate is becoming important nowadays to gain trust from the customer. Already, trusted browsers like Google Chrome have started to show Not Secure on the URL bar if the website does not have an SSL certificate installed on the server for the website.

For websites accepting an online payment from their customer by selling some product, having an SSL certificate is mandatory to protect their e-commerce transaction data.

Getting an SSL certificate in the past used to cost money.
However, some providers now offer it free to make the internet a safe place.

If it is for your blog or making a small business website, almost likely everyone wants to keep the server and website costs lower.

You get it free of cost. Why not use it and secure the data on your website to make the internet world safer for everyone?

How to get a free SSL certificate?

The answer is simple. There are multiple ways you can get a free SSL certificate in 2022.

We will take you through the step-by-step guide here to show you how you can get it and install it on your server.

Prerequisites

I usually write down the article for Laravel developers in this blog. Laravel framework requires a server that can be easily accessible through an SSH connection.

I use Digital Ocean for almost all of my Laravel applications. So I heavily recommend Laravel developers to use it as it is pretty good for the Laravel framework to host with them.

  • Have a VPS server like Digital Ocean, Linode, Vultr, etc.
  • The example I will use it with the Nginx server.
  • A Cloudflare account setup.

So, if you already have a site and want an SSL certificate to be installed and configured, then you are at the right place here.

Step 1 - Generating an Origin CA TLS Certificate from Cloudflare

Cloudflare offers a free TLS certificate signed by them to install on your Nginx server.

The primary use of this TLS certificate is to establish a secure connection between your server and Cloudflare's servers.

Log in to your Cloudflare account in any secure web browser. Go to select your particular domain. Then, navigate to the SSL/TLS section on the dashboard.

Right there, navigate to the Origin Server tab, and click on the Create Certificate button.

Keep the selected option Generate private key and CSR with Cloudflare by default.

Now, click Create button, and on the next page, you will see the Origin Certificate and Private key.

You need to copy the generated content on those two particular keys and save it on your server.

Due to security reasons, the Private key will not show again, copy both of the keys to your server and make sure you keep the backup before you decide to click Ok.

We will take the /etc/ssl directory to keep the origin certificate and the private key files on the server.

The folder already exists on the server. Go to copy the key on the Origin Certificate and save it to /etc/ssl/cert.pem

sudo nano /etc/ssl/cert.pem


Similarly, save the private key.

sudo nano /etc/ssl/key.pem

Note: Be sure to avoid blank lines when copying the keys for the relevant certificates.

Now that you copied the key and certificate files to your server, you need to update the Nginx configuration to use them.

Step 2 - Pointing the Origin CA Certificate to Nginx

You need to update the Nginx configuration for your site to use the origin certificate and private key to secure the connection between Cloudflare’s servers and your server.

Next, you need to make sure the UFW allows HTTPS traffic.

You need to Enable Nginx Full, which will open both port 80 (HTTP) and port 443 (HTTPS)

sudo ufw allow 'Nginx Full'


Now reload UFW

sudo ufw reload


Now, you need to check the new rules are allowed and that UFW is active.

sudo ufw status

The output looks like below.

Output
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)           ALLOW       Anywhere (v6)


Now you are ready to adjust your Nginx server block. Nginx creates a default server block during installation. Remove it if it still exists, as you’ve already configured a custom server block for your domain.

sudo nano /etc/nginx/sites-available/site_domain

The basic configuration on the file should look like this.

server {
        listen 80;
        listen [::]:80;

        root /var/www/site_domain/html;
        index index.html index.htm index.nginx-debian.html;

        server_name site_domain www.site_domain;

        location / {
                try_files $uri $uri/ =404;
        }
}

Here, the job is to modify the Nginx configuration file to follow the following instructions below.

  • Listen to port 80 and redirect all requests to use HTTPS.
  • Listen to port 443 and communicate with the certificate files added in the previous stage.

The file modification looks like below.

server {
    listen 80;
    listen [::]:80;
    server_name site_domain www.site_domain;
    return 302 https://$server_name$request_uri;
}

server {

    # SSL configuration

    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate         /etc/ssl/cert.pem;
    ssl_certificate_key     /etc/ssl/key.pem;

    server_name site_domain www.site_domain;

    root /var/www/site_domain/html;
    index index.html index.htm index.nginx-debian.html;


    location / {
            try_files $uri $uri/ =404;
    }
}

Finally, save the file and exit from the edit mode.

Now, test the Nginx configuration syntax to avoid errors.

sudo nginx -t

After all, when there are no syntax errors. You can reload the Nginx configuration to enable the changes on the server.

sudo systemctl restart nginx

Enable up Full (strict) mode on the SSL/TLs section on the dashboard.

Go to the Overview tab under SSL/TLS section on the dashboard.

By setting this up, Cloudflare always encrypts the connection between your server and their servers.

Finally, visit your website to validate the setup is working as intended.

The browser should report that the site is secure.

Step 3 - Set up Authenticated Origin Pulls

We need to set up Authenticated Origin Pulls to validate the origin server taking the Cloudflare rather than others.

We are validating the certificate from Cloudflare here.

Download the Cloudflare certificate signed by a CA from Cloudflare’s documentation.

Create this certificate in the path /etc/ssl/cloudflare.crt and save, avoid the blank lines on the file.

Again, update your Nginx configuration to use TLS Authenticated Origin Pulls to validate the requests coming to the server.

Open the configuration file for your domain.

sudo nano /etc/nginx/sites-available/site_domain

Add the ssl_client_certificate and ssl_verify_client directives as shown below and save the file.

. . .

server {

    # SSL configuration

    ...
    
    ssl_client_certificate /etc/ssl/cloudflare.crt;
    ssl_verify_client on;

    . . .

Verify the Nginx configuration and reload the server to enable the changes.

sudo nginx -t

sudo systemctl restart nginx

Finally, go to SSL/TLS section in the Cloudflare dashboard, open the Origin Server tab and toggle the Authenticated Origin Pulls option.

Visit your domain to validate your changes and to test the changes toggle between the Authenticated Origin Pulls option in the Cloudflare dashboard.

Conclusion

Thanks for reading up to the end. We hope this article helped to secure another website to the internet world.

Happy Coding!

]]>
<![CDATA[Manage multiple PHP versions in Linux: 2022 edition]]> https://sujipthapa.co/blog/manage-multiple-php-versions-in-linux-2022-edition 9c61e7ef-90d4-4b6e-bf94-1dfc97609dbf 2022-03-12T00:00:00+05:45 Mr. Sujip Thapa Do you also have the below question in your mind?

How to manage multiple PHP versions in your local development environment?

If you are having trouble managing multiple PHP versions in your local development environment, then you're at the right place to get the best solution for your issue.

If you are over Linux like me, it is possible to install multiple PHP versions to run one version at a time with your web server.

You may need to switch between them as per your requirement with your project that supports the particular PHP version.

Assume that you have installed multiple PHP versions (let's take a few, PHP 5.6, 7.0, 7.2, 7.4, 8.0, and 8.1).

Also, keep in mind that you can only set a single version as a default one to run with your web server (Nginx or Apache).

It is a challenge to become a modern web developer, as one should stay updated with new technologies and have to play with them every day.

Besides that, if a developer handles packages, frameworks, and libraries and their client's projects, they need to have a supported setup to apply new updates and fixes for their works.

Let's dive into the actual workflow on how you can manage multiple PHP versions in your machine.

Open up your favorite code editor and copy and paste the snippet below.

phpswap () {
    if [[ -z "$1" ]]; then
        echo "You did not specify a version (5.6, 7.0, 8.0, 8.1)"
    elif [[ "$1" == "5.6" ]]; then
        echo "Switching to PHP 5.6"
        sudo ln -sf /usr/bin/php5.6 /etc/alternatives/php
        
        sudo a2dismod php7.0
        sudo a2dismod php8.0
        sudo a2dismod php8.1
        
        sudo a2enmod php5.6
        
        sudo systemctl restart apache2

    elif [[ "$1" == "7.0" ]]; then
        echo "Switching to PHP 7.0"    
        sudo ln -sf /usr/bin/php7.0 /etc/alternatives/php
        
        sudo a2dismod php5.6
        sudo a2dismod php8.0
        sudo a2dismod php8.1
        
        sudo a2enmod php7.0

        sudo systemctl restart apache2

    elif [[ "$1" == "8.0" ]]; then
        echo "Switching to PHP 8.0"
        sudo ln -sf /usr/bin/php8.0 /etc/alternatives/php
        sudo a2dismod php5.6
        sudo a2dismod php7.0
        sudo a2dismod php8.1

        sudo a2enmod php8.0
        sudo systemctl restart apache2

    elif [[ "$1" == "8.1" ]]; then
        echo "Switching to PHP 8.1"
        sudo ln -sf /usr/bin/php8.1 /etc/alternatives/php
        sudo a2dismod php5.6
        sudo a2dismod php7.0
        sudo a2dismod php8.0

        sudo a2enmod php8.1
        sudo systemctl restart apache2
    else
        echo "You did not specify a version (5.6, 7.0, 8.0, 8.1)"
    fi
}

The script has support for multiple versions of PHP and has the way to run it with a single command and easy to remember bash function that you can run on your terminal whenever you need it.

Apply the relevant changes to the actual version you want to use in your environment.

After all, you need to apply it to the run time of your system somewhere.

Open up your terminal and hit the below command.

nano ~/.bashrc

Once the edit mode appears on your screen, paste the script somewhere in the file and save it.

Log off your system and login in again, or reboot if necessary, then you are ready to go with it.

Usage

Open up your terminal and check your current php version with php -v

After showing the current version, you can hit phpswap, and it alerts you with options you have set up previously with the bash function.

In my case, it shows like this below.

➜ phpswap
You did not specify a version (7.0, 8.0, 8.1)

Then, hit phpswap 8.1, and it does everything else for you, and after it finishes running, it's ready with another version activated.

Easy right?

Conclusion

Thanks for reading this post up to the end.

If the article was helpful to you, share it with your circle to help spread the good content.

If you have any feedback feel free to submit the contact us form, and I will get your message right into my inbox.

Happy Coding!

]]>
<![CDATA[Laravel Jetstream: Quick Review]]> https://sujipthapa.co/blog/laravel-jetstream-quick-review d6f48ab2-eb68-4f8b-b828-9c1c162212f1 2020-09-05T21:40:00+05:45 Mr. Sujip Thapa The official team from Laravel has announced the next major version, Laravel 8.0, to release on the 8th of September 2020.

Along with this significant release, few first-party packages, including Laravel Jetstream, Fortify are also releasing to join the Laravel ecosystem.

The author of Laravel initially announced Laravel Jetstream at Laracon Online 2020 with a complete walk-through featuring the new goodies for Laravel 8.

The packages releasing will also be the next free and open-source packages by the Laravel team.

What is Jetstream?

Laravel Jetstream is a brand new application scaffolding package, which comes with some convenient features, and gives a perfect start point for your next Laravel 8 project.

In short, Jetstream includes features like login, registration, email verification, 2FA, session management, API support, and teams support.

The past versions of Laravel stood with the Bootstrap framework for a long time. As of now, with the extensive popularity of Tailwind CSS, Jetstream is also designed using Tailwind CSS, which is a utility-first CSS framework.

Jetstream offers two different flavors to choose between Livewire, Inertia, which allows developers to use their preferred ones.

Read: How to Deploy Laravel Application to VPS Server

Usage

$ php artisan jetstream:install livewire

// or

$ php artisan jetstream:install inertia

For teams feature, add --teams option on the artisan command.

$ php artisan jetstream:install livewire --teams

Once you install your Jetstream stack, remember to compile your frontend assets using the command below.

npm install && npm run dev

Through, Laravel Installer

The team has recently released the v4 version of the Laravel installer to support Jetstream's installation via the installer. Make sure you upgrade your composer package if you have it already installed.

$ laravel new your-next-project-name --jet

Features Overview

You will be able to see this beautifully designed dashboard view once you set it up correctly.

Laravel Jetstream Dashboard

User Profile

Jetstream comes with a profile edit feature for the user, and the forms fields are necessary standard fields, including profile photo for the user registration, so you are always free to customize those files for your use case.

Security

Jetstream brings features like change password, enable 2FA, revoke authenticated browser sessions under the account. These are the regular features that most do not need customization. However, if you would like to modify, you could change the relevant code in action classes under the app/Actions/Fortify directory.

Email Verification

Jetstream out of the box comes with an email verification feature. If you decide to enable this feature, once a user registers for your project, they will be taken to a screen to click the verification link sent to their email used during the registration.

To enable this feature, scroll to features array within the fortify.php config file, as you have full control to edit the file.

Account Deletion

Jetstream introduces an action panel that allows the user to delete their account entirely. You have full control over the code to customize for the backend logic under App\Actions\Jetstream classes.

API Support

Jetstream comes with first-party integration with Laravel Sanctum for API support. If you wonder how the Laravel Sanctum works, follow the official docs for full information.

Inside Jetstream, with Sanctum's use, each user can generate multiple API tokens for their account.

These tokens are used in API call to authorize the access to the application via the API call.

You must enable this feature as well under the jetstream.php config file.

'features' => [
     Feature::api(),
],

Teams

Jetstream packs a great feature as teams, as many people are going to use it in their next project. It gives a great way to manage a team, create a team, switch between the team, even manage roles, permission to the member, etc.

The team feature also needs to be enabled via the jetstream.php config file.

'features' => [
    Feature::teams(),
],

To read in-depth about the teams feature, and use case and customization, follow the official documentation.

While using any of the two choices from Livewire or Inertia, you can still utilize many packed features with your entire application.

Those features include components, models, forms, validation helpers. If your application context matches to use them, you are free to take them and never write your own again. To publish those files, use the below command.

$ php artisan vendor:publish --tag=jetstream-views

Closing

In the Laracon Online talk, Taylor mentioned, Laravel team has almost offered the non-billing part of Laravel Spark as Laravel Jetstream, which is entirely free for everyone now.

Conclusion

Thanks for staying up to the end of the article. If you think it is worth reading, share this article in your circle and follow me on Twitter for more updates.

]]>
<![CDATA[How to Deploy Laravel Application to VPS Server]]> https://sujipthapa.co/blog/how-to-deploy-laravel-application-to-vps-server 18c05ef6-7be4-44c4-865c-971842edcfa2 2020-08-30T16:58:00+05:45 Mr. Sujip Thapa In the previous article, I wrote about how we can utilize an Envoy package, and gave a walk through to the Envoy use cases, and explained who it makes a lot easier on deploying your next Laravel application to the remote server.

Throughout this article, you are going to learn how you can deploy your laravel apps, with the precise use of Laravel Envoy, including how you can efficiently deploy your updates once you make changes in your version control platforms like Github, Bitbucket.

Read: Laravel Envoy to Deploy your Application to Hosting Server

Also, this article will cover a few more other ideas about the deployment process for Laravel application on different platforms.

Let's get started step by step about the deployment on how you can start deploying your Laravel projects now using the latest Laravel version.

Prerequisites

Before you start, you need to have a deployment-ready server with everything up and running to launch your next Laravel app, as we are not going to deal with any server set up stuff here.

However, you will need to make sure your server meets the requirements based on the Laravel version you are using.

I regularly follow the Digital Ocean technical articles to study about server set up things, as they have an outstanding community with great content.

Set up SSH

Now, the initial step is to generate an SSH key on your server and add it back to VCS platforms like Github, Bitbucket, or whatever you use.

I primarily use Linux Ubuntu flavor to host my apps, so this article takes reference based on the Linux Ubuntu but also would work for most of the Linux flavors.

Access your server via console using SSH connection and follow the command below.

$ ssh-keygen -t rsa -b 4096 -C "user@example.com"

Copy that SSH key based on where you save it.

On Linux, you can cat the contents like below.

$ cat<~/.ssh/id_rsa.pub

After adding it to the version control platform, you can test your SSH connection like below.

$ ssh -T git@bitbucket.org

If you are a beginner and need more assistance, I suggest you read this article by Bitbucket, which has in-depth information on it.

Set up Composer

For those who want to set up composer, follow the steps below.

$ cd ~
$ mkdir bin
$ cd bin
$ curl -sS https://getcomposer.org/installer | php

Set up Envoy

// Envoy.blade.php

@setup
    $branch = isset($branch) ? $branch : "master";
    $serverUser = 'deployer';
    $rootDirectory = '~/home/' . $serverUser;

    $server = $serverUser . '@server_ip';
@endsetup

@servers(['production' => $server])

@task('clone', ['on' => 'production'])
    echo '>> cd {{ $rootDirectory }}'
    cd {{ $rootDirectory }}

    echo '>> mkdir {{ $rootDirectory }}/project'
    mkdir {{ $rootDirectory }}/project

    echo '>> chmod 755 {{ $rootDirectory }}/project'
    chmod 755 {{ $rootDirectory }}/project

    echo '>> cd {{ $rootDirectory }}/project'
    cd {{ $rootDirectory }}/project

    echo '<< git clone git@github.com:username/project.git deploy'
    git clone git@github.com:username/project.git deploy
@endtask

@task('environment', ['on' => 'production'])
    echo '>> cd {{ $rootDirectory }}/project/deploy'
    cd {{ $rootDirectory }}/project/deploy

    echo '<< cp .env.example .env'
    cp .env.example .env

    echo '>> SSH to your server, paste your valid .env credentials & save them. Then run envoy run post-deploy'
@endtask

@task('composer-install', ['on' => 'production'])
    echo '>> cd {{ $rootDirectory }}/project/deploy'
    cd {{ $rootDirectory }}/project/deploy

    echo '<< /home/{{ $serverUser }}/bin/composer.phar install --prefer-dist --no-scripts --no-dev -q -o'

    /home/{{ $serverUser }}/bin/composer.phar install --prefer-dist --no-scripts --no-dev -q -o
@endtask

@task('composer-update', ['on' => 'production'])
    echo '>> cd {{ $rootDirectory }}/project/deploy'
    cd {{ $rootDirectory }}/project/deploy

    echo '<< /home/{{ $serverUser }}/bin/composer.phar dump -o && php artisan optimize'

    /home/{{ $serverUser }}/bin/composer.phar dump -o && php artisan optimize

@endtask


@task('migrate', ['on' => 'production'])
    echo '>> cd {{ $rootDirectory }}/project/deploy'
    cd {{ $rootDirectory }}/project/deploy

    php artisan migrate --force;
@endtask

@task('symlink', ['on' => 'production'])
    echo '<< ln -s /home/{{ $serverUser }}/project/deploy/public /var/www/html'

    ln -s /home/{{ $serverUser }}/project/deploy/public /var/www/html
@endtask

@task('deploy-changes', ['on' => 'production'])
    echo '>> cd {{ $rootDirectory }}/project/deploy'
    cd {{ $rootDirectory }}/project/deploy

    echo '>> git checkout {{ $branch }}'
    git checkout {{ $branch }}

    echo '<< git pull --rebase'
    git pull --rebase
@endtask

@story('deploy', ['on' => 'production'])
    setup
    environment
@endstory

@story('post-deploy', ['on' => 'production'])
    composer-install
    composer-update
    migrate
    symlink
@endstory

@story('update')
    deploy-changes
    composer-update
    migrate
@endstory

Deploy

You will only need to run this task when you launch your application for the initial phase on the remote server.

$ envoy run deploy

The initial task finishes with a message telling about preparing the .env file.

Set up .env

Prepare the .env configuration with the necessary credentials for your application to run on the server.

SSH to the server, copy & paste to the server where .env file lies, and save it.

Final Deploy

Once you save the server .env file with correct credentials, then run another command.

$ envoy run post-deploy

After the first successful deploy, you cannot run the same process to deploy your future updates on the repository. Now, your future job will be only pulling new changes or installing more new packages if needed.

$ envoy run update

Finishing

It is a reasonable question to come in your mind, this deployment script is likely to break the application, and it could take your application to go through downtime either on any of the deployment.

I agree with the above statement, as it is not a zero-downtime deployment solution, as the article was focused generally on the way of only deploying your code to the server.

As being a developer, having some server related knowledge is almost necessary for every day to work with fun on any application, no matter the size and infrastructure required to scale it.

People these days talk about zero-downtime deployment and have seen a lot of articles about it, and there are commercial platforms available to handle the concern.

If you want to deploy your code to your server without worrying about downtime, choose the service like Laravel Envoyer, which is a commercial product from the Laravel ecosystem.

Even more, you do not want to worry about managing servers and want to go hassle-free with auto-scaling your apps, use Forge, and Vapor a serverless platform.

Using those managed services, you need to pay for both your server cost as well as the service cost every month, using those platforms helps your team to focus more on the work rather than suffering from deployments and servers.

Also, as a free solution for zero-downtime deployment strategy, there is an open-source package built by Papertank.

Tip: People ask a lot of questions about how to deploy Laravel to shared hosting frequently. But, my answer to this is, Laravel works great on VPS service providers like Digital Ocean, Vultr Linode, AWS, or any Custom VPS, etc.

Conclusion

Thanks for being up to the end of the article. Please share this article to your circle, if you enjoyed reading it.

Follow me on Twitter for more updates or visit the blog more often to view new articles at a later date.

]]>
<![CDATA[Laravel Envoy to Deploy your Application to Hosting Server]]> https://sujipthapa.co/blog/laravel-envoy-to-deploy-your-application-to-hosting-server 89a47491-4b3f-4271-aed2-9c69206056cd 2020-08-29T16:30:10+05:45 Mr. Sujip Thapa Laravel Envoy is an open-source package to efficiently deploy your apps to the server or even run tasks in the local environment. It is one of the most helpful tools in the Laravel ecosystem that every developer utilize every day. This tool provides a precise, minimal syntax for defining any tasks you run on your remote servers.

By making the use of Blade style syntax, you can quickly set up tasks for deployment actions, run inbuilt Artisan commands, and many more. Envoy currently has support for Mac, Linux only.

Similar to any other composer packages you install with your Laravel application, Laravel Envoy also follows the same process.

If you don’t already have it installed, then follow the below simple command.

$ composer global require laravel/envoy

After a successful installation, now your turn is to start to set up your tasks needed to deploy your application. Test your set up like below, shows the screen like below with more other information.

$ envoy -v

Laravel Envoy 2.3.1

You can initialize the Envoy.blade.php file by using the command below.

$ envoy init user@192.168.1.1

Adding Tasks

Before starting to write any tasks, define a new file Envoy.blade.php in the root of your project directory.

@servers(['web' => 'user@192.168.1.1'])

@task('deploy')
    cd /path/to/site
    git pull origin master
@endtask

As you can see above, the @servers directive in a single line, referencing the array of servers to point around when you deploy the application.

The @task directive keeps the bash script to run on your server in the deployment process. For the initialization of variables necessary for the deployment script, you can also use the @setup directive to assign variables based on your application structure.

If your project logic requires to verify certain things, and that requires to include any PHP files in between the deployment process, you could always use the @include directive at the top of your Envoy.blade.php file.

@include('vendor/autoload.php')

@setup
    $branch = isset($branch) ? $branch : "main";
    $env = isset($env) ? $env : "production";
    $account = isset($account) ? $account : "sujipthapa";
@endsetup

@servers(['local' => '127.0.0.1', 'production' => '192.168.1.1'])

Define Variables

As per your requirement, Laravel Envoy also supports passing variables with values to your tasks within the command line.

$ envoy run deploy --branch=main --account=sujipthapa

Adding Stories

Adding @stories directive helps you to group up your small tasks into a flow of actions, which gives you a way to maintain the sequence to execute scripts on your server in your deployment process.

For instance, you may add a story label as a post-deploy, which groups your tasks like git pull, composer install, migration, etc.

Notify

You may instantly send a notification to different channels like Telegram, Discord, Slack right inside from the Envoy setup file.

It just requires to define your simple setup for that process like below.

@finished
    @slack('slack-webhoo-url', '#deploy')
@endfinished

Overview

As from the above in-depth run down, below can you can try to understand the Envoy set up with few useful directives and usual tasks defined for your to as quick overview.

@setup
    $branch = isset($branch) ? $branch : "main";
    $env = isset($env) ? $env : "production";
    $account = isset($account) ? $account : "sujipthapa";
@endsetup

@servers(['local' => '127.0.0.1', 'production' => '192.168.1.1'])

@task('cd_server_path', ['on' => 'production'])

    echo ">> cd /home/{{ $account }}/project"

    cd /home/{{ $account }}/project

@endtask


@task('git_commands')

    echo ">> git checkout {{ $branch }} ..."

    git checkout {{ $branch }}

    echo ">> git pull origin {{ $branch }} --force ..."

    git pull origin {{ $branch }} --force

@endtask

@task('run_composer')

    echo ">> composer depencencies..."

    composer install --no-interaction --quiet --no-dev --prefer-dist --optimize-autoloader

@endtask

@task('run_migrate')
    php artisan migrate --env={{ $env }} --force --no-interaction
@endtask

@story('deploy')
    cd_server_path
    git_commands
    run_composer
    run_migrate
@endstory

Finally, your turn is to run the deploy command.

$ envoy run deploy

I suggest you follow the official Laravel Envoy docs as more options and new features roll out every single day, as it is an open-source project, so many people contribute and use the ecosystem.

Conclusion

Thanks for reaching up to the end of the article. Please share this article to your circle, if you loved reading it.

Follow me on Twitter for more updates or visit the blog more often to view new articles at a later date.

]]>
<![CDATA[eSewa Payment Gateway Integration with Laravel]]> https://sujipthapa.co/blog/esewa-payment-gateway-integration-with-laravel ed28d640-1cb5-4508-94dc-a9d5f3826431 2019-03-16T15:17:00+05:45 Mr. Sujip Thapa In this article, I will show you how to integrate eSewa API v1.0 with the Laravel framework. eSewa is Nepal's first popular online payment gateway helping businesses to accept their payments online.

As like a few other payment gateways integration with PHP, I've recently worked on a new package for eSewa, which is a used in the majority of eCommerce platform in Nepal. I got a chance to work for the client to accept their payment online.

I have first explored on google to know if anyone has worked to create a better PHP package for the gateway. I found there is no any good package for the payment gateway. So decided to create a new reusable library with unit testing. I always try to share the real-time example and solution for the working developers through this blog.

Below, I'll cover complete steps needed to integrate eSewa API using the package built on top of Omnipay library.

Before diving into the code, let me first share my experience on how to grab API keys for development, production environment.

While I went through their API docs, there are no test credentials given in there. eSewa API credentials are available to the merchant after contacting the eSewa office via phone call or email support.

Now, let's jump into the code after you have grabbed the API credentials to make API calls by using the package.

Coding

The library is framework agnostic, can be used in any custom project, not only with Laravel. Here, we're going to integrate it on top of the Laravel framework. If you're a Laravel developer, hopefully, you will benefit from it to save your time.

Also, read:
Omnipay PayPal Integration
PayPal Instant Payment Notification

Include the package with your project with a composer command.

composer require league/omnipay sudiptpa/omnipay-esewa

Routes

Route::get('/checkout/payment/esewa', [
    'name' => 'eSewa Checkout Payment',
    'as' => 'checkout.payment.esewa',
    'uses' => 'EsewaController@checkout',
]);

Route::post('/checkout/payment/{order}/esewa/process', [
    'name' => 'eSewa Checkout Payment',
    'as' => 'checkout.payment.esewa.process',
    'uses' => 'EsewaController@payment',
]);

Route::get('/checkout/payment/{order}/esewa/completed', [
    'name' => 'eSewa Payment Completed',
    'as' => 'checkout.payment.esewa.completed',
    'uses' => 'EsewaController@completed',
]);

Route::get('/checkout/payment/{order}/failed', [
    'name' => 'eSewa Payment Failed',
    'as' => 'checkout.payment.esewa.failed',
    'uses' => 'EsewaController@failed',
]);

Model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Order
 * @package App
 */
class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

Esewa

<?php

namespace App;

use Exception;
use Omnipay\Omnipay;

/**
 * Class Esewa
 * @package App
 */
class Esewa
{
    /**
     * @return \SecureGateway
     */
    public function gateway()
    {
        $gateway = Omnipay::create('Esewa_Secure');

        $gateway->setMerchantCode(config('services.esewa.merchant'));
        $gateway->setTestMode(config('services.esewa.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @return $response
     */
    public function purchase(array $parameters)
    {
        try {
            $response = $this->gateway()
                ->purchase($parameters)
                ->send();
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }

        return $response;
    }

    /**
     * @param array $parameters
     * @return $response
     */
    public function verifyPayment(array $parameters)
    {
        $response = $this->gateway()
            ->verifyPayment($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getFailedUrl($order)
    {
        return route('checkout.payment.esewa.failed', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('checkout.payment.esewa.completed', $order->id);
    }
}

Controller

<?php

namespace App\Http\Controllers;

use App\Esewa;
use App\Order;
use Exception;
use Illuminate\Http\Request;

/**
 * Class EsewaController
 * @package App\Http\Controllers
 */
class EsewaController extends Controller
{
    /**
     * @param Request $request
     */
    public function checkout(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 20));

        return view('esewa.checkout', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function payment($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = with(new Esewa);

        try {
            $response = $gateway->purchase([
                'amount' => $gateway->formatAmount($order->amount),
                'totalAmount' => $gateway->formatAmount($order->amount),
                'productCode' => 'ABAC2098',
                'failedUrl' => $gateway->getFailedUrl($order),
                'returnUrl' => $gateway->getReturnUrl($order),
            ], $request);

        } catch (Exception $e) {
            $order->update(['payment_status' => Order::PAYMENT_PENDING]);

            return redirect()
                ->route('checkout.payment.esewa.failed', [$order->id])
                ->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
        }

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => "We're unable to process your payment at the moment, please try again !",
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = with(new Esewa);

        $response = $gateway->verifyPayment([
            'amount' => $gateway->formatAmount($order->amount),
            'referenceNumber' => $request->get('refId'),
            'productCode' => $request->get('oid'),
        ], $request);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $request->get('refId'),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('checkout.payment.esewa')->with([
                'message' => 'Thank you for your shopping, Your recent payment was successful.',
            ]);
        }

        return redirect()->route('checkout.payment.esewa')->with([
            'message' => 'Thank you for your shopping, However, the payment has been declined.',
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function failed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        return view('esewa.checkout', compact('order'));
    }
}

HTML

@extends('default')

@section('content')
    <div class="title m-b-md">
        eSewa Checkout
    </div>

    <div class="links">

        @if($message = session('message'))
            <p>
                {{ $message }}
            </p>
        @endif

        <p>
            <strong>QuietComfort® 25 Acoustic Noise Cancelling® headphones — Apple devices</strong>
        </p>

        <br>

        <form method="POST" action="{{ route('checkout.payment.esewa.process', $order->id) }}">

            @csrf

            <button class="btn btn-primary" type="submit">
                ${{ $order->amount }} Pay with eSewa
            </button>
        </form>
    </div>
@stop

Code

The sample code is available on github example repository.

Conclusion

Thanks for following up this article up to the end. If you have any feedback for me, feel free to leave your comment. If you think you have found a bug, please reach us via email or create an issue on github. Do not forget to share if you think worthreading and sharing this article.

Happy Coding!

]]>
<![CDATA[Laravel 5.7: Adding Support for Login with Username or Email]]> https://sujipthapa.co/blog/laravel-57-adding-support-for-login-with-username-or-email 577722fb-dc4c-46f5-a20b-f37742194e38 2018-10-17T00:00:00+05:45 Mr. Sujip Thapa I've already published a few articles on Laravel Login to support username or email. In this article, I'm going to cover the process of adding the username or email support with Laravel 5.7 default authentication system.

The process below helps you to understand the following things.

  • Adds support for email verification which is supported by default in the framework now.
  • Adds support for register with the username, which is not available on a default authentication system.
  • Adds support for login with either username or email.

The reason behind writing this article is peoples, preferring my previous articles about Laravel login and register with username or email support for earlier versions of Laravel.

Compatibility

If you are starting your fresh Laravel 5.7 project, and want to add the support for username or email for login and register, you could follow the steps from the beginning.

For any existing project just upgraded to Laravel 5.7, it is, of course, useful to follow the article to customize your authentication system.

Customization

Let's begin with the registration part to add support for username or email login with Laravel 5.7.

Register

For a fresh Laravel 5.7 setup, begin with generating the default auth scaffolding with an artisan console command, which ships with the framework out of the box.

Routes

// no email verification
Auth::routes();

// after adding, email verification
Auth::routes(['verify' => true]);


I would like to recommend you to follow the earlier article [Email Verification after Registration with Laravel 5.7] side by side with this article to implement email verification with Laravel 5.7 authentication system.

Database

For existing project add a new migration file using an artisan command to add a new field to the users' table, and for the new fresh project, you can easily edit the migration file generated with auth scaffolding and add a single line below.

$table->string('username')->unique();

To fill value in the database for username field, we've to add a key username to the $fillable property under app/User.php database model.

protected $fillable = ['name', 'email', 'password', 'username'];

View

The user registration form also requires a little bit of customization to add the username field.

<div class="form-group row">
    <label for="username" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>

    <div class="col-md-6">
        <input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required>

        @if ($errors->has('username'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('username') }}</strong>
            </span>
        @endif
    </div>
</div>

Controller

The default auth system ships with a controller class, which handles the necessary methods for the registration system. We've to edit a few methods under that class to add the username to the database.

Login

We've already added the necessary routes, email verification steps linked with another article. For the login system to support username or email we need to override a few methods on the login controller.

The login form requires a very little change on the input type field from email to text.

<input id="email" type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

The controller class for login also ships with the framework and uses an AuthenticatesUsers trait, which handles the specific methods necessary for the default auth system.

To apply the new changes we need, we have to override the methods under the controller class for the login system.

    // LoginController.php

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = $this->field($request);

        return [
            $field => $request->get($this->username()),
            'password' => $request->get('password'),
        ];
    }

    /**
     * Determine if the request field is email or username.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    public function field(Request $request)
    {
        $email = $this->username();

        return filter_var($request->get($email), FILTER_VALIDATE_EMAIL) ? $email : 'username';
    }

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $field = $this->field($request);

        $messages = ["{$this->username()}.exists" => 'The account you are trying to login is not registered or it has been disabled.'];

        $this->validate($request, [
            $this->username() => "required|exists:users,{$field}",
            'password' => 'required',
        ], $messages);
    }

Code

The full source code for the all above customization is available on the github repository.

Conclusion

Thanks for following up my regular articles, and reading up to the end. If you have any feedback for me, feel free to leave your comment. Do not forget to share if you think worth sharing, and reading my articles.

Happy Coding!

]]>
<![CDATA[Email Verification after Registration with Laravel 5.7]]> https://sujipthapa.co/blog/email-verification-after-registration-with-laravel-57 6176c3b6-78d4-45c9-b693-b68e2e362801 2018-09-01T13:16:17+05:45 Mr. Sujip Thapa Nowadays most of the modern applications force their users to verify the ownership by sending an activation email after they complete the account registration. Moving ahead, with the release of Laravel 5.7 the user email verification is shipping with the framework out of the box. People were creating the custom feature to implement this before the version 5.7.
This feature now makes easier to implement email verification on each application people build by using the Laravel framework.

In this article, we're implementing the complete email verification process in depth. We're going to achieve the verification feature by using the standard framework code without doing any modification.

Let's start the coding together.

Auth Scaffolding

Let's start with publishing the default auth scaffoldings by using the artisan command.

After publishing the necessary files, we don't have to worry about the routes, views, controllers required for authentication, as they ship with the framework, but you can always customize them if needed.

php artisan make:auth

Database

The new class is introduced in the framework to implement the email verification system.

So, now the App\User model should implement the Illuminate\Contracts\Auth\MustVerifyEmail contract.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    // ...
}

We need to add one more field email_verified_at to the user's table to store the verified timestamp when a user clicks the activation link sent to their email address.

    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });

Routing

A new controller Auth\VerificationController class is introduced to handle the necessary logic to send an email verification link to the user's email address.

Also, register the verification routes by passing the parameter like below.

// before
Auth::routes();

// after
Auth::routes(['verify' => true]);

Middleware

A new middleware Illuminate\Auth\Middleware\EnsureEmailIsVerified class is introduced to protect the routes from being accessed by the unauthorized users.

You can have a look at the Kernel.php to see the registered middleware.

    protected $routeMiddleware = [
        ...
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];

Also, you may want to protect other routes, like profile, the dashboard to non-activated users by attaching the middleware on routes.

// example
Route::get('/home', 'HomeController@index')
    ->name('home')
    ->middleware('verified');

Route::get('/member/profile', function () {
    // verified users only
})->middleware('verified');

Views

The necessary views for login, register, and email verification are created inside resources/views/auth directory when you publish the auth scaffoldings.

After Verification

After the successful verification, the application redirects a user to the /home page by default.

You are free to customize the redirect location by defining a redirectTo method or property on the VerificationController.php.

protected $redirectTo = '/dashboard';

Also,

If you want to control the page redirection when the user interacts with login, register pages, you want them to go back to the previous page.

Read: Redirect to Previous URL After Logging In

Conclusion

Thanks for reading the article up to the end, please let me know through comments if you have any issue on the integration.

Happy Coding!

]]>
<![CDATA[What's New Coming to Laravel 5.7 Release]]> https://sujipthapa.co/blog/whats-new-coming-to-laravel-57-release 86e89f5e-5943-4ae6-8aa1-018040ea28ff 2018-08-17T10:15:15+05:45 Mr. Sujip Thapa Laravel a most popular PHP framework, which is actively supported and contributed open source project, which is reaching to its next release 5.7 in August 2018.

The release will receive bug fixes until February 2019 and security fixes until August 2019.

This release continues to improvements for the previous version 5.6, and also includes some exciting new features.

In this blog post, I'm listing some cool features that are already announced by the Laravel team and taking some reference from the github repository.

I'll keep on updating this post up to the final release is announced from the official team, as new pull requests are still coming from the community contributors.

1. Laravel Nova

Laravel Nova%2C beautifully designed admin panel package for Laravel.

The most awaited laravel package that was announced by Taylor during Laracon US 2018, and which is a beautiful admin panel package for Laravel application. It is a code-driven admin panel for your new or existing laravel project. The previous version of Laravel still supports Nova, as it is a simple composer package. To know in depth about it follow the official documentation, also go through the introduction article on the medium by Taylor.

Laravel Nova is officially released now on Aug 22, 2018, the initial release v1.0.* (Orion) is now available to purchase from the official website.

2. Email Verification

The framework introducing optional email verification feature with this release. To utilize this feature, you have to add the email_verified_at timestamp column to the user's table migration that ships with the framework.

To advise newly joined users to verify their email, the User model should implement the MustVerifyEmail interface.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    // ...
}

After implementing the interface, the newly registered members will receive an email containing a signed verification link. Once the user clicks this link, the application will automatically update the database and redirect the user to the intended location.

This new feature also introduced a verified middleware. If you wish to protect your application's routes from only verified members, it can be attached to the application routes.

'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,


3. Guest User Gates / Policies

The previous version of Laravel used to return false if an unauthenticated user wants to access the application. However, with the new version (5.7) it will now allow declaring an "optional" type-hint or supplying a null default value to permit the guest user to pass through the authorization checks.

Gate::define('update-product', function (?User $user, Product $product) {
    // ...
});


4. Symfony Dump Server

The Symfony's dump-server is coming to Laravel 5.7. It is a command via package built by a Laravel community member Marcel Pociot.

This feature will be a lot useful to debug an error on your application without interrupting the application runtime.

Laravel 5.7 Dump Server

The command runs in the background and collects data transmitted from the application and shows output through the console mode.

php artisan dump-server

// Output to the HTML file.
php artisan dump-server --format=html > report.html

This package is open sourced by the author, contribute if you have any idea for it.

5. URL Generator & Callable Syntax

To generate the URLs to controller actions, Laravel now supports callable array syntax, instead of strings only.

Previously,

$url = action('BlogController@index');

$url = action('BlogController@view', ['id' => 1]);

Now,

use App\Http\Controllers\HomeController;

$url = action([BlogController::class, 'index']);

$url = action([BlogController::class, 'view'], ['id' => 1]);

6. Paginator Links

You can now control the number of pagination links on each side of the paginated URLs design. By default, there will be three links created on each side of the paginator links. The newly introduced method onEachSide() allows to control them with Laravel 5.7.

{{ $pages->onEachSide(5)->links() }}

7. Read / Write Streams method in Filesystem

Laravel 5.7 introduces new methods for the Filesystem integration.

    Storage::disk('s3')->writeStream(
        'destination-file.zip',
        Storage::disk('local')->readStream('source-file.zip')
    );

8. Notification Localization
You can now assign locale for the notifications to send other than the current language.

The Illuminate\Notifications\Notification class adds new locale method to assign the desired language.

$user->notify((new NewUser($user))->locale('np'));

You could also utilize the facade to set localization for multiple notification entries.

    Notification::locale('np')
        ->send($subscribers, new WeeklyNewsletter($newsletter));

9. Improved Error Messages

You can now better track your errors messages with your Laravel application. As of Laravel 5.7, you will get a cleaner concise message saying that the method doesn't exist on the specific model.

Improved Error Messages

10. Resources Directory Changes

A little bit changes to the resources directory coming with the new release.

This change was announced by Taylor with a tweet, as assets directly will go away and js, sass, lang, views coming out into the resources directory.

Laravel 5.7 resources directory changes.

When you upgrade your application to 5.7, you don’t need to reconstruct the resources/asset directory according to the newer directory structure. The older structure will still work.

11. Testing Artisan Commands

The first employee of Laravel (Mohamed Said) recently contributed a great feature in the framework to test artisan commands. He announced via his tweet about this feature, and which is also documented in official documentation already. Now, with this addition, framework now provides a simple API for testing console applications that ask for user input.

class InstallCommandTest extends TestCase
{
    public function testInstallTest()
    {
        $this->artisan('app:setup', [
            'name' => 'Setup New Project'
        ])
            ->expectsQuestion('Are you sure you want to start installation ?', 'Yes')
            ->expectsOutput('Initializing...')
            ->expectsQuestion('Please select your preferred version', 'v2.5')
            ->expectsOutput('Installing...')
            ->expectsQuestion('Do you want to run composer dump -o ?', 'Yes')
            ->expectsOutput('Generating Optimized Autoload Files...')
            ->assertExitCode(0);
    }
}

Conclusion

Thanks for reading this article up to the end, please follow this article in later dates to know more new features coming to the 5.7 release.

Happy Coding!

]]>
<![CDATA[Building a Password Less Authentication System with Laravel Signed Routes]]> https://sujipthapa.co/blog/building-a-password-less-authentication-system-with-laravel-signed-routes fdd631b4-7d56-4667-84f8-43b4ed8c5d6f 2018-08-12T14:22:18+05:45 Mr. Sujip Thapa I've seen many moderns apps are offering passwordless authentication in their platform. A lot of social media, email platforms are also offering the login with their API system by providing limited data necessary for user registration.

A lot of modern webs, mobile apps using social login to give a great user experience while using their platforms.

Today in this blog post, I'm explaining the process of customizing to use own passwordless authentication system with the Laravel framework.

Let's start together.

This article will utilize features from Laravel 5.6 version.

laravel new passwordless-auth

cd passwordless-auth

php artisan make:auth

After publishing the default Laravel auth scaffoldings, we now need to remove unnecessary files listed below.

ResetPasswordController.php
ForgotPasswordController.php

passwords/email.blade.php
passwords/reset.blade.php

The register, login pages come up with password fields, as we're building passwordless auth, so we have to tweak on those files.

login.blade.php
register.blade.php

Note: It is better to give some instructions on each pages describing how our passwordless authentication system works.

Now, adding auth routes needed for this integration.

We're not going to use the default routes which ship with the framework for the auth system.

We'll care the naming convention of routes, but we have to avoid unnecessary ones coming with the framework which are not necessary for this integration.

Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login/attempt', 'Auth\LoginController@attempt')->name('login.attempt');
Route::get('login/{token}/validate', 'Auth\LoginController@login')
    ->name('login.token.validate')
    ->middleware('signed');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');

Let's start with registering a user without a password to log in.

Before writing any codes for login, register process, let me give you some idea on what things need to update.

Signed Route Middleware
The middleware prevents the user to misuse the expired URLs by checking the signature validity.

    protected $routeMiddleware = [
        ...
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    ];​

The new feature signed route was introduced to the framework with version 5.6.

Handle InvalidSignatureException
We need to handle the exception for invalid signature, if a user tries to login with the expired signed URL show message to them to generate new URL.

    use Illuminate\Routing\Exceptions\InvalidSignatureException;

    public function render($request, Exception $exception)
    {
        if (is_a($exception, InvalidSignatureException::class)) {
            return response()->view('_signature-expired');
        }

        return parent::render($request, $exception);
    }
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">
                        <h2>{{ __('Error') }}</h2>
                    </div>
                    <div class="card-body">
                        <div class="alert alert-danger text-center text-muted">
                            The signature seems to be expired, please try generating a new one and try again.
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Remove Password Fields
We're not using the password field anymore, so remove from the migration file and User model.

After all, these make sure you run the migration to generate database tables.
Register

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    use RegistersUsers;

    /**
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
        ]);
    }

    /**
     * Handle a registration request for the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        return redirect()->route('login')
            ->with(['success' => 'Success! your account is registered.']);
    }
}

The main updates in this file are, remove password form validation, create methods.

Another major update is to prevent a user from logging in automatically into the application after registration inside the register method.

Tip: I've seen BuySell Ads, a popular advertising platform using this kind of system as only admin adds the user.

Login

I've added a new trait for the Login process to keep the overrides a little bit cleaner.

Previously with the framework's default auth, there is a trait called AuthenticatesUsers.

Now, with this integration, we will be importing new trait which itself imports the existing AuthenticatesUsers.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Auth\Traits;
use App\Http\Controllers\Controller;

class LoginController extends Controller
{
    use Traits\PasswordLessAuth;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

Also, the Login Throttling feature will still work.

<?php

namespace App\Http\Controllers\Auth\Traits;

use App\LoginAttempt;
use App\Notifications\NewLoginAttempt;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

trait PasswordLessAuth
{
    use AuthenticatesUsers;

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $messages = ['exists' => trans('auth.exists')];

        $this->validate($request, [
            $this->username() => 'required|email|exists:users',
        ], $messages);
    }

    /**
     * Handle a login attempt request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function attempt(Request $request)
    {
        $this->incrementLoginAttempts($request);

        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        $this->validateLogin($request);

        if ($this->createLoginAttempt($request)) {
            return $this->sendAttemptResponse($request);
        }

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function login($token, Request $request)
    {
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($token, $request)) {
            return $this->sendLoginResponse($request);
        }

        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param string $token
     * @param \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function attemptLogin($token, Request $request)
    {
        $user = LoginAttempt::userFromToken($token);

        if (is_object($user)) {
            return $this->guard()->login($user);
        }
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \App\LoginAttempt
     */
    protected function createLoginAttempt(Request $request)
    {
        $authorize = LoginAttempt::create([
            'email' => $request->input($this->username()),
            'token' => str_random(40) . time(),
        ]);

        $authorize->notify(new NewLoginAttempt($authorize));

        return $authorize;
    }

    /**
     * @param $request
     */
    public function sendAttemptResponse($request)
    {
        return \View::make('auth._link-sent');
    }
}

To customize the validation message for the user exists rule, open up resources/lang/en/auth.php and update like below.

<?php

return [

    ...
    'exists' => 'The provided email address does not match our records.'
];

Let me give some run down about the login feature and new methods added to the PasswordLessAuth.php trait.

  • The method validateLogin() now, only validates email field and if that exists in the database.
  • The new attempt() method is responsible for sending an email with a signed URL after validating the request.
  • The new createLoginAttempt(), is being called via attempt() method, which creates a new login attempt which will be validated later while user clicks on the link sent via email.
  • After creating a login attempt, we display a message to the user with sendAttemptResponse() method.

Let's create a model, migration and notification class for the login attempt process.

<?php

namespace App;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;

class LoginAttempt extends Model
{
    use Notifiable;

    /**
     * @var string
     */
    protected $table = 'login_attempts';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'email', 'token',
    ];

    /**
     * @return mixed
     */
    public function user()
    {
        return $this->hasOne(User::class, 'email', 'email');
    }

    /**
     * @param $token
     */
    public static function userFromToken($token)
    {
        $query = self::where('token', $token)
            ->where('created_at', '>', Carbon::parse('-15 minutes'))
            ->first();

        return $query->user ?? null;
    }
}

Add, those fields by creating a new migration file like below.

    Schema::create('login_attempts', function (Blueprint $table) {
        $table->increments('id');
        $table->string('email')->index();
        $table->string('token')->index();
        $table->timestamps();
    });

To send the login link in an email, we have a new notification class.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\URL;

class NewLoginAttempt extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($attempt)
    {
        $this->attempt = $attempt;
    }

    /**
     * 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 with(new MailMessage)
            ->from(env('ADMIN_MAIL_ADDRESS'))
            ->subject('Login Your Account')
            ->greeting("Hello {$this->attempt->user->name}!")
            ->line('Please click the button below to get access to the application, which will be valid only for 15 minutes.')
            ->action('Login to your account', URL::temporarySignedRoute('login.token.validate', now()->addMinutes(15), [$this->attempt->token]))
            ->line('Thank you for using our application!');
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

After a user makes a valid attempt to login into the application, we display a message to the user with a view file like below.

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">
                        <h2>{{ __('Success') }}</h2>
                    </div>
                    <div class="card-body">
                        <div class="alert text-center text-muted">
                            Please check your email for a login link, which will be valid for next 15 minutes only.
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <a class="btn btn-link" href="{{ route('login') }}">
                                    {{ __('Get Another Link') }}
                                </a>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Conclusion

The full source code now freely available on github, you are free to modify as per your application logic.

Also Read: Laravel 5.6 Login, Register, Activation with Username or Email Support

Thanks for reading this article up to the end, please don't forget to give your feedback in the comment section below.

Happy Coding!

]]>
<![CDATA[Testing Packages on Composer with Specific Commit Before Creating New Tag]]> https://sujipthapa.co/blog/testing-packages-on-composer-with-specific-commit-before-creating-new-tag f416d66e-3cad-41f3-a6ec-11f5b47ddca1 2018-07-21T08:18:18+05:45 Mr. Sujip Thapa Composer, which is a modern developer tool for dependency management for PHP. It is used to declare the dependencies your project depends on it and it manages them very well for you. It is incredibly flexible to manage the dependencies in various stage of your project.

Let's suppose you're releasing a new package, but before announcing a new major stable version, you want to test it thoroughly in the development branch.

Now, to use the specific git commit before publishing a new tag, composer offers a way to include the commit hash value directly via the command line or via the require block in the composer.json file.

composer require vendor/package:dev-master#0fcf728

or

    "require": {
        "vendor/package": "dev-master#0fcf728"
    }

The composer command above will check out your package version to that specific commit, so you can test the code properly before finally releasing a stable version.

Also, sometime you may want to install a specific released version of the package.

composer require vendor/package:version

// Example 
composer require sudiptpa/paypal-ipn:1.0.x-dev

Also Read: Speed up your Composer Install or Update Process

Thanks for reading the article up to the end, if you have any feedback feel free to leave your comment below.

If it is worth reading, let other people know about it by sharing this post.

Happy Coding!

]]>
<![CDATA[Laravel 5.6 Login, Register, Activation with Username or Email Support]]> https://sujipthapa.co/blog/laravel-56-register-login-activation-with-username-or-email-support e3e9db5e-d8de-4922-907b-f6b166af7c2c 2018-06-10T00:00:00+05:45 Mr. Sujip Thapa This is another interesting blog post about Laravel 5.6 login, register with username or email support. I've already published two articles about customizing the laravel authentication process. In this article, I will be helping you to build following things below, with Laravel v5.6.

  • Adding username support on default register, login process.
  • Activating user after the registration process is completed via email.
  • Disabling default login after register, only allowing to log in for the activated user.
  • Using Laravel notification to send email to the user about registration.

To follow this article you should have existing or fresh laravel setup with version 5.6, as you will be seeing some codes samples only supporting this particular version and they may not be compatible with other older versions.

The regular laravel installation comes without auth scaffolding with the recent releases, to generate the default auth scaffolding it ships with an artisan command out of the box.

If you haven't already generated them, use the below artisan command to populate them.

php artisan make:auth

Let's start with the additional routes for authentication under routes/web.php

Auth::routes();

Route::get('activate/{token}', 'Auth\RegisterController@activate')
    ->name('activate');

To add username support on register process, let's add few new fields in the migration file.

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->string('token');
            $table->integer('active')->default(0);
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

We've added three new fields (username, token, active), to store unique username, activation token, active field to main active flag for users.

Now, updating model app/User.php with new fields.

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    const ACTIVE = 1;
    const INACTIVE = 0;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'username', 'email', 'password', 'token', 'active',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Register

Adding username field for a registration form inside resources/views/auth/register.blade.php

<div class="form-group row">
    <label for="username" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>

    <div class="col-md-6">
        <input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required>

        @if ($errors->has('username'))
            <span class="invalid-feedback">
                <strong>{{ $errors->first('username') }}</strong>
            </span>
        @endif
    </div>
</div>

To view, the full source code for the registration form read the article up to end.

To make the username support coming through the registration form, we need to modify few methods inside the controller.

    // App\Http\Controllers\Auth\RegisterController.php
    
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'username' => 'required|string|max:20|unique:users',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        $user = User::create([
            'name' => $data['name'],
            'username' => $data['username'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'token' => str_random(40) . time(),
        ]);

        $user->notify(new UserActivate($user));

        return $user;
    }

    /**
     * Handle a registration request for the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        return redirect()->route('login')
            ->with(['success' => 'Congratulations! your account is registered, you will shortly receive an email to activate your account.']);
    }

    /**
     * @param $token
     */
    public function activate($token = null)
    {
        $user = User::where('token', $token)->first();

        if (empty($user)) {
            return redirect()->to('/')
                ->with(['error' => 'Your activation code is either expired or invalid.']);
        }

        $user->update(['token' => null, 'active' => User::ACTIVE]);

        return redirect()->route('login')
            ->with(['success' => 'Congratulations! your account is now activated.']);
    }

Let's point out the updates made in the above controller.

  • We've added a validation rule for username field inside the validator() method.
  • We've updated create() method to call a notification class to send an activation email to the user after registration is completed.
  • Overriding register() method originally called from the RegistersUsers trait to protect from being auto-login to non-activated users.
  • Also created new method activate() to allow activation to the new users.

We've added a new notification class, UserActivate.php to send an activation link to the users while they register their new account.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class UserActivate extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @param $user
     * @return void
     */
    public function __construct($user)
    {
        $this->user = $user;
    }

    /**
     * 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)
            ->from(env('ADMIN_MAIL_ADDRESS'))
            ->subject('Activate Account!')
            ->greeting(sprintf('Hi, %s', $this->user->name))
            ->line('We just noticed that you created a new account. You will need to activate your account to sign in into this account.')
            ->action('Activate', route('activate', [$this->user->token]))
            ->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 customization for registration process is finally done with above changes, now we will be looking at login process below.

Login

We've updated the file login.blade.php to add support for email or username. To make that addition we've changed the input type email to be text to support both username or email but the input type name remains same.

<div class="form-group row">
    <label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail / Username') }}</label>

    <div class="col-md-6">
        <input id="email" type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

        @if ($errors->has('email'))
            <span class="invalid-feedback">
                <strong>{{ $errors->first('email') }}</strong>
            </span>
        @endif
    </div>
</div>

Now, we're adding another method to the controller.

    // App\Http\Controllers\Auth\LoginController.php

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = $this->field($request);

        return [
            $field => $request->get($this->username()),
            'password' => $request->get('password'),
            'active' => User::ACTIVE,
        ];
    }

    /**
     * Determine if the request field is email or username.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    public function field(Request $request)
    {
        $email = $this->username();

        return filter_var($request->get($email), FILTER_VALIDATE_EMAIL) ? $email : 'username';
    }

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $field = $this->field($request);

        $messages = ["{$this->username()}.exists" => 'The account you are trying to login is not activated or it has been disabled.'];

        $this->validate($request, [
            $this->username() => "required|exists:users,{$field},active," . User::ACTIVE,
            'password' => 'required',
        ], $messages);
    }

Let's point out what has changed inside the controller above.

  • The method credentials(), originally called from AuthenticatesUsers trait which collects credential to attempt the authentication is now overridden to the controller.
  • The field username or email is determined inside the credentials() method.
  • Added active field in the authentication process.

After updating the above files the login process also now supports username or email.

The full source code is freely available on github, you are free to modify and use it for your application.

Thanks for reading this article up to the end, please don't forget to give your feedback in the comment section below.

Happy Coding!

]]>
<![CDATA[SecurePay Integration Guide for Omnipay v3 PHP Library with Laravel]]> https://sujipthapa.co/blog/securepay-integration-guide-for-omnipay-v3-php-library-with-laravel 51d273ef-a3da-4b71-818d-5f952e36beb9 2018-05-30T20:02:17+05:45 Mr. Sujip Thapa I've already covered few Omnipay integration guides with my previous blog posts. In this post, I'm going to explain in detail about SecurePay, a leading payment solution for businesses of different sizes over Australia. I've recently contributed to the v3 release of the omnipay-securepay open source library for PHP on github.

By using this package (omnipay/securepay) we can implement different gateways listed below.

  • SecurePay_DirectPost (DirectPost v2 Integration)
  • SecurePay_SecureXML (Secure XML API Integration)

We're going to integrate SecurePay DirectPost v2 in this article below.

  • The customer submits the payment information directly to the Direct Post payment service from the website.
  • The payment gateway validates and redirects back to the website to fulfill the order.

Before starting the coding let's install the package via composer.

We're going to integrate this package with Laravel v5.6 for this article, use the command below to install the package.

composer require league/omnipay omnipay/securepay

Let's add some new routes for this integration under app/routes/web.php

Route::get('/checkout/payment', [
    'name' => 'Payment',
    'as' => 'checkout.payment',
    'uses' => 'PaymentController@checkout',
]);

Route::post('/checkout/payment/{order}/process', [
    'name' => 'Payment',
    'as' => 'checkout.payment.process',
    'uses' => 'PaymentController@payment',
]);

Route::get('/checkout/payment/{order}/completed', [
    'name' => 'Payment Completed',
    'as' => 'checkout.payment.completed',
    'uses' => 'PaymentController@completed',
]);

Route::get('/checkout/payment/{order}/failed', [
    'name' => 'Payment Failed',
    'as' => 'checkout.payment.failed',
    'uses' => 'PaymentController@failed',
]);

Adding new model toapp/Order.php handle database operations to store orders coming through the website.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;
    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

This model has really basic stuff to show the process while building this integration guide, you are free to customize as per the requirement of your application.

A new database table is needed to store the orders. Let's create a table orderswith a console command.

php artisan make:migration create_table_orders --create=orders
<?php

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

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('transaction_id')->nullable();
            $table->float('amount')->unsigned()->nullable();
            $table->integer('payment_status')->unsigned()->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

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

Let's add a new classapp/SecurePay.php which interacts with library classes and framework classes to interact with API services.

<?php

namespace App;

use Exception;
use Illuminate\Http\Request;
use Omnipay\Common\CreditCard;
use Omnipay\Common\Exception\InvalidCreditCardException;
use Omnipay\Omnipay;

/**
 * Class SecurePay
 * @package App
 */
class SecurePay
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('SecurePay_DirectPost');

        $gateway->setMerchantId(config('services.securepay.merchant_id'));
        $gateway->setTransactionPassword(config('services.securepay.password'));
        $gateway->setTestMode(config('services.securepay.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @param Request $request
     */
    public function card(array $parameters, Request $request)
    {
        return [
            'card' => new CreditCard([
                'firstName' => $request->get('first_name'),
                'lastName' => $request->get('last_name'),
                'number' => $request->get('card_number'),
                'expiryMonth' => $request->get('expiry_month'),
                'expiryYear' => $request->get('expiry_year'),
                'cvv' => $request->get('cvc'),
            ]),
        ];
    }

    /**
     * @param array $parameters
     * @param Request $request
     * @return mixed
     */
    public function purchase(array $parameters, Request $request)
    {
        $parameters = array_merge($parameters, $this->card($parameters, $request));

        try {
            $response = $this->gateway()
                ->purchase($parameters)
                ->send();

        } catch (InvalidCreditCardException $e) {
            throw new Exception($e->getMessage());
        }

        return $response;
    }

    /**
     * @param array $parameters
     * @param Request $request
     * @return mixed
     */
    public function complete(array $parameters, Request $request)
    {
        $parameters = array_merge($parameters, $this->card($parameters, $request));

        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('checkout.payment.failed', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('checkout.payment.completed', $order->id);
    }
}

To store the API credentials required to connect with API service, you can use config/services.phpwhich ships with the framework out of the box.

    'securepay' => [
        'merchant_id' => env('SECUREPAY_MERCHANTID'),
        'password' => env('SECUREPAY_PASSWORD'),
        'sandbox' => env('SECUREPAY_SANDBOX', true)
    ],

Now, we need a controller class to handle and process all incoming requests into the application. Let's add a class PaymentController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\SecurePay;
use Exception;
use Illuminate\Http\Request;

/**
 * Class PaymentController
 * @package App\Http\Controllers
 */
class PaymentController extends Controller
{
    /**
     * @param Request $request
     */
    public function checkout(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 140));

        // you application logic goes here
        // the above order is just for example.

        return view('checkout.payment', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function payment($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = new SecurePay;

        try {
            $response = $gateway->purchase([
                'amount' => $gateway->formatAmount($order->amount),
                'transactionId' => $order->id,
                'currency' => 'USD',
                'cancelUrl' => $gateway->getCancelUrl($order),
                'returnUrl' => $gateway->getReturnUrl($order),
            ], $request);
        } catch (Exception $e) {
            $order->update(['payment_status' => Order::PAYMENT_PENDING]);

            return redirect()
                ->route('checkout.payment.failed', [$order->id])
                ->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
        }

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => "We're unable to process your payment at the moment, please try again !",
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = new SecurePay;

        $response = $gateway->complete([
            'amount' => $gateway->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $gateway->getCancelUrl($order),
            'returnUrl' => $gateway->getReturnUrl($order),
        ], $request);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $response->getTransactionReference(),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('checkout.payment')->with([
                'message' => 'Payment Successful, Thank you for your order !',
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function failed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        return view('checkout.payment', compact('order'));
    }
}

To submit the payment information by the customer we need a checkout page, for this example, I have implemented a nice view with the code snippet below.

<div class="card-details">
  <h3 class="title">Credit Card Details</h3>
  <span>@csrf</span>
  <div class="row">
    <div class="form-group col-sm-8">
      <label for="card-holder">Card Holder</label>
      <div class="input-group expiration-date">
          <input type="text" class="form-control" placeholder="Fist Name" name="first_name" aria-label="Fist Name" aria-describedby="basic-addon1">
          <span class="date-separator">/</span>
          <input type="text" class="form-control" placeholder="Last Name" name="last_name" aria-label="Last Name" aria-describedby="basic-addon1">
      </div>
    </div>
    <div class="form-group col-sm-4">
      <label for="">Expiration Date</label>
      <div class="input-group expiration-date">
        <input type="text" class="form-control" placeholder="MM" aria-label="MM" name="expiry_month" aria-describedby="basic-addon1">
        <span class="date-separator">/</span>
        <input type="text" class="form-control" placeholder="YY" aria-label="YY" name="expiry_year" aria-describedby="basic-addon1">
      </div>
    </div>
    <div class="form-group col-sm-8">
      <label for="card-number">Card Number</label>
      <input id="card-number" type="text" class="form-control" placeholder="Card Number" name="card_number" aria-label="Card Holder" aria-describedby="basic-addon1">
    </div>
    <div class="form-group col-sm-4">
      <label for="cvc">CVC</label>
      <input id="cvc" type="text" class="form-control" placeholder="CVC" aria-label="CVC" name="cvc" aria-describedby="basic-addon1">
    </div>
    <div class="form-group col-sm-12">
      <button type="submit" class="btn btn-primary btn-block">
          <i class="fa fa-credit-card" aria-hidden="true"></i>
          Pay with Credit Card
      </button>
    </div>
  </div>
</div>

To view the full HTML code click here.

The preview looks like below.

SecurePay Integration, Checkout

To view the full source code written while crafting this blog post, visit the github repository and grab the code.

Thanks for reading up to the end, I hope you enjoyed reading this article. If you have any feedback on this article feel free to leave your comment below.

Happy Coding!

]]>
<![CDATA[PayPal Integration – Omnipay PayPal PHP Library v3.0 with Laravel]]> https://sujipthapa.co/blog/paypal-integration-omnipay-paypal-php-library-v30-with-laravel e23bd3c6-3ed4-4b6b-993e-4561be3b45c3 2018-05-20T05:12:00+05:45 Mr. Sujip Thapa This is my third blog post about PayPal Integration with Omnipay payment processing library. In this article, I will mainly cover about the recent release v3.0, which is finally available for developers around the world. I'm sure many developers around the world were eagerly waiting for this release.

In my previous article I explained about how to Integrate Omnipay v2.* with Laravel framework.

In the new release v3.0, the team focused mainly on separating the HTTP Client, to be independent with Guzzle. This release now supports symfony 3,4 components to run with all Laravel v5.* versions. This is a most awaited release, developers were not able to use, as it was not fully compatible with latest symfony components.

To know about the breaking changes and new additions to the package, view the full release note from their official doc and github repository.

The original omnipay/omnipay package has been changed as league/omnipay

By using this package (omnipay/paypal) we can implement different gateways listed below.

  • PayPal_Express (PayPal Express Checkout)
  • PayPal_ExpressInContext (PayPal Express In-Context Checkout)
  • PayPal_Pro (PayPal Website Payments Pro)
  • PayPal_Rest (Paypal Rest API)

We're going to integrate PayPal Express Checkout in this article below.

Before starting the coding let's install the package via composer.

composer require league/omnipay omnipay/paypal

Let's add new routes for the PayPal integration under app/routes/web.php

Route::get('/paypal/{order?}', [
    'name' => 'PayPal Express Checkout',
    'as' => 'order.paypal',
    'uses' => 'PayPalController@form',
]);

Route::post('/checkout/payment/{order}/paypal', [
    'name' => 'PayPal Express Checkout',
    'as' => 'checkout.payment.paypal',
    'uses' => 'PayPalController@checkout',
]);

Route::get('/paypal/checkout/{order}/completed', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.completed',
    'uses' => 'PayPalController@completed',
]);

Route::get('/paypal/checkout/{order}/cancelled', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.cancelled',
    'uses' => 'PayPalController@cancelled',
]);

Route::post('/webhook/paypal/{order?}/{env?}', [
    'name' => 'PayPal Express IPN',
    'as' => 'webhook.paypal.ipn',
    'uses' => 'PayPalController@webhook',
]);

I mainly write named routes all over my applications, having name routes are easy to create URLs from anywhere within the project with a simple helper route(), I have mentioned before about this mechanism with my previous articles as well.

Adding required models for this implementation.

app/Order.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

The order model only has few basic fields implemented just for the simple example, you are free to customize with your required fields in your application.

Adding new database table orders, you need to create a migration file with a console command.

php artisan make:migration create_table_orders --create=orders
<?php

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

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('transaction_id')->nullable();
            $table->float('amount')->unsigned()->nullable();
            $table->integer('payment_status')->unsigned()->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

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

Adding new helper class for generating gateway object and routes to interact with PayPal API.

app/PayPal.php

<?php

namespace App;

use Omnipay\Omnipay;

/**
 * Class PayPal
 * @package App
 */
class PayPal
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('PayPal_Express');

        $gateway->setUsername(config('services.paypal.username'));
        $gateway->setPassword(config('services.paypal.password'));
        $gateway->setSignature(config('services.paypal.signature'));
        $gateway->setTestMode(config('services.paypal.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @return mixed
     */
    public function purchase(array $parameters)
    {
        $response = $this->gateway()
            ->purchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param array $parameters
     */
    public function complete(array $parameters)
    {
        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('paypal.checkout.cancelled', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('paypal.checkout.completed', $order->id);
    }

    /**
     * @param $order
     */
    public function getNotifyUrl($order)
    {
        $env = config('services.paypal.sandbox') ? "sandbox" : "live";

        return route('webhook.paypal.ipn', [$order->id, $env]);
    }
}

Laravel ships with a config file config/services.php to store credentials to be used with third-party API services. For this implementation add following credentials under the same file like below.

    'paypal' => [
        'username' => env('PAYPAL_USERNAME'),
        'password' => env('PAYPAL_PASSWORD'),
        'signature' => env('PAYPAL_SIGNATURE'),
        'sandbox' => env('PAYPAL_SANDBOX'),
    ],

To handle and maintain the request coming into the application we need to pass information to the controller. We are adding new PayPalController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\PayPal;
use Illuminate\Http\Request;

/**
 * Class PayPalController
 * @package App\Http\Controllers
 */
class PayPalController extends Controller
{
    /**
     * @param Request $request
     */
    public function form(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 140));

        // the above order is just for example.

        return view('form', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function checkout($order_id, Request $request)
    {
        $order = Order::findOrFail(decrypt($order_id));

        $paypal = new PayPal;

        $response = $paypal->purchase([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
        ]);

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $paypal = new PayPal;

        $response = $paypal->complete([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
            'notifyUrl' => $paypal->getNotifyUrl($order),
        ]);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $response->getTransactionReference(),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('order.paypal', encrypt($order_id))->with([
                'message' => 'You recent payment is sucessful with reference code ' . $response->getTransactionReference(),
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     */
    public function cancelled($order_id)
    {
        $order = Order::findOrFail($order_id);

        return redirect()->route('order.paypal', encrypt($order_id))->with([
            'message' => 'You have cancelled your recent PayPal payment !',
        ]);
    }

        /**
     * @param $order_id
     * @param $env
     * @param Request $request
     */
    public function webhook($order_id, $env, Request $request)
    {
        // to do with new release of sudiptpa/paypal-ipn v3.0 (under development)
    }
}

Finally, adding a simple view to submitting a form to pay the order amount with PayPal, the form redirects the user to PayPal hosted page to enter the user account details for the security reason.

app/resources/views/form.blade.php

@extends('app')

@section('content')
    <div class="container">
        <div class="gateway--info">
            <div class="gateway--desc">
                @if(session()->has('message'))
                    <p class="message">
                        {{ session('message') }}
                    </p>
                @endif
                <div class="row">
                    <div class="col">
                        <img src="{{ asset('images/paypal.png') }}" class="img-responsive gateway__img">
                    </div>
                    <div class="col">
                        <img src="{{ asset('images/laravel.png') }}" class="img-responsive gateway__img">
                    </div>
                </div>
                <p><strong>Order Overview !</strong></p>
                <hr>
                <p>Item : Yearly Subscription cost !</p>
                <p>Amount : ${{ $order->amount }}</p>
                <hr>
            </div>
            <div class="gateway--paypal">
                <form method="POST" action="{{ route('checkout.payment.paypal', ['order' => encrypt(mt_rand(1, 140))]) }}">
                    {{ csrf_field() }}
                    <button class="btn btn-pay">
                        <i class="fa fa-paypal" aria-hidden="true"></i> Pay with PayPal
                    </button>
                </form>
            </div>
        </div>
    </div>
@stop

The preview of the above view looks like below, I just made for this example.

PayPal Integration with PHP

For the above real-time example, I tried to cover only the mechanism to handle the payment request, failed payment, canceled a payment. You are free to customize it and manage as per your application architecture. The above example was created with Laravel v5.6 version but it works for all v5.* versions, that's the benefit of the new release for omnipay/paypal latest version v3.0.

To view the full source codes, view few commits (65e4b5a, 75a9585, c469e59, ab0b4db b24fa97) on github example repository.

Thanks for reading this article up on the end, please leave your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[Australia Post API — Location & Postcode Predictive Search]]> https://sujipthapa.co/blog/australia-post-api-location-postcode-predictive-search 7162cd34-5022-4348-8910-990334982898 2018-04-11T21:45:00+05:45 Mr. Sujip Thapa Last Updated: 22nd Sep, 2020

In this article, I'm writing about auto-suggesting postcode, the suburb of Australia with Australia Post API while filling up the address form.

I was recently working with an Australian client, it was an e-commerce platform to sell their products online.

I've also integrated shipping services like Temando, Transdirect, Auspost to calculate the postage cost in real-time. To protect their customer from not making mistake while filling up address form, they were interested in integrating real-time postcode, suburb auto-suggest. The idea was just for making the real-time postage cost calculation more consistent with the valid location.

1. Grab API Key

Before starting to build the feature and to make an API call, firstly we need to grab an API key from the Auspost developers center.Auspost API Key

2. Workflow

  • A customer starts typing at suburb, or postcode field in an HTML input box.
  • A twitter typeahead library detects the text change and fires an AJAX request, the server-side script again makes an API call behind the scene to get data from Auspost API.
  • The API call receives JSON data from Auspost API server, and it will be rendered as an auto-suggest popup.

3. Coding

I'm going to integrate this feature on top of Laravel framework v5.6, you are free to utilize it with any type of PHP project.

Adding new routes to display form, and for AJAX API call to the backend.

routes/web.php

Route::get('/postcode/search', [
    'name' => 'Australia Post Postcode Search',
    'as' => 'app.postcode.search',
    'uses' => 'AustraliaPostController@index',
]);

Route::get('/postcode/api/search', [
    'name' => 'Australia Post Postcode Search',
    'as' => 'app.postcode.api.search',
    'uses' => 'AustraliaPostController@search',
]);

app/Http/Controllers/AustraliaPostController.php

<?php

namespace App\Http\Controllers;

use App\Auspost\Request as Auspost;
use Illuminate\Http\Request;

/**
 * Class AustraliaPostController
 * @package App\Http\Controllers
 */
class AustraliaPostController extends Controller
{
    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Australia Post API search form.
     *
     * @return mixed
     */
    public function index()
    {
        return view('auspost.search');
    }

    /**
     * @return array
     */
    public function search()
    {
        $parameters = [
            'q' => $this->request->get('query'),
        ];

        $response = with(new Auspost(config('auspost.auth_key')))
            ->send($parameters);

        $localities = collect($response['localities']['locality'] ?? []);

        if ($localities->count()) {
            $collection = is_array($localities->first()) ? $localities : [$localities];

            return response()->json($collection);
        }

        return [];
    }
}

config/auspost.php

<?php

// replace xx with your real Auth Key sent by Auspost developers centre.

return [
    'auth_key' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
];

app/Auspost/Request.php

<?php

namespace App\Auspost;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;

/**
 * Class Request
 * @package App\Auspost
 */
class Request
{
    const END_POINT = 'https://digitalapi.auspost.com.au/postcode/search.json';

    /**
     * The API token.
     *
     * @var string
     */
    protected $token;

    /**
     * The guzzle http client.
     *
     * @var \GuzzleHttp\ClientInterface
     */
    protected $client;

    /**
     * Create a new request instance.
     *
     * @param string $token
     */
    public function __construct($token)
    {
        $this->token = $token;
        $this->client = new Client();
    }

    public function getEndpoint()
    {
        return self::END_POINT;
    }

    /**
     * Retrive data via API call from Auspost server.
     *
     * @param array $parameters
     * @param $method
     *
     * @return App\Auspost\Response
     */
    public function send(array $parameters = [], $method = 'get')
    {
        $url = $this->getEndpoint() . '?' . http_build_query($parameters);

        $parameters = [
            'headers' => [
                'Content-Type' => 'application/json',
                'Auth-Key' => $this->token,
            ],
        ];

        try {
            $response = $this->client->request($method, $url, $parameters);

        } catch (ClientException $exception) {
            return $exception;
        }

        return with(new Response($response))->toArray();
    }
}

app/Auspost/Response.php

<?php

namespace App\Auspost;

use GuzzleHttp\Psr7\Response as GuzzleResponse;

/**
 * Class Response.
 * @package App\Auspost
 */
class Response extends GuzzleResponse
{
    /**
     * The guzzle http client response.
     *
     * @var \GuzzleHttp\Message\Response
     */
    protected $response;

    /**
     * Create a new response instance.
     *
     * @param GuzzleResponse $response
     */
    public function __construct(GuzzleResponse $response)
    {
        $this->response = $response;
    }

    /**
     * @return mixed
     */
    public function toJson()
    {
        return (string) $this->response->getBody();
    }

    /**
     * @return mixed
     */
    public function toArray()
    {
        return json_decode($this->toJson(), true);
    }
}

resources/views/search.blade.php

<div class="card-body">
    <form method="POST" action="/register">
        @csrf

        <div class="form-group row">
            <label for="search" class="col-md-4 col-form-label text-md-right">Postcode</label>
            <div class="col-md-8">
                <input id="search" type="text" data-suggest-postcode="{{ route('app.postcode.api.search') }}" class="form-control" name="email">
            </div>
        </div>
    </form>
</div>

I have only shown a small block of code above for the form, to view the full source code for the above tutorial feel free to visit the repository on github.

Also, view the last updated code here with another pull request in GitHub.

4. Preview

Auspost API Search PreviewNote: To run the above example, it requires twitter typeahead javascript library v0.11.1, Laravel 5.6, guzzle 6.*, but you could still use it with lower version of Laravel and guzzle library.

Conclusion

Thanks for reading the full article up to the end, feel free to leave your feedback if you are happy reading it.

Happy Coding!

 

]]>
<![CDATA[PSR-2 Coding Standard – Automatically Format in Sublime Text]]> https://sujipthapa.co/blog/psr-2-coding-standard-automatically-format-in-sublime-text a51b37a5-4b89-44a7-8ce0-c2f8c3bd1b21 2018-04-07T13:57:00+05:45 Mr. Sujip Thapa It is always really nice when a code editor allows fixing your code to PSR2 coding standard by just hitting a single command.

At my work, we heavily use Laravel framework, which follows the PSR-2 coding standard. So, every day we create new packages, add new classes we first format our code to PSR-2 coding standard before submitting a pull request. We always strive to follow the coding standard and modern best practices to keep our code cleaner and elegant.

 

I personally use sublime as my primary code editor all the time, the main reason is its lightness and popularity among the developers around the world, another beneficial part is the huge availability of open-source packages for it.

Below, I'm going to cover how we format our PHP code to PSR-2 coding standard in sublime text. The team at symfony and the contributors have done really great job building a package that can be used with sublime text to format coding standard.

Step 1.

Installing php-cs-fixer via composer to run globally on your machine.

  composer global require friendsofphp/php-cs-fixer

After the installation process is completed open your terminal and simply run php-cs-fixer command just to confirm it's installed properly.

It is always a tedious job when you go and fix the formatting on each file you have in your project. Below, I have an example for you if you want to try to format your files manually.

 //Eg:
 php-cs-fixer fix /path/project --rules=@PSR2

Please refer to the official doc to discover all the available rules and commands you could use for your formatting.

 

Step 2.

To automate the code formatting process, we're going to set up a build process in a sublime text, so whenever you need to format your code instead of running the long command, you could just press the button on a keyword to fix it.

Open your sublime text editor.
Go to Tools Menu > Build System > New Build System

{
    "shell_cmd": "php-cs-fixer fix $file --rules=@PSR2"
}

Now, try to save, a prompt window will open, create a new folder called Builds at that position, save it inside that folder with name PSR-2.sublime-build

The build process setup is now ready to use, before using it we need to make a selection under Tools Menu > Build System > PSR 2 which is a new command we just added with the process above.

Usage

  • If you're Windows, Linux user just hit Ctrl + B
  • If you're Mac user ⌘ + B

Learn more about PSR-2 Coding Standard.

Conclusion

Thanks for reading the article up to the end, if you are happy reading it and was helpful to you, feel free to share and leave your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[Instagram Feed – Building Gallery with Real-time Data via Instagram API]]> https://sujipthapa.co/blog/instagram-feed-building-gallery-with-real-time-data-via-instagram-api 86275746-c168-4967-990e-acfab63825e2 2018-03-24T18:14:12+05:45 Mr. Sujip Thapa I recently wrote an article about building an RSS feed section to publish Facebook Instant Articles automatically. In this article, I will show how to build a gallery on your website with the picture you have uploaded to your Instagram account. In this tutorial I'll show you an easy way to pull down the data via API, using Vinkla’s Instagram package. I would like to thank him for creating a nice package.

 

API Credentials

Follow up the developer guide via their documentation, generate Client ID, Client Secret and you will need to go to another site to generate an access token.

Step 1

Let’s add new config array under existing file config/services.php

    'instagram' => [
        'access-token' => 'xxxxx.xxx.xxxx',
        //replace xxx with your actual access token
    ],

Step 2

Adding a new route to handle the URL /instagram/feed under routes/web.php

    Route::get('/instagram/feed', [
        'name' => 'Instagram Feed',
        'as' => 'app.instagram.feed',
        'uses' => 'InstagramController@feed',
    ]);

Step 3

Installing package via composer, use command from below or follow the installation guide from the package repository on github.

composer require vinkla/instagram php-http/message php-http/guzzle6-adapter
 

Step 4

Creating a new controller to handle the request and execute the API call.

    <?php

    namespace App\Http\Controllers;

    use Vinkla\Instagram\Instagram;

    /**
     * Class InstagramController
     * @package App\Http\Controllers
     */
    class InstagramController extends Controller
    {
        /**
         * @return mixed
         */
        public function feed()
        {
            $instagram = new Instagram(config('services.instagram.access-token'));

            $images = collect($instagram->get())->map(function ($each) {
                return $each->images->standard_resolution->url;
            });

            return view('gallery', compact('images'));
        }
    }

Now, look at the code above in the controller, I have added the logic to pull down the Instagram feeds via API and utilized the Laravel collection map() method to filter the images with standard resolution only. If you would like to show likes, a caption associated with those images you could customize that array and call them while rendering on the view file.

Step 5

Finally, its time to loop the images on gallery.blade.php file under resources/views.

    <div class="tz-gallery">
        <div class="row">
          @foreach($images as $key => $image)
            <div class="col-sm-12 col-md-4">
                <a class="lightbox" href="{{ $image }}">
                    <img src="{{ $image }}" alt="Instagram Feed" class="img-responsive">
                </a>
            </div>
            @endforeach
        </div>
    </div>
 

I have added a small block of code to just show the process of looping and displaying the images. The full source code I wrote for this tutorial is available on github, feel free to review the full pull request and utilize on your project.

Conclusion

Thanks for reading this article up to the end, if it was helpful to you, feel free share the knowledge with others, also don't forget to leave your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[How to Create A Trending Articles List using Google Analytics with Laravel]]> https://sujipthapa.co/blog/how-to-create-a-trending-articles-list-using-google-analytics-with-laravel 80c51a2a-845d-4407-8fa3-bf0b8846ba32 2018-03-17T13:45:00+05:45 Mr. Sujip Thapa In this article, I will guide you about building top trending articles using Google Analytics API with Laravel framework.

Recently I built this feature for my own blog, if you would like to view it open up the home page of my blog and see around the right sidebar.

I have used the popular laravel package laravel-analytics built by Spatie.

Prerequisites

Before starting to do the actual coding, first, you need to generate the API credentials required for API connectivity.

 

I am not going to cover the process of generating the API credentials, but I recommend you to follow the full steps explained by Spatie in their package documentation page.

Feature

The trending section I have built has following features, it was based on my own requirement.

If you would like to show the trending posts directly to your view without storing any records to the database or without any implicit logic, that is also provided by the package out of the box.

I will be doing it differently in this article, see below.

  • A console command to fetch the daily top views via Google Analytics API. Also, the command will purge the old records from database pulled before one week, that all depends on you, and you will be able to customize that all.
  • A helper class to fetch and manipulate the weekly page views pulled via API and group them by each blog.
  • The database process will be cached for every single day, so that will reduce queries to load the site faster.

Coding

Installing package via console mode with the composer.

composer require spatie/laravel-analytics

The package will be auto-registered in laravel v5.5 and above. If you are using lower version register the package by following the installation guide.

 

app/Blog.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    /**
     * @var string
     */
    protected $table = 'blog';

    /**
     * @var array
     */
    protected $dates = ['published_at', 'deleted_at'];

    /**
     * @var string
     */
    protected $fillable = ['user_id', 'name', 'slug', 'excerpt', 'content', 'status', 'published_at'];

    /**
     * @param $slug
     */
    public static function findByTitle($slug = null)
    {
        return self::where('slug', $slug)->first();
    }
}

I am assuming that you are running blogs already, my blog model looks like above.

Adding new migration for trendings table.

php artisan make:migration create_trendings_table --create=trendings
<?php

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

class CreateTrendingsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('trendings', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('blog_id')->nullable();
            $table->integer('views')->nullable();
            $table->string('url', 1024)->nullable();
            $table->string('page_title', 1024)->nullable();
            $table->tinyInteger('status')->default(1)->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

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

app/Trending.php

<?php

namespace App;

use App\Blog;
use Illuminate\Database\Eloquent\Model;
use Spatie\Analytics\Period;

/**
 * Class Trending
 * @package App
 */
class Trending extends Model
{
    /**
     * @var string
     */
    protected $table = 'trendings';

    /**
     * @var boolean
     */
    public $timestamps = true;

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var string
     */
    protected $fillable = ['blog_id', 'views', 'url', 'page_title'];

    /**
     * @return mixed
     */
    public function blog()
    {
        return $this->belongsTo(Blog::class);
    }

    /**
     * @param $query
     * @return mixed
     */
    public function scopeMonthly($query)
    {
        $period = Period::months(1);

        return $query->whereBetween('created_at', [
            $period->startDate,
            $period->endDate->endOfDay(),
        ]);
    }

    /**
     * @param $query
     * @return mixed
     */
    public function scopeWeekly($query)
    {
        $period = Period::days(7);

        return $query->whereBetween('created_at', [
            $period->startDate,
            $period->endDate->endOfDay(),
        ]);
    }
}

Adding new console command for API connectivity.

 

php artisan make:command TrendingCommand

<?php

namespace App\Console\Commands;

use App\Blog;
use App\Trending;
use Illuminate\Console\Command;
use Spatie\Analytics\AnalyticsFacade as Analytics;
use Spatie\Analytics\Period;

/**
 * Class TrendingCommand
 * @package App\Console\Commands
 */
class TrendingCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'analytics:trending';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Sync page view from Google Analytics API';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $pages = Analytics::fetchMostVisitedPages(Period::days(1), 300);

        if ($pages->count()) {
            $this->purge();

            $pages->map(function ($each) {
                $each = (object) $each;

                if (starts_with($each->url, '/blog/')) {
                    $slug = str_replace("/blog/", '', $each->url);
                    $blog = Blog::findByTitle($slug);

                    if (!empty($blog)) {
                        Trending::create([
                            'blog_id' => $blog->id,
                            'views' => $each->pageViews,
                            'status' => $blog->status,
                            'page_title' => $blog->name,
                            'url' => $each->url,
                        ]);

                        $this->info("{$blog->name} - {$each->pageViews} \n");
                    }
                }
            });
        }
    }

    /**
     * @return mixed
     */
    public function purge()
    {
        $period = Period::days(8);

        $this->info("Purging records before : {$period->startDate} \n");

        return Trending::where('created_at', '<', $period->startDate)
            ->forceDelete();
    }
}

Registering console command to run with the application.

app/Console/Kernel.php

    protected $commands = [
        'App\Console\Commands\TrendingCommand',
    ];

app/Helpers/Trending.php

<?php
namespace App\Helpers;

use App\Trending as Popular;
use Illuminate\Support\Facades\Cache;

/**
 * Class Trending
 * @package App\Helpers
 */
class Trending
{
    /**
     * @return mixed
     */
    public static function weekly($take = 15)
    {
        $collection = collect();

        $trendings = Cache::remember('popular', 60 * 24, function () {
            return Popular::with('blog')->weekly()->get();
        });

        $trendings->groupBy('blog_id')->map(function ($each) use ($collection) {
            $object = collect($each);

            $item = $object->first();
            $item->views = $object->sum('views');

            $collection->push($item);
        });

        return $collection->take($take);
    }
}

The job of the above helper class is to implicitly manipulate the seven days pages views and sort them out by the highest page views.

Now, creating new view partial to include it where ever you want to show the trending list.

resources/views/_trending.blade.php

@inject('trending', 'App\Helpers\Trending')

@php
    $trendings = $trending::weekly();
@endphp

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header">Trending</div>
                    <div class="card-body">
                        @if($trendings->count())
                            <ul>
                                @foreach($trendings as $key => $each)
                                   <li>
                                        <div class="article">
                                            <a href="{{ asset($each->url) }}" title="{{ $each->views }}">
                                                <span class="text-muted">{{ $each->blog->published_at->format('d M, Y') }}</span><br>
                                                {{ $each->blog->name }}
                                            </a>
                                        </div>
                                    </li>
                                @endforeach
                            </ul>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Alternative

If you would like to consume the real-time data via API instead of going through the above feature I built for my use case, you could just follow the package documentation and it has enough resources to understand.

 
// pull down real time data via Google Analytics API

Analytics::fetchMostVisitedPages(Period::days(7));

Now after completing the coding work, the most important thing is to set up a cron job that runs mid-night at 12:00 am to pull down the complete page views for that day.

My cron job looks like below.

0 0 * * * /usr/bin/php /home/deployer/project/artisan analytics:trending

Conclusion

Thanks for reading the article up to the end, if you are happy reading it and was helpful to you, feel free to share and leave your feedback below in the comment section. The article was written based on Laravel v5.6, feel free to utilize it with higher or lower versions as per your requirement. The full source code is available on github.

Happy Coding!

]]>
<![CDATA[Publishing Facebook Instant Articles via RSS Feed with Laravel]]> https://sujipthapa.co/blog/publishing-facebook-instant-articles-via-rss-feed-with-laravel b6fb2f6a-59dd-45ec-a530-145d4e1b0706 2018-03-08T15:19:00+05:45 Mr. Sujip Thapa If you want to publish your web articles to Facebook Instant Articles platform and looking for the best way to export your quality articles from your blog to Instant Articles platform without worrying much about configurations, then you're at the right place now.

In this post, I will show you how to automatically export articles whenever you publish a new article on your website. I recently built this feature for my own website, and it was built on top of Laravel framework.

If your website runs other than WordPress and you want to integrate the RSS feed, this post will help you to understand how to do that for your platform. If you would like to know about exporting the Instant Articles from WordPress website, you can read this doc from facebook developers platform.

 

Two different ways to export Instant Articles.

RSS Publishing, API Publishing

The easiest way is setting up RSS feed, which will be automatically ingested periodically by Facebook. So there will be no other configurations required to pull down your fresh articles from your CMS.

Let's begin to build RSS feed, to allow Facebook to read in XML version.

Adding new routes under routes/web.php

Route::get('blog/rss', [
    'as' => 'app.blog.rss',
    'uses' => 'BlogController@rss',
]);

Route::get('blog/{slug}', [
    'as' => 'app.blog.view',
    'uses' => 'BlogController@view',
]);

Adding new Blog model app/Blog.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    const STATUS_ENABLED = 1;
    const STATUS_DISABLED = 0;

    /**
     * @var string
     */
    protected $table = 'blog';

    /**
     * @var array
     */
    protected $dates = ['published_at', 'deleted_at'];

    /**
     * @var string
     */
    protected $fillable = ['user_id', 'name', 'slug', 'guid', 'excerpt', 'content', 'status', 'published_at'];

    /**
     * @param Builder $query
     * @return mixed
     */
    public function scopePublished($query)
    {
        return $query->where('published_at', '<=', now());
    }

    /**
     * @param Builder $query
     * @return mixed
     */
    public function scopeEnabled($query)
    {
        return $query->where('status', self::STATUS_ENABLED);
    }
}

Adding new controller BlogController.php

<?php

namespace App\Http\Controllers;

use App\Blog;
use Illuminate\Support\Str;

/**
 * Class BlogController
 * @package App\Http\Controllers
 */
class BlogController extends Controller
{
    public function rss()
    {
        $blogs = Blog::enabled()
            ->published()
            ->latest('published_at')
            ->get();

        $blogs->map(function ($each) {
            if (empty($each->guid)) {
                $each->guid = Str::uuid();
            }
        });

        return view('rss', [
            'blogs' => $blogs,
            'title' => 'Short title about your blog',
        ]);
    }

    /**
     * @param $slug
     */
    public function view($slug)
    {
        return Blog::where('slug', $slug)->first();
    }
}

Str::uuid() was introduced with Laravel v5.6, with lower versions you could use my simple package for creating guid.

 
composer require sudiptpa/guid

To discover the features, please through the github link.

Adding new view resources/rss.blade.php

<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>{{ $title }}</title>
    <link>{{ url('/') }}</link>
    <description>
        A short description about your blog.
    </description>
    <language>en-us</language>
    <lastBuildDate>{{ date('c') }}</lastBuildDate>
    @forelse($blogs as $key => $blog)
      <item>
        <title><![CDATA[{{ $blog->name }}]]></title>
        <link>{{ route('app.blog.view', ['slug' => $blog->slug]) }}</link>
        <guid>{{ $blog->guid }}</guid>
        <pubDate>{{ date('c', strtotime($blog->published_at)) }}</pubDate>
        <author>Author Name</author>
        <description><![CDATA[{{ $blog->excerpt }}]]></description>
        <content:encoded>
          <![CDATA[
            @include('partials._rss')
          ]]>
        </content:encoded>
      </item>
    @empty
      <item>No feeds found</item>
    @endforelse
  </channel>
</rss>

Adding another partial file included with the above view. resources/partials/_rss.blade.php

<!doctype html>
<html lang="en" prefix="op: http://media.facebook.com/op#">
    <head>
        <meta charset="utf-8">
        <meta property="op:markup_version" content="v1.0">
        <meta property="fb:article_style" content="default"/>
        <link rel="canonical" href="{{ route('app.blog.view', ['slug' => $blog->slug]) }}">
        <title>{{ $blog->name }}</title>
    </head>
    <body>
    <article>
        <header>
            <h1>{{  $blog->name  }}</h1>
            <h2> {{ $blog->excerpt }}</h2>
            <h3 class="op-kicker">
                PHP <!-- Replace with your category name-->
            </h3>
            <address>
                Sujip Thapa <!-- Replace with your author name-->
            </address>
            <time class="op-published" dateTime="$blog->published_at->format('c') }}">{{ $blog->published_at->format('M d Y, h:i a') }}</time>
            <time class="op-modified" dateTime="{{ $blog->updated_at->format('c') }}">{{ $blog->updated_at->format('M d Y, h:i a') }}</time>
        </header>

        {{ $blog->content }}

        <footer>
        <aside>
            A short footer note for your each Instant Articles.
        </aside>
        <small>© Copyright {{ date('Y') }}</small>
        </footer>
    </article>
    </body>
</html>

Note: After exporting articles to Instant Articles platform, you will need to share the post on your facebook page, after sharing on that page article will start appearing as Instant Article.

Finally, to implement the above feature, follow the below steps.

  1. Facebook Page
  2. Publishing Tools
  3. Instant Articles
  4. Configuration
  5. Production RSS Feed

in the input box paste the link ending with blog/rss: (example: https://sujipthapa.co/blog/rss)

 

After saving the RSS feed URL, Facebook will start to retrieve fresh articles periodically.

Conclusion

Thanks for reading the article up to the end, feel free to give your feedback below in the comment section if you like.

Happy Coding!

]]>
<![CDATA[Define Your Google Preferred Domain with Laravel Middleware]]> https://sujipthapa.co/blog/define-your-google-preferred-domain-with-laravel-middleware 4ce2c044-7ed9-4aa1-a3ec-0057612eb5bc 2018-03-04T09:15:00+05:45 Mr. Sujip Thapa This is my second blog post about setting up Google preferred domain from code context for your Laravel application. In my previous post, I have explained a really simple way of doing it with almost no configuration available to set up the preferred version of your choice. In this post, I am going to cover the advanced way of doing it with some configuration available with certain options.

Why is defining preferred domain necessary?

Defining a Google preferred domain is a way of instructing Google to use a final version of your domain to be used forever. Your web pages link should be permanent and never be changing frequently so that Google can start indexing, which is a process of adding pages of your website into Google search. The indexed pages will appear in Google searches around the world with matching keywords.

 

The important part is if your web pages URL are not same when a google crawler runs on your site or the pages start to redirect to 404, Google will start removing them from indexing and they will not appear in search results.

The example of what I wanted to describe above is, your domain should always be like, either www or non-www (i.e. http://www.sujipthapa.co or http://sujipthapa.co).

The primary concern of setting up a google preferred domain is to improve the SEO of your website, that instructs Google to always recognize the final version of your URL to appear in search results.

I would like to recommend you to read this Google support article to understand in detail about the benefits of setting google preferred domain for your website.

Note: Below with the code, it will only focus on forcing your URL to be the preferred version you have selected on Google Search Console. The code will depend on the configuration and will instruct to redirect on the preferred version.

Setup

The first step of the setup is verifying your preferred domain on google end. Read the Google support article carefully and define your preferred version of your domain.

 

Let's start by adding new config file under config/domain.php

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Preferred Domain / Canonical Domain
    |--------------------------------------------------------------------------
    | Example: https://www.example.com (www), https://example.com (non-www)
    |
    | Available Mode: "//www.", "//"
     */

    'preferred' => '//',

    /*
    |--------------------------------------------------------------------------
    | HTTP Protocol
    |--------------------------------------------------------------------------
    |
    | Available Mode: "http://", "https://"
     */

    'protocol' => 'https://',
];

The above configuration file has two settings available, one is preferred domain, and another is for SSL mode. I highly recommend you to install SSL certificate for your website. If an SSL certificate is not installed, beginning in July 2018 your site visitors will see a “Not Secure” warning, recently Google Security Blog announced with the release of Chrome 68, it will mark all web pages with HTTP as "not secure" version.

Adding new middleware class PreferredDomain.php under app/Http/Middleware directory.

<?php

namespace App\Http\Middleware;

use App\Domain;
use Closure;

/**
 * Class PreferredDomain
 * @package App\Http\Middleware
 */
class PreferredDomain
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $domain = new Domain($request);

        if ($domain->diff()) {
            return redirect()->to($domain->translated(), 301);
        }

        return $next($request);
    }
}

This middleware should monitor every HTTP request on your website. So, register it like below under app/Http/Kernal.php

    /**
     * @var array
     */
    protected $middleware = [
        ...
        \App\Http\Middleware\PreferredDomain::class,
    ];

Again, adding new class Domain.php under app directory to apply a way of translating your non-preferred domain to preferred version automatically, it will be translated to the domain version you have defined in the configuration file. It will monitor each request to verify that each request URL is consistent with your preferred version on Google Search Console.

<?php

namespace App;

use Illuminate\Http\Request;

/**
 * Class Domain
 * @package App
 */
class Domain
{
    const FORCE_HTTP = 'http://';
    const FORCE_HTTPS = 'https://';

    const FORCE_WWW = '//www.';
    const FORCE_NOWWW = '//';

    const REGEX_FILTER_WWW = '/(\/\/www\.|\/\/)/';
    const REGEX_FILTER_HTTPS = '/^(http:\/\/|https:\/\/)/';

    /**
     * @var \Illuminate\Http\Request
     */
    protected $request;

    /**
     * @var string
     */
    protected $translated;

    /**
     * @param \Illuminate\Http\Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * @return bool
     */
    public function isEqual(): bool
    {
        return $this->request->fullUrl() !== $this->translated();
    }

    /**
     * Determines if the original url differs with translated.
     *
     * @return bool
     */
    public function diff(): bool
    {
        $this->translated = $this->translate();

        return $this->isEqual();
    }

    /**
     * @return string
     */
    public function translated(): string
    {
        if (!$this->translated) {
            $this->translated = $this->translate();
        }

        return $this->translated;
    }

    /**
     * @return string
     */
    public function translate(): string
    {
        $url = $this->request->fullUrl();

        $protocol = $this->getProtocol();

        $filtered = preg_replace(self::REGEX_FILTER_HTTPS, $protocol, $url);

        $preferred = $this->getPreferred();

        return preg_replace(self::REGEX_FILTER_WWW, $preferred, $filtered);
    }

    /**
     * Determines if the request supports the https,
     * otherwise, fallback to default (http) protocol.
     *
     * @return string
     */
    public function getProtocol(): string
    {
        if (!$this->request->secure()) {
            return self::FORCE_HTTP;
        }

        return config('domain.protocol') ?? self::FORCE_HTTP;
    }

    /**
     * Determines the preferred domain from config.
     *
     * @return string
     */
    public function getPreferred(): string
    {
        return config('domain.preferred') ?? self::FORCE_NOWWW;
    }
}

Let's say my preferred version is - https://sujipthapa.co but user tries to go to https://www.sujipthapa.co now the middleware will monitor and redirect the non-preferred to preferred version. The job of the middleware is to translate the non-preferred to preferred version and issue a 301 redirect to correct version.

 

This is an implicit process that executes before the HTTP request enters into the application.

I have used the above middleware Laravel v5.5, but it is still compatible with the v5.6 version released in Feb 2018.

Conclusion

Thanks for reading the post up to the end, feel free to share, comment your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[Easily Switch PHP versions on Linux with Script]]> https://sujipthapa.co/blog/easily-switch-php-versions-on-linux-with-script 76409e7d-0499-4495-82a9-4e00a7719a4a 2018-02-11T01:18:00+05:45 Mr. Sujip Thapa If you are looking for best way to switch multiple PHP versions on your machine, you're at the right place to get the best solution for your requirement.

Let's say on your working computer you have installed multiple PHP versions (for eg: PHP 7.0 and PHP 7.2). As a default version, the PHP 7.0 is set to Nginx or Apache and CLI mode.

Of course, as a modern web developer who works on the different framework, packages need to have multiple PHP versions installed on their working machine. During their work on different projects, the project may not support all latest versions, so the developer may need to switch to a required version of PHP to run that project.

Below I will show you how to use the command line as well as a bash alias function to easily switch your versions.

You may want to go from PHP 7.2 to 7.0, use the commands below.

If you don't know how to check the php version on Linux Ubuntu type below command on terminal.

    php -v
//php 7.2 to php 7.0 switcher

sudo a2dismod php7.2 ; sudo a2enmod php7.0 ; sudo systemctl restart apache2

sudo ln -sfn /usr/bin/php7.0 /etc/alternatives/php

//if you are using nginx
sudo systemctl restart nginx

Similarly, while PHP version 7.0 is active, you can fall back to 7.2 with the following commands.

//php 7.0 to php 7.2 switcher

sudo a2dismod php7.0 ; sudo a2enmod php7.2 ; sudo systemctl restart apache2

sudo ln -sfn /usr/bin/php7.2 /etc/alternatives/php

//if you are using nginx
sudo systemctl restart nginx

Repeating the above process frequently is really irritating, time-wasting, right?

Finally, I decided to create a bash alias as a function to reduce my time to repeat the php switching and easy to remember.

Setup

Open up your terminal and type.

sudo nano ~/.bashrc

Scroll around the last line, where we want to paste the below function.

phpswap () {
    local IS_PHP7=`php --version|grep "PHP 7.2"`

    if [[ -z $IS_PHP7 ]]; then
        echo "Switching to PHP 7.2"
        sudo a2dismod php7.0;
        sudo a2enmod php7.2;
        sudo systemctl restart apache2;
        sudo ln -sfn /usr/bin/php7.2 /etc/alternatives/php
    else
        echo "Switching to PHP 7.0"
        sudo a2dismod php7.2;
        sudo a2enmod php7.0;
        sudo systemctl restart apache2;
        sudo ln -sfn /usr/bin/php7.0 /etc/alternatives/php
    fi
}

Usage

After completing the above process simply hit phpswap on your terminal.

I use multiple PHP versions, 7.2, 7.0 with Ubuntu 16.04.3 LTS. I simply run phpswapover the terminal, it detects the current default PHP version and switches to exactly reverse version which I need to go with.

Easy right?

We're done!

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

Last Updated: 16th Feb, 2018

]]>
<![CDATA[PayPal Instant Payment Notification (IPN) Handling with Laravel]]> https://sujipthapa.co/blog/paypal-instant-payment-notification-ipn-handling-with-laravel 1ae515cb-abb2-4734-b838-a96f6e6bb295 2018-02-10T09:43:00+05:45 Mr. Sujip Thapa This is a follow-up article from my previous post A guide to Integrate Omnipay PayPal with Laravel, at the end of that article I have mentioned that I will be writing about handling PayPal Instant Payment Notification (IPN) with Laravel. So finally it is published, so you can go through it.

Below, I am going to show in detail about the way of handling the webhook notification that comes from PayPal into the Laravel application.

If you are a beginner level developer I highly recommend you to read this official doc to understand the flow of PayPal Instant Payment Notification.

Why handling IPN is necessary?

Let's say a customer at your site is trying to pay for certain service or goods, your application takes the user to PayPal to complete the payment, the user makes payment, it is completed but due to some technical error the payment completed page from PayPal couldn't return to your application to handle completed orders. In that case, your application may fail to send emails, update the database for recently paid order status and its payment status.

To handle this situation PayPal gives a webhook service that sends POST request with a payload about the payment status.

Let's get started :)

Before diving into the code first let's understand how to set up a notification URL that PayPal actually uses to send a POST request to your web application. There are actually two different ways to setup IPN notification URL.

  • You can set up a static end that never changes and that is always ready to handle the PayPal webhook that may arrive at any time into your application. Follow the below steps from this official doc.
null
  • Another way is that we can send a dynamic notify URL with a payment request implicitly from the code, that way we don't need to worry about the setting up notify URL under PayPal > My Account. Below I will show how to send a notify URL with the payment request.

In the previous article, you can see how to send the notify URL with the payment request, I have made the notify URL to be dynamic with parameters like order_id, environment, so the URL changes for each order.

    $paypal = new PayPal;

    $response = $paypal->complete([
        'amount' => $paypal->formatAmount($order->amount),
        'transactionId' => $order->id,
        'currency' => 'USD',
        'cancelUrl' => $paypal->getCancelUrl($order),
        'returnUrl' => $paypal->getReturnUrl($order),
        'notifyUrl' => $paypal->getNotifyUrl($order),
    ]);

    //route for generating notify URL

    /**
     * @param $order
     */
    public function getNotifyUrl($order)
    {
        $env = config('paypal.credentials.sandbox') ? "sandbox" : "live";

        return route('webhook.paypal.ipn', [$order->id, $env]);
    }

To understand this article, I recommend you to read the previous post about integrating PayPal with Laravel.

Let's start diving into the code.

Installation

composer require sudiptpa/paypal-ipn

If you are using Laravel v4 stick with 1.0.x-dev in your composer.json file.

{
    "require": {
        "sudiptpa/paypal-ipn": "1.0.x-dev",
    }
}

Setup

Defining a route to handle POST request coming from PayPal server.

Route::post('/webhook/paypal/{order?}/{env?}', [
    'name' => 'PayPal Express IPN',
    'as' => 'webhook.paypal.ipn',
    'uses' => 'PayPalController@webhook',
]);

After creating a route to accept incoming POST request via PayPal, you need to keep in mind that Laravel by default filters each HTTP requests entering the application with VerifyCsrfToken middleware. In order to allow the above route to access the application the URL should be excluded from the csrf check.

I have previously covered about it with another article "Disabling CSRF on Specific Route via Middleware", so you can go throgh it to understand how to do that.

Let's create a migration for storing IPN records in the database.

<?php

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

class CreateTablePaypalIpnRecords extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('paypal_ipn_records', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('order_id')->nullable();
            $table->string('verified');
            $table->string('transaction_id');
            $table->string('payment_status');
            $table->string('request_method')->nullable();
            $table->string('request_url')->nullable();
            $table->longText('request_headers')->nullable();
            $table->longText('payload')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

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

A new model PayPalIPN.php for storing the IPN logs into database.

<?php

namespace App;

use App\Order;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Class PayPalIPN
 * @package App
 */
class PayPalIPN extends Model
{
    use SoftDeletes;

    const COMPLETED = "Completed";
    const IPN_FAILURE = "FALIURE";
    const IPN_INVALID = "INVALID";
    const IPN_VERIFIED = "VERIFIED";

    /**
     * @var boolean
     */
    public $timestamps = true;

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['order_id', 'verified', 'transaction_id', 'payment_status', 'request_method', 'request_url', 'request_headers', 'payload'];

    /**
     * @var string
     */
    protected $table = 'paypal_ipn_records';

    /**
     * @return boolena
     */
    public function isCompleted()
    {
        return in_array($this->payment_status, [self::COMPLETED]);
    }

    /**
     * @return boolena
     */
    public function isVerified()
    {
        return in_array($this->verified, [self::IPN_VERIFIED]);
    }

    /**
     * @return mixed
     */
    public function orders()
    {
        return $this->belongsTo(Order::class);
    }
}

Adding few new methods in Order.php model for easy condition check and model scope to perform database operation.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Class Order
 * @package App
 */
class Order extends Model
{
    use SoftDeletes;

    const COMPLETED = 1;
    const PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];

    /**
     * @param Builder $query
     * @param string $transaction_id
     * @return mixed
     */
    public function scopeFindByTransactionId($query, $transaction_id)
    {
        return $query->where('transaction_id', $transaction_id);
    }

    /**
     * Payment completed.
     *
     * @return boolean
     */
    public function paid()
    {
        return in_array($this->payment_status, [self::COMPLETED]);
    }

    /**
     * Payment is still pending.
     *
     * @return boolean
     */
    public function unpaid()
    {
        return in_array($this->payment_status, [self::PENDING]);
    }
}

Now creating a webhook() method on PayPalController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\PayPal;
use App\Repositories\IPNRepository;
use Illuminate\Http\Request;
use PayPal\IPN\Listener\Http\ArrayListener;

/**
 * Class PayPalController
 * @package App\Http\Controllers
 */
class PayPalController extends Controller
{
    /**
     * @param IPNRepository $repository
     */
    public function __construct(IPNRepository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * @param $order_id
     * @param $env
     * @param Request $request
     */
    public function webhook($order_id, $env, Request $request)
    {
        $listener = new ArrayListener;

        if ($env == 'sandbox') {
            $listener->useSandbox();
        }

        $listener->setData($request->all());

        $listener = $listener->run();

        $listener->onInvalid(function (IPNInvalid $event) use ($order_id) {
            $this->repository->handle($event, PayPalIPN::IPN_INVALID, $order_id);
        });

        $listener->onVerified(function (IPNVerified $event) use ($order_id) {
            $this->repository->handle($event, PayPalIPN::IPN_VERIFIED, $order_id);
        });

        $listener->onVerificationFailure(function (IPNVerificationFailure $event) use ($order_id) {
            $this->repository->handle($event, PayPalIPN::IPN_FAILURE, $order_id);
        });

        $listener->listen();
    }
}

Again, creating another class IPNRepository.php that act as a bridge between model and controller.

<?php
namespace App\Repositories;

use App\Order;
use App\PayPalIPN;
use Illuminate\Http\Request;

/**
 * Class IPNRepository
 * @package App\Repositories
 */
class IPNRepository
{
    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * @param $event
     * @param $verified
     * @param $order_id
     */
    public function handle($event, $verified, $order_id)
    {
        $object = $event->getMessage();

        if (is_numeric($order_id)) {
            $order = Order::find($order_id);
        }

        if (empty($order)) {
            $order = Order::findByTransactionId(
                $object->get('txn_id')
            )->first();
        }

        $paypal = PayPalIPN::create([
            'verified' => $verified,
            'transaction_id' => $object->get('txn_id'),
            'order_id' => $order ? $order->id : null,
            'payment_status' => $object->get('payment_status'),
            'request_method' => $this->request->method(),
            'request_url' => $this->request->url(),
            'request_headers' => json_encode($this->request->header()),
            'payload' => json_encode($this->request->all()),
        ]);

        if ($paypal->isVerified() && $paypal->isCompleted()) {
            if ($order && $order->unpaid()) {
                $order->update([
                    'payment_status' => $order::COMPLETED,
                ]);

                // notify customer
                // notify order handling staff
                // update database logic
            }
        }
    }
}

Testing

PayPal provides an Instant Payment Notification (IPN) simulator to test your integration.

If you want to test with real sandbox credentials, use your staging server for your application, as PayPal never comes to your local development environment with IPN request.

I personally use Insomnia, to design, and test APIs.

Conclusion

The complete source code is available in the github repository, where I push the real codes I prepare while creating every single tutorial on my blog.

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Last Updated: Jan 7, 2021

Happy Coding!

]]>
<![CDATA[Disabling CSRF on Specific Route via Middleware]]> https://sujipthapa.co/blog/disabling-csrf-on-specific-route-via-middleware a0face84-4ebf-4183-93f0-5463b5eb7eae 2018-02-09T15:15:00+05:45 Mr. Sujip Thapa I was lately working with PayPal API on my Laravel project. In the process of coding and testing for Instant Payment Notification (IPN) part, I got an issue with csrf token. The issue was mainly with the POST request to the application via external service, so it threw TokenMismatchException via the VerifyCsrfToken middleware.

One best thing is Laravel ships with CSRF enabled by default for each HTTP request that enters the application, which is made really easy, it handles automatically.

If your application consumes third-party API service, that service may be a webhook to notify about any event and that sends HTTP request to your application. You need to be aware that Laravel filters the request that enters without csrf token, as it monitors all request entering into the application for security reason.

There is a good solution as well, and that also ships with the framework by default. See below how to disable checking csrf token for specific routes in your application, and that fixed my issue as well.

app/Http/Middleware/VerifyCsrfToken.php

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        '/webhook/paypal/*',
    ];

 

You could specify multiple URLs on that array if you would like to exclude other routes.

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Eloquent: Query Builder Compare Two Date Columns]]> https://sujipthapa.co/blog/eloquent-query-builder-compare-two-date-columns 84ce2416-f337-4e5c-95bb-471842931352 2018-01-22T06:17:17+05:45 Mr. Sujip Thapa I regularly share my knowledge about Laravel, Web Development tips, and tutorials via my blog.

Now, here in this article, I am going to cover about comparing two different timestamp columns to filter out records from the database.

Recently I was working on filtering some API activity log to push them on notification interface, and the database design was with columns consisted of timestamp datatype as synced_at, optimized_at.

I wanted to filter the activity log based on values in two dates columns synced_at, optimized_at, my logic was to fetch database records if the timestamp in synced_at is greater optimized_at. The API process normally does a remote sync and does some database optimization task for making frontend stuff load faster with a database view.

I want to give you two different ways of comparing the columns.

Activity::where('synced_at', '>', DB::raw('optimized_at'))
    ->latest()
    ->get();

or

Activity::whereRaw('unsynced_at > optimized_at')
    ->latest()
    ->get();

Both of the database queries from the above code snippet give same results.

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Adding Custom Header with Response in Laravel]]> https://sujipthapa.co/blog/adding-custom-header-with-response-in-laravel 60a07da5-3925-40f1-826a-37a80abdb22f 2018-01-14T17:01:17+05:45 Mr. Sujip Thapa I was working out for the best way to add some custom header to my application for most of the view responses, I wanted the header to be "Cache-Control: no-cache, must-revalidate".

I came up with the best solution with a laravel middleware and decided to share the code snippets with the laravel community people as well. The code snippet will give an idea for you to work on next similar logic and the great solution for setting up the custom header.

Let's get started ;)

Create new middleware with an artisan command, it will be stored in \App\Http\Middleware\NoCache.php

php artisan make:middleware NoCache

<?php

namespace App\Http\Middleware;

use Closure;

/**
 * Class NoCache
 * @package App\Http\Middleware
 */
class NoCache
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $response->header('Cache-Control', 'no-cache, must-revalidate');

        return $response;
    }
}

Open \App\Http\Kernel.php and register the middleware.

There are different ways you might want to use this middleware, you could either apply it globally on every HTTP request over the application or use only on specific routes.

 

a. Register like below to apply globally on every HTTP request.

    /**
     * @var array
     */
    protected $middleware = [
        ...
        \App\Http\Middleware\NoCache::class,
    ];

b. Another way is, registering with a key and apply it on routes with that key like below.

    /**
     * @var array
     */
    protected $routeMiddleware = [
        ...
        'nocache' => \App\Http\Middleware\NoCache::class,
    ];

Now, applying middleware on routes.

Route::group(['middleware' => 'nocache'], function () {
    Route::get('blog/{slug}', [
        'as' => 'app.blog.view',
        'uses' => 'BlogController@view',
    ]);
});

We are done!

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Automatically Posting to Facebook Page via Laravel Notifications]]> https://sujipthapa.co/blog/automatically-posting-to-facebook-page-via-laravel-notifications a25bb0d3-356c-4305-ab86-c583d29a8f8c 2018-01-10T15:40:00+05:45 Mr. Sujip Thapa  A powerful feature that came out with Laravel v5.3 was Laravel Notifications. There are several numbers of notification channels created by the contributors in a github organization called laravel-notification-channels.

The notifications feature is fully available with later versions after it was launched along with the framework version v5.3. After that people started creating different notifications channel that can be used with your laravel application.

Laravel framework ships with support for sending email, SMS(via Nexmo), and Slack. If you wish to store notifications in a database, they may also be stored, so you can display.

I’ve been working on a new update for this website to make auto-posting to the facebook page via API when a new blog post is published on the website. I'm using a package that has already been created by Ahmed Ashraf.

I will give a full overview of available methods and fully functional code snippets of how I implemented it to automatically post them on my Facebook page.

Package Installation

You can easily install this package via composer:

composer require laravel-notification-channels/facebook-poster

If you are below Laravel v5.5, you need to register the service provider class to config/app.php

...
'providers' => [
   ...    
   NotificationChannels\FacebookPoster\FacebookPosterServiceProvider::class,
],
...

Next, you need to go to Facebook to generate some API credentials, to set up this Facebook Poster service.

I recently wrote a detailed blog post to cover the process for generating API credentials, visit this post "Generating Never Expiring Facebook Page Access Token".

After completing the process of generating the API credentials, put them on config/services.php

'facebook_poster' => [
    'app_id' => env('FACEBOOK_APP_ID'),
    'app_secret' => env('FACEBOOK_APP_SECRET'),
    'access_token' => env('FACEBOOK_ACCESS_TOKEN'),
],

Setup Model

In my project setup, I have a Blog model that is responsible for storing and retrieving all blog posts. I'm observing the laravel model events, laravel fires many events in different context. In my case, I am listening to the created event to automate this process.

You can use your own model related to your topic that you already have. Simply use the Notifiable trait to it.

use Illuminate\Notifications\Notifiable;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    use Notifiable;

Now, create a notification class with a command:

php artisan make:notification ArticlePublished

All you need to do is, adjust via() method, and create a new toFacebookPoster() method, which will be called via FacebookPosterChannel.php class, view the complete class below.

app\Notifications\ArticlePublished.php

<?php

namespace App\Notifications;

use App\Blog;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use NotificationChannels\FacebookPoster\FacebookPosterChannel;
use NotificationChannels\FacebookPoster\FacebookPosterPost;

/**
 * Class ArticlePublished
 * @package App\Notifications
 */
class ArticlePublished extends Notification
{
    use Queueable;

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return [FacebookPosterChannel::class];
    }

    /**
     * @param $blog
     */
    public function toFacebookPoster($blog)
    {
        return with(new FacebookPosterPost($blog->title))
            ->withLink($blog->getLink());
    }
}

Now, most interesting part is making the process automated. I'm creating an event listener to stay ready to fire up a notification when a new blog is stored in the database.

If you want to discover the supported methods, visit the github repository for the package.

You could either fire the notification via the controller after the blog post is created:

/**
 * Create a new blog instance.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    // Validate the request...

    $blog = Blog::create([
        'title' => $request->title,
        'slug' => $request->slug
        ]);

    $blog->notify(new ArticlePublished);
}

Another better way is listening to the model events, the cleaner way I like and the way I follow every single day.

Observing Model

Create a new event subscriber class under, app/Listenerscall it BlogEventSubscriber.php

<?php

namespace App\Listeners;

use App\Notifications\ArticlePublished;

/**
 * Class BlogEventSubscriber
 * @package App\Listeners
 */
class BlogEventSubscriber
{

    /**
     * Handle blog creating events.
     *
     * @param $blog
     */
    public function onCreated($blog)
    {
        $blog->notify(new ArticlePublished());
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'eloquent.created: App\Blog',
            'App\Listeners\BlogEventSubscriber@onCreated'
        );
    }
}

We need to register this class in, app/Providers/EventServiceProvider.php

    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\BlogEventSubscriber',
    ];

Development Issue

I tried to send the localhost link along with Facebook post but it throws an exception while sending localhost URL, so be aware of that.

    public function toFacebookPoster($blog)
    {
        return with(new FacebookPosterPost($blog->title))
            ->withLink($blog->getLink());
    }

We are done!

Conclusion

Thanks for reading this article up to the end, if you have any feedback regarding the post please feel free to leave your comment below. The code snippets are fully tested, and the full source is available in the github repository.

]]>
<![CDATA[Generating Never Expiring Facebook Page Access Token]]> https://sujipthapa.co/blog/generating-never-expiring-facebook-page-access-token 09805c44-b467-4c39-9d91-4ce855f34d68 2018-01-09T15:17:17+05:45 Mr. Sujip Thapa I’ve been working on the new feature for my own blog to auto-posting the posts from the website to the facebook page via Facebook Graph API. My logic was just to make the process automated when I publish new blog post on my website end.

Below, I will cover the processes that are needed on facebook end to collect the required credentials to make the API connectivity successful.

The main credentials needed are, app_id, app_secrect, access_token for my use case.
We can easily get the app_id, app_secrect after creating the app, but to get the long-lived page access token there are some steps to follow.

Facebook gives short-lived, long-lived access tokens. I prefer to generate long-lived access token just to get rid of the expiry date and monitoring effort required for it.

I will cover all the steps we need to follow to get the never expiring page access token.

Steps

1. Sign up for facebook developers channel.

2. Create a Facebook App or use an existing app if you already have one.

null

3. Copy, App ID, App Secret

4. Now, navigate to Facebook Graph API Explorer, to generate short-lived access token.

a. Firstly, Select an app from Application drop-down.

b. Again, in the next drop-down select "Get user access token".

c. Once you click on “Get user access token in the drop down” you will see a pop-up window like below. There you will be able to select the permission(scopes) for the user access token.

d. Here for my use case I have only selected “publish pages” and “manager pages” permissions which are needed to create never expiring page access token. If would like to understand more about the scopes visit permissions doc.

Generate user access token by clicking on "Get Access Token" button, this will create short-lived user access token.

5. Now we will need to create a long-lived user access token.

Now, navigate to Access Token Tool this page, you will see short-lived user access token, app access token for all the apps you have under your account.

Press debug option at right side for the user access token for the current app we are trying to create a long-lived access token. This will again take you to Access Token Debugger where you will see the full information for the short-lived user access token.

null

Short-lived user access token means that will expire after an hour. So to extend the expiry date, we need to go to the bottom, there is an option to generate long-lived(2 months) user access token. Click on “Extend Access Token” and you will get the long-lived access token.

6. Lastly, creating never expiring Access Token.

Again, go to Graph API Explorer and paste the recently created long-lived user access token in the Access Token field.

Now, we need to change the API endpoint to access “/me/accounts” and click on "Submit" button. This process will show the result with all pages information including page access token i.e never expiring token.

7. Finally, we are at the verification step to make sure the page access token is never expiring.

null

Hurray!

Last Updated - May 24, 2018 : The process I have explained above also works for new version v3.0 of Facebook Graph API

Conclusion

Thanks for reading this article up to the end, if you have any feedback regarding the post please feel free to leave your comment below.

Happy Coding!

]]>
<![CDATA[Speed up your Composer Install or Update Process]]> https://sujipthapa.co/blog/speed-up-your-composer-install-or-update-process 083df214-0532-4eee-8038-18855ea79ab8 2018-01-05T21:31:18+05:45 Mr. Sujip Thapa We all know that, whenever we go for running composer update, we, of course, need to wait until the dependencies are loaded and download. It usually takes up to 5-10 minutes to complete the full process of pulling down from the package hosted server. This waiting time depends on the performance of your machine.

In this article, I am going to give an overview of how to speed up your composer update process. I recently used and experimented the process, so I wanted to share with everyone who wants to utilize this speedy way.

 

There are only two methods, I know and I did experiment with.

Disabling the Xdebug mode

Installing a third-party library prestissimo created by @hirak

If you want to check your xdebug mode, when running composer you can follow this StackOverflow link.

Another method is using a global composer plugin prestissimo, which is blazingly faster, this plugin helps to installs dependencies in parallel mode.

Follow the below command to install the composer plugin globally on your machine.

Install

composer global require hirak/prestissimo

Uninstall

composer global remove hirak/prestissimo

After installing the composer plugin globally you are done, next is to go to your terminal and hit composer update and test the speed.

I am personally using this composer plugin to make my composer process faster. I highly recommend you to try it. I haven't run into any problems using it at all. If you found any issues let people know about it, so feel free to leave your comment below.

 

In my case the composer update used to take around 10 minutes to finish, now after utilizing this package, it turned into 1 min and less, which is blazingly faster for me, a lot of time-saving while waiting for a package to install and move to code works.

Thanks for reading this article, If you like feel free to share with other people in your circle to let them know about the above.

Enjoy !

]]>
<![CDATA[Redirect to Previous URL After Logging in, Laravel v5.5]]> https://sujipthapa.co/blog/redirect-to-previous-url-after-logging-in-laravel-v55 7c402d62-3e69-48af-a7f7-7e61d721a946 2018-01-01T14:52:15+05:45 Mr. Sujip Thapa An end-user visit the website tries to view several products, services by navigating to different pages. If he finally wants to submit an order. Now the website, of course, takes him to login or register page to provide his personal information for shipping or billing purpose. After logging in, he might want the website to take him back again to the previous page where he was viewing the product information.

I was talking about the user experience while browsing the pages, the behavior for the end-user might differ to each other, but the website owner should make a policy for the better user experience. Especially, the e-commerce websites should consider this types of UI for their end-user.

 

Let's say you are running an e-commerce website, where people can view products, create orders. Let's assume you want the website easily navigates back the user to the previous page after logging into the website.

Let's start building the feature for redirecting the user back to the previous page.

Let's add a section in the login file.

resources/views/auth/login.blade.php

<div class="form-group">
    @if (Request::has('previous'))
        <input type="hidden" name="previous" value="{{ Request::get('previous') }}">
    @else
        <input type="hidden" name="previous" value="{{ URL::previous() }}">
    @endif
</div>

View the login.blade.php file on github for the full updated code.

Additionally, you might want to add a login link to your website pages like:

<a href="{{ route('login') . '?previous=' . Request::fullUrl() }}">
    Login
</a>

When a user clicks the login link, the page redirects to the login page, it then sets the previous URL to input that will be sent to login POST request.

    /**
     * @return string
     */
    public function redirectTo()
    {
        if ($this->request->has('previous')) {
            $this->redirectTo = $this->request->get('previous');
        }

        return $this->redirectTo ?? '/home';
    }

The LoginController.php will handle the previous input value to check if there is the previous link to redirect back. A method redirectTo() created in LoginController.php is responsible to determine the next redirect path after the user is successfully authenticated.

 

If you're not familiar with Laravel's auth system and confused that only defining the new redirectTo() method helps in redirecting to our intended URL, then you can have a look at, AuthenticatesUsers.php, RedirectsUsers.php. We cannot touch the core files shipped with Laravel framework, but there is a way we can override those methods to change the behavior as per our requirement.

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Integrating Medium Style Image Zoom]]> https://sujipthapa.co/blog/integrating-medium-style-image-zoom 37a2b5f2-588f-4631-9cf6-7988acaf1eec 2017-12-31T16:16:17+05:45 Mr. Sujip Thapa I'm sure you already know about Medium, a widely used and popular publishing platform to share, articles, important stores over the internet.

Medium – Read, write and share stories that matter.

Medium is an online publishing platform developed by Evan Williams, and launched in August 2012. It is owned by A Medium Corporation

Here I'm going to show how I implemented a medium style clean image zoom-in, zoom-out feature for your website. If you would like to see the medium style image zoom-in, out feature on my blog that I implemented recently with the new design published last week, visit some articles to try out the feature. for example: here is a post with few images, have a look at this blog post.

Before diving into the code, I would like to say big thanks to the package creator @francoischalifour, who built this awesome package as open source.

 

The package is available on the npm registry, with no dependencies.

Features

  • Responsive — scale on mobile and desktop.
  • Performant and lightweight — should be able to reach 60 fps.
  • High definition support — load the HD version of your image on zoom.
  • Image selection — apply the zoom to a selection of images.
  • Mouse, keyboard, and gesture friendly — click anywhere, press a key or scroll away to dismiss the zoom.
  • Event handling — trigger events when the zoom enters a new state.
  • Customization — set your own margin, background, and scroll offset.
  • Custom templates — extend the default look to match your UI.
  • Link support — open the link to the image in a new tab when a meta key is held (⌘ or Ctrl)
  • Image opener — when no link, open the image source in a new tab when a meta key is held (⌘ or Ctrl)

Installation

npm install --save medium-zoom

# or

yarn add medium-zoom

Or, If you want to use the CDN version:

<script src="https://unpkg.com/medium-zoom@0/dist/medium-zoom.min.js"></script>

Usage

Import the script:

<script src="node_modules/medium-zoom/dist/medium-zoom.min.js"></script>

Or, using the module syntax or imports:

const mediumZoom = require('medium-zoom')
// or
import mediumZoom from 'medium-zoom'

You don't need to worry about the CSS styling imports.

Integration

mediumZoom(<selector>, <options>)

In my case, I wanted to use medium style zoom, in-out feature for every image I add to the blog post. i.e It is only applied to the post detail page, where the user wants to view the image in zoom, in-out mode. I wanted my readers to enjoy the view of images without having to open it on a new tab to understand it clearly.

 
mediumZoom('.post__container img');

By default, the zoom is applied to all scaled images (with HTML or CSS properties). You can specify the zoomable images with a CSS selector and add options.

Additionally, you can pass an HTML Element, a NodeList, an HTMLCollection or an array of images to the plugin.

// CSS selector
mediumZoom('#cover')

// HTML Element
mediumZoom(document.getElementById('cover'))

// NodeList
mediumZoom(document.querySelectorAll('[data-action="zoom"]'))

// HTMLCollection
mediumZoom(document.images)

// Array
const imagesToZoom = [
  document.querySelector('#cover'),
  ...document.querySelectorAll('[data-action="zoom"]')
]

mediumZoom(imagesToZoom)

API

Options can be passed via a JavaScript object through the mediumZoom call.

Open up the github repo and go through the API options available if you wish to add the customized feature for your web application.

Go through the examples section if you wish to understand the complete possible features provided by the package.

 

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Authorizing New Device based on IP Address, with Laravel Middleware]]> https://sujipthapa.co/blog/authorizing-new-device-based-on-ip-address-with-laravel-middleware 6c757df3-5cad-4866-9f51-3eef2813fa3b 2017-12-27T16:17:16+05:45 Mr. Sujip Thapa I recently saw few of the e-commerce & payment gateway sites using device authorization system based on IP address, browser etc. I was also working on a same for my client's web app recently so wanted to share a detailed post with the community people.

In this blog post, I will go in detail to cover following stuff.

  • Allow the user to enter login credentials, if the login credentials are valid, also verify if the user's device is authorized with the current IP address assigned to the user's device.
  • If the user's device is not authorized to access the protected pages, like the dashboard, the application will send an email to the recently logged in user's email to ask for authorizing the device before proceeding.
  • After sending the email, the page will redirect to wait for email authorization, and that will keep refreshing on certain time interval to check if the user is authorized, so it can redirect to the dashboard.
  • If the user is not active and did not authorize the device within next 15 min after email is sent, it will log out the user as a reason for a timeout with a certain message.

 

I will be using Laravel v5.5 to build this feature. Also, for user's device information, IP to location and to generate a unique token for every new request I will be using my own two PHP packages published on packagist as open source, and browser detection library by @cbschuld. So make sure you have a composer, PHP 7.0 >= installed in your environment.

Package Installation

You can install those packages via composer.

composer require sudiptpa/guid

composer require sudiptpa/ipstack


Now let's start with creating the foundation of the feature to implement with Laravel project.

app/routes/web.php

Route::group(['middleware' => ['authorize', 'auth']], function () {
    Route::get('/dashboard', [
        'name' => 'Dashboard',
        'as' => 'dashboard',
        'uses' => 'HomeController@dashboard',
    ]);
});

Route::group(['middleware' => ['auth']], function () {
    Route::get('/authorize/{token}', [
        'name' => 'Authorize Login',
        'as' => 'authorize.device',
        'uses' => 'Auth\AuthorizeController@verify',
    ]);

    Route::post('/authorize/resend', [
        'name' => 'Authorize',
        'as' => 'authorize.resend',
        'uses' => 'Auth\AuthorizeController@resend',
    ]);
});


The first grouped route shows, that the user cannot access the dashboard without logging in and, needs authorization. I will show the authorization middleware very soon below.

Similarly, the second grouped routes, show that only authenticated users can verify the device with associated IP address.

app/database/migrations

 

Now let's create a migration table to store the authorizes for all users.

<?php

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

class CreateAuthorizesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('authorizes', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->nullable();
            $table->boolean('authorized')->nullable();
            $table->string('token')->nullable();
            $table->string('ip_address')->nullable();
            $table->string('browser')->nullable();
            $table->string('os')->nullable();
            $table->string('location')->nullable();
            $table->tinyInteger('attempt')->default(0)->nullable();
            $table->timestamp('authorized_at')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

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


app/Authorize.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;

/**
 * Class Authorize
 * @package App
 */
class Authorize extends Model
{
    /**
     * @var string
     */
    protected $table = 'authorizes';

    /**
     * @var boolean
     */
    public $timestamps = true;

    /**
     * @var array
     */
    protected $dates = ['authorized_at', 'deleted_at'];

    /**
     * @var string
     */
    protected $fillable = [
        'user_id', 'authorized', 'token', 'ip_address', 'browser', 'os', 'location', 'attempt', 'authorized_at',
    ];

    /**
     * @param $query
     * @return mixed
     */
    public function scopeCurrentUser($query)
    {
        return $query->where('user_id', Auth::id());
    }

    /**
     * @param $date
     */
    public function setAuthorizedAtAttribute($date)
    {
        $this->attributes['authorized_at'] = Carbon::parse($date);
    }

    /**
     * @return mixed
     */
    public static function active()
    {
        return with(new self)
            ->where('ip_address', Request::ip())
            ->where('authorized', true)
            ->where('authorized_at', '<', Carbon::tomorrow())
            ->first();
    }

    /**
     * @return mixed
     */
    public function resetAttempt()
    {
        $this->update(['attempt' => 0]);

        return $this;
    }

    /**
     * @return mixed
     */
    public function noAttempt()
    {
        return $this->attempt < 1;
    }

    /**
     * @param $token
     */
    public static function validateToken($token = null)
    {
        $query = self::where([
            'token' => $token,
        ])->first();

        if (sizeof($query)) {
            $query->update([
                'authorized' => true,
                'authorized_at' => now(),
            ]);

            return self::active();
        }
    }

    /**
     * @return mixed
     */
    public static function make()
    {
        return self::firstOrCreate([
            'ip_address' => Request::ip(),
            'authorized' => false,
            'user_id' => Auth::id(),
        ]);
    }

    /**
     * @return mixed
     */
    public static function inactive()
    {
        $query = self::active();

        return $query ? null : true;
    }
}


In this model I have written few methods that actually work on authorization, you will see its usage below with middleware, controller.

In this blog post, I am only covering only the dashboard, login routes to be protected from the authorize middleware. I only forced the LoginController.php to apply on login routes only. Make sure you apply this middleware to other routes to be protected from unauthorized access to your application.

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/dashboard';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('authorize')->only('login');
        $this->middleware('guest')->except('logout');
    }


app/Http/Middleware/AuthorizeDevice.php

<?php

namespace App\Http\Middleware;

use App\Authorize;
use App\Mail\AuthorizeDevice as AuthorizeMail;
use Closure;
use Illuminate\Support\Facades\Mail;

/**
 * Class AuthorizeDevice
 * @package App\Http\Middleware
 */
class AuthorizeDevice
{
    /**
     * @var \App\Authorize
     */
    private $authorize;

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Authorize::inactive() && auth()->check()) {
            $this->authorize = Authorize::make();

            if ($this->authorize->noAttempt()) {
                Mail::to($request->user())
                    ->send(new AuthorizeMail($this->authorize));

                $this->authorize->increment('attempt');
            }

            if ($this->timeout()) {
                auth()->guard()->logout();

                $request->session()->invalidate();

                return redirect('/')->with([
                    'status' => 'You are logged out of system, please follow the link we sent before 15 minutes to authorize your device, the link will be valid with same IP for 24hrs.',
                ]);
            }

            return response()->view('auth.authorize');
        }

        return $next($request);
    }

    /**
     * Determines if the authorize attempt is timed out.
     *
     * @return bool
     */
    private function timeout()
    {
        $waiting = $this->authorize
            ->created_at
            ->addMinutes(15);

        if (now() >= $waiting) {
            return true;
        }

        return false;
    }
}


Now register the middleware within app/Http/Kernel.php

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        ...
        'authorize' => \App\Http\Middleware\AuthorizeDevice::class,
    ];


The most interesting part of this blog is the above middleware class that handles the incoming request into an application that enters the actual routes which are protected before the user's device is authorized.

Open up the Authorize.php model from GitHub to understand the full methods about how it is working.

The middleware is before middleware, follow the laravel official doc if you don't know about it.

Actually, it works with the authenticated user's when the device is not authorized, so it protects the user's trying to enter the application without authorization.

The middleware first, checks, if there is an authorization, exists in the database or creates a new with the current IP address for currently logged in user.

It sends the email requesting authorization to access the application, see below for the simple preview I made while writing this blog post.

The middleware is also handling the session timeout for the authorization to be made within next 15 min after the email has been sent, otherwise, it will log out the user automatically.

If you wish to manage the separate middleware for the timeout feature you could use another class, and register it accordingly like we did above.

Alright, now let's move to email sending code.

app/Mail/AuthorizeDevice.php

<?php

namespace App\Mail;

use App\Browser;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Sujip\Ipstack\Ipstack;

/**
 * Class AuthorizeDevice
 * @package App\Mail
 */
class AuthorizeDevice extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * @var mixed
     */
    protected $authorize;

    /**
     * Create a new message instance.
     *
     * @param $authorize
     *  @return void
     */
    public function __construct($authorize)
    {
        $this->authorize = $authorize;
        $this->browser = new Browser;
    }

    /**
     * @return mixed
     */
    public function setBrowser()
    {
        $this->authorize->browser = $this->browser->getBrowser();

        return $this;
    }

    /**
     * @return mixed
     */
    public function setToken()
    {
        $this->authorize->token = guid();

        return $this;
    }

    /**
     * @return mixed
     */
    public function setLocation()
    {
        $location = with(new Ipstack(
            $this->authorize->ip_address
        ))->formatted();

        $this->authorize->location = $location;

        return $this;
    }

    /**
     * @return mixed
     */
    public function setPlatform()
    {
        $this->authorize->os = $this->browser->getPlatform();

        return $this;
    }

    public function saveAuthorize()
    {
        $this->authorize->save();
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        $this
            ->setBrowser()
            ->setToken()
            ->setLocation()
            ->setPlatform()
            ->saveAuthorize();

        return $this
            ->view('emails.auth.authorize')
            ->with(['authorize' => $this->authorize]);
    }
}


The mail sending class prepares data like location, IP, token, browser, platform(os) and email sending view. In order to collect the user's current device information, I am using a third-party class written by @cbschuld as open source code in GitHub. I would like to say thanks for the nice package to him.

Also, to generate the token, I am using a GUID generator package that I wrote recently for my own use case and published as opensource as well. The guid() helper function will return global unique identifier every time a user requires a token to authorize the device.

The coolest feature in the Laravel 5.5 Illuminate\Mail\Mailable is mail preview to test the email view before sending it. I loved it. :)

See example below how I tested myself.

Route::get('/mailable', function () {
    $authorize = App\Authorize::find(1);

    return new App\Mail\AuthorizeDevice($authorize);
});


After sending the email requesting you to authorize the device, the preview I built was like below, you are free to apply CSS as per your use case.

null

The AuthorizationController.php was written to handle, resend authorize email, and validate the token from the email sent to the current user.

<?php

namespace App\Http\Controllers\Auth;

use App\Authorize;
use App\Http\Controllers\Controller;
use App\Mail\AuthorizeDevice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redirect;

/**
 * Class AuthorizeController
 * @package App\Http\Controllers\Auth
 */
class AuthorizeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     *  Validate the token for the Authorization.
     *
     * @param $token
     * @return \Illuminate\Http\Response
     */
    public function verify($token = null)
    {
        if (Authorize::validateToken($token)) {
            return Redirect::route('dashboard')->with([
                'status' => 'Awesome ! you are now authorized !',
            ]);
        }

        return Redirect::route('login')->with([
            'error' => "Oh snap ! the authorization token is either expired or invalid. Click on Email didn't arraive ? again",
        ]);
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function resend(Request $request)
    {
        if (Authorize::inactive() && auth()->check()) {
            $authorize = Authorize::make()
                ->resetAttempt();

            Mail::to($request->user())
                ->send(new AuthorizeDevice($authorize));

            $authorize->increment('attempt');

            return view('auth.authorize');
        }
    }
}


View the auth/authorize.blade.php from github to know how it was like.

 

View the complete code pushed to github while I was writing this blog post.

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[A Simple IP to Geo Location solution for PHP]]> https://sujipthapa.co/blog/a-simple-ip-to-geo-location-solution-for-php f3b854bd-24fb-4f7f-a30c-917a4bcff5ed 2017-12-25T12:23:10+05:45 Mr. Sujip Thapa The freegeoip.net service is discontinued, so please refer to the new package to get the same service sudiptpa/ipstack

I've abandoned the legacy package sudiptpa/geoip as the service provider discontinued on July 1st, 2018.

I was working on my own use case to find out IP to Geo Location and finally found a free open source service from http://freegeoip.net. It is a community funded project. The service includes GeoLite2 data created by MaxMind

freegeoip.net provides a public HTTP API for software developers to search the geolocation of IP addresses. It uses a database of IP addresses that are associated with cities along with other relevant information like time zone, latitude, and longitude.

You're allowed up to 15,000 queries per hour by default. Once this limit is reached, all of your requests will result in HTTP 403, forbidden, until your quota is cleared.

The freegeoip web server is free and open source so if the public service limit is a problem for you, download it and run your own instance.

API support.

The HTTP API takes GET requests in XML, JSON, CSV format.

http://freegeoip.net/{format}/{IP_or_hostname}

While making an API call, if no IP or hostname is provided, then your own IP is looked up.

 

Package Implementation

To make this API call simple and organized I decided to create a package for my own use case as well as for the community people to use it as open source. It is available to view it on Github.

Installation

You can install the package via composer.

composer require sudiptpa/geoip


Usage

This package only supports json format for now.

Here are a few examples on how you can use the package:

$geo = new Sujip\GeoIp\GeoIp($ip);

$geo->country();

$geo->city();

$geo->region();

$geo->formatted(); // Jawalakhel, Central Region, Nepal

Also, have a look at the source code of Sujip\GeoIp\GeoIp to discover the methods you can use via GitHub.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[A Complete Guide to Integrate Facebook Like and Share]]> https://sujipthapa.co/blog/a-complete-guide-to-integrate-facebook-like-and-share ec3eb66a-4c08-460f-8b0f-c26ca64f4112 2017-12-22T16:10:15+05:45 Mr. Sujip Thapa Nowadays social media platform has grown a lot more and really helpful in marketing, sharing information, communication etc. So here in this post, I am going into detail about how to integrate the Facebook social plugin, with real-time Like, Share button on your products links or blog posts on your web application.

I will be using Laravel framework to demonstrate this integration. So before starting, I will assume you have laravel framework setup, facebook account ready to use in facebook developers mode.

 

At the end of the post, you will get the below output.

Now let's start with creating the basic foundation to show the blog posts and view it.

app/routes/web.php

Route::group(['prefix' => 'blog'], function () {
    Route::get('/', [
        'as' => 'app.blog',
        'uses' => 'BlogController@index',
    ]);
    Route::get('/{slug}', [
        'as' => 'app.blog.view',
        'uses' => 'BlogController@view',
    ]);
});

app/database/migrations

<?php

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

class CreateBlogsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('blogs', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('slug');
            $table->string('excerpt');
            $table->longText('content')->nullable();
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
        });
    }

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

app/Blog.php

<?php

namespace App;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    /**
     * @var string
     */
    protected $table = 'blogs';

    /**
     * @var array
     */
    protected $dates = ['published_at'];

    /**
     * @var array
     */
    protected $fillable = ['title', 'slug', 'excerpt', 'content', 'published_at'];

    /**
     * @param $query
     * @return mixed
     */
    public function scopePublished($query)
    {
        return $query->where('published_at', '<=', Carbon::now());
    }

    /**
     * @param $date
     */
    public function setPublishedAtAttribute($date)
    {
        $this->attributes['published_at'] = Carbon::parse($date);
    }
}

app/Http/Controllers/BlogController.php

<?php

namespace App\Http\Controllers;

use App\Blog;
use Illuminate\Http\Request;

/**
 * Class BlogController
 * @package App\Http\Controllers
 */
class BlogController extends Controller
{
    /**
     * @var int
     */
    protected $paginate = 10;

    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function index()
    {
        $blogs = Blog::published()->paginate(
            $this->paginate
        );

        return view('blog.index', [
            'blogs' => $blogs,
            'title' => 'Blogs',
            'open_graph' => [
                'title' => 'Blogs',
                'image' => asset('assets/logo.jpeg'),
                'url' => $this->request->url(),
                'description' => 'A blog website to share tutorials and tips !',
                'keywords' => 'A Laravel Blog, Tips, Tutorials',
            ],
        ]);
    }

    /**
     * @param $slug
     */
    public function view($slug)
    {
        $blog = Blog::published()
            ->where('slug', $slug)
            ->firstOrFail();

        return view('blog.view', [
            'blog' => $blog,
            'open_graph' => [
                'title' => $blog->title,
                'image' => asset('assets/preview.jpeg'),
                'url' => $this->request->url(),
                'description' => $blog->excerpt,
            ],
        ]);
    }
}

Now, I will assume you have following code in your layout blade in your view folder. See how I have created my layout file here for this tutorial.

<!DOCTYPE html>
<html lang="en">
<head>
    ....
    ....
    ....

    @yield('head')

</head>
<body>

Open up the file form Github repository index.blade.php

Similarly, open up view.blade.php

 

The above integration will show the following output.

Blogs

null

 

Blog Preview

null

Now, let's navigate to facebook developers doc.

Scroll down to - Like Button Configurator

You are free to configure the URL to Like, Width, Layout, Action Type, Button Size, Show Friends's Faces, Include Share Button options as per your wish.

After completing the above configuration click on Get Code button. It will show a pop up window like below. Don't forget to read the information shown in the pop up window carefully. The share feature requires open graph meta tags on heads section of your html page, so have a look at the code to know how I implemented it.

There are two different way of implementing the facebook real-time like, share buttons.

 

Javascript SDK

Step 1: Include the JavaScript SDK on your page once, ideally right after the opening body tag.

<div id="fb-root"></div>
<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.11&appId=180539309049634';
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

Don't forget to replace the appId=180539309049634 with your own app. Select your facebook app before generating the code. If you don't have any app for your site, create one from facebook developers page under My App.

Step 2: Place this code wherever you want the plugin to appear on your page.

<div class="fb-like" data-href="{{ route('app.blog.view', ['slug' => $post->slug]) }}" data-layout="standard" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div>

IFrame

<iframe src="https://www.facebook.com/plugins/like.php?href={{ route('app.blog.view', ['slug' => $post->slug]) }}&width=51&layout=box_count&action=like&size=small&show_faces=true&share=true&height=65&appId" width="51" height="65" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>

Place this code wherever you want the plugin to appear on your page.

Javascript Code Preview

Now, look at the index.blade.php, view.blade.php on my Github repo about how I implemented the code to show the facebook real-time like, share buttons.

 

View the complete code pushed to github while I was writing this tutorial.

Finally! now we have a complete guide setup for integrating facebook, real-time like, share buttons.

Conclusion

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Create unique GUID with PHP]]> https://sujipthapa.co/blog/create-unique-guid-with-php 3520273c-c5e9-4df9-9827-1212d60c75ee 2017-10-06T06:02:15+05:45 Mr. Sujip Thapa In this blog post, I am going to write about my little PHP package for creating globally unique identifiers (GUID).

Begin with installation via Composer:

composer require sudiptpa/guid

I was inspired mainly to create this package while I was searching for GUID information in the php.net official PHP website. I found a good solution by a community member Dave Pearsonview source code here.

I was looking a solution for Laravel, so I created this package as reusable for everyone as an opensource package.

 

 Usage

In order to consume this package from Laravel application, register the package service provider within your config/app.phpfile.

'providers' => [
    Sujip\Guid\GuidServiceProvider::class,
]

'aliases' => [
   'Guid' => 'Sujip\Guid\Guid'
]

If your Laravel application is running with v5.5 and higher version, this package also has support for package auto-discovery, Laravel will automatically register its service providers and facades when it is installed, creating a convenient installation experience for you.

If you are a Laravel Developer:

Make use of Laravel Facade:

echo "GUID: " . Guid::create(); //example output : 2b23924f-0eaa-4133-848e-7ce1edeca8c9

 or use a helper function guid()

echo "GUID: " . guid(); // example output: 2b23924f-0eaa-4133-848e-7ce1edeca8c9

 If you want to use this package outside of the framework.

use Sujip\Guid\Guid;


$guid = new Guid;

$guid = $guid->create();

Output

//Example: 2b23924f-0eaa-4133-848e-7ce1edeca8c9

Conclusion

This package is framework agnostic, which means it can be used in any type of PHP projects.

Thanks for reading up to the end. If you have any feedback regarding this blog post feel free to leave your comment below. Also don't forget to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Set your Preferred Domain on Laravel Application]]> https://sujipthapa.co/blog/set-your-preferred-domain-on-laravel-application d976c361-7a03-47ae-b2d9-b696a3bbb916 2017-09-09T08:45:00+05:45 Mr. Sujip Thapa In this blog post, I would like to show how to set preferred domain from your application context.

Before diving into the code, let's understand why the preferred domain is much important for SEO, web pages indexing on Search Engine.

Follow the link to read in detail from Google support article.

The preferred domain is the one that you would like used to index your website's pages (it is sometimes referred to as the canonical domain). Links may point to your site using both the www and non-www versions of the URL (for instance, http://www.example.com and http://example.com). The preferred domain is the version that you want to be used for your site in the search results.

 

Here, in this guide, I'm going to implement non-www preferred domain for every incoming HTTP request to the application. I have created a custom Laravel Middleware to set the preferred domain.

People usually do this with server configuration, (eg: with .htaccess for apache), but you can also force it from application context. I have used this mechanism with several Laravel applications I have built.

Let's start with creating a middleware with an artisan command.

php artisan make:middleware PreferredDomain

Open up your app/Http/Middleware/PreferredDomin.php and paste code below.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Redirect;

/**
 * Class PreferredDomain
 * @package App\Http\Middleware
 */
class PreferredDomain
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (starts_with($request->header('host'), 'www.')) {
            $host = str_replace('www.', '', $request->header('host'));
            $request->headers->set('host', $host);

            return Redirect::to($request->fullUrl(), 301);
        }

        return $next($request);
    }
}

We want to run this middle over every HTTP request that comes to our application, simply we need to register the middleware class in the $middleware property in app/Http/Kernel.php class.

    protected $middleware = [
        ...

        \App\Http\Middleware\PreferredDomain::class,
    ];

Note: If you're still using v4.2 version of Laravel don't worry I have solution for you too as well ;)

 

Open up your app/filters.php

App::before(function ($request) {
    if (starts_with($request->header('host'), 'www.')) {
        $host = str_replace('www.', '', $request->header('host'));
        $request->headers->set('host', $host);

        return Redirect::to($request->fullUrl(), 301);
    }
});


Conclusion

This tutorial heavily depends on Laravel framework, I primarily use Laravel to build my projects.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Last Updated: 21st Jan, 2018

Happy Coding!

]]>
<![CDATA[Laravel v5.5: Login, Register with Username or Email]]> https://sujipthapa.co/blog/laravel-v55-login-register-with-username-or-email dbf59aa9-4880-4a63-b33c-271196ec7ad5 2017-09-09T07:03:16+05:45 Mr. Sujip Thapa In this blog post, I would like to share some ideas on how to customize your Laravel v5.5 authentication to support username or email based login. Laravel v5.5 is a second LTS version, that @laravelphp announced on Twitter on 20 Feb 2017. Just simply like the previous LTS version, this will also include 2 years of bug fixes and 3 years of security updates.

Laravel v5.5 release announcement.

If you are new to my blog, view my previous post about Laravel v5.4 authentication with username or email.

 

In my previous blog post, people were requesting to cover register, login. So, here I am going to cover both process with new version v5.5 just released at the end of August 2017.

Let's start the tutorial :)

Before starting to look at the code you should be ready with running v5.5 or fresh installation setup on your machine.

If you need a guide for the installation process see the release note and follow the official guide.

Let's begin with creating authentication scaffolding, migrations for User model.

Generate authentication scaffolding with an artisan command.

php artisan make:auth

As we are going to add username field on the database. 

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('username')->nullable()->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

Now simply run migration and go ahead.

php artisan migrate

After running this command your database table is now ready with username field support.

 

Now, open up your User model, add the username field to the $fillable array.

    protected $fillable = [
        'name', 'email', 'password', 'username',
    ];

Open up your RegisterController.php, I have added a validation rule for the username in validator() method. Similarly, I have added username field to create() method as well.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
     */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'username' => 'required|string|max:20|unique:users',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'username' => $data['username'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

Also, I have changed a little bit on resources/views/auth/register.blade.php to support username, email. So, simply add username field on it.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Register</div>
                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('register') }}">
                        {{ csrf_field() }}
                        <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                            <label for="name" class="col-md-4 control-label">Name</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
                                @if ($errors->has('name'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                        <div class="form-group{{ $errors->has('username') ? ' has-error' : '' }}">
                            <label for="username" class="col-md-4 control-label">Username</label>
                            <div class="col-md-6">
                                <input id="username" type="text" class="form-control" name="username" value="{{ old('username') }}" required>
                                @if ($errors->has('username'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('username') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>
                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>
                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password" required>
                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Register
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

 Laravel v5.5 Regist, Login Form

You're done with the registration process. Now let's look at the login process.

Open up Illuminate\Foundation\Auth\AuthenticatesUsers it has several methods to handle authentication process. In our customization we only need to override one method credentials() to our App\Http\Controllers\Auth\LoginController.php

 

Also, don't forget to add an import use Illuminate\Http\Request; 

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
     */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
            ? $this->username()
            : 'username';

        return [
            $field => $request->get($this->username()),
            'password' => $request->password,
        ];
    }
}

 You will require changing the login form to support username, email field, simply open up resources/views/auth/login.blade.php and just change input type to be text from email, just leave other as it is.

<input id="email" type="text" class="form-control" name="email" value="{{ old('email') }}" required autofocus>

 Alright ! now we have a full functionality to support login, register with username, email with Laravel v5.5.

Conclusion

This tutorial heavily depends on Laravel 5.5, so if you are going to implement this on another version of framework please have a look at the traits used properly to override the credentials array. If you need guide about v5.4 view the previous post here.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[A Guide to Integrate Omnipay PayPal with Laravel]]> https://sujipthapa.co/blog/a-guide-to-integrate-omnipay-paypal-with-laravel d27ac1da-999f-4212-9539-e4bd04d73e71 2017-07-01T05:59:14+05:45 Mr. Sujip Thapa In this blog post, I would like to share some idea about best way to integrate paypal express checkout using OmniPay with PHP. Omnipay is a framework agnostic, payment processing library for PHP 5.3 and higher.

Omnipay libraries are stable, consistent, and fully united tested by the developers and contributors around the globe. I would like to thank them for their great effort on these awesome open source packages.

Omnipay libraries are really easy to understand and integrate with any framework or non-framework based projects.

 

Also, have a look at their Github repository to discover the supported gateways you want to integrate with your project.

You can simply pull the packages which are hosted freely on packagist by using composer.

In this blog post, I will go step by steps to integrate the popular library for PayPal express checkout with omnipay/omnipay-paypal.

I am going to show the steps with laravel framework v5.4.

Before getting started with the guidelines you will need the following stuff ready for your machine.

  • Composer installed.
  • PHP >= 5.6.4 for Laravel, follow official doc.
  • Web server, the database on your machine. eg: Apache, MySQL

After successfully installing the laravel on your machine.

Important Note

If you are using Symfony 3 or Symfony 3 components, or laravel v5.* which uses Symfony 3 components please note that Omnipay 2.* version still uses guzzle 3.*, which depends on symfony/event-dispatcher 2.*. It conflicts with Symfony 3 in the regular installation. For this fix, we may need to wait until Omnipay 3.* release, which is still under development.

For the alternative fix, go to the command line cd to your project root to force the installation of symfony/event-dispatcher ^2.8 which is fully compatible with both Symfony 3 components and guzzle 3.*.

composer require symfony/event-dispatcher:^2.8

Go to `composer.json` and put the following.

{
    "require": {
        "omnipay/paypal": "~2.0"
    }
}

or

composer require omnipay/paypal

Now let's create some example route to handle the requests and Controller and Model for it.

app/routes/web.php

<?php

Route::get('/{order?}', [
    'name' => 'PayPal Express Checkout',
    'as' => 'app.home',
    'uses' => 'PayPalController@form',
]);

Route::post('/checkout/payment/{order}/paypal', [
    'name' => 'PayPal Express Checkout',
    'as' => 'checkout.payment.paypal',
    'uses' => 'PayPalController@checkout',
]);

Route::get('/paypal/checkout/{order}/completed', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.completed',
    'uses' => 'PayPalController@completed',
]);

Route::get('/paypal/checkout/{order}/cancelled', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.cancelled',
    'uses' => 'PayPalController@cancelled',
]);

Route::post('/webhook/paypal/{order?}/{env?}', [
    'name' => 'PayPal Express IPN',
    'as' => 'webhook.paypal.ipn',
    'uses' => 'PayPalController@webhook',
]);

In my projects I prefer writing named routes, we can simply use route() helper from our views. You can change the above web.php routes to your application specific standard URLs.

app/Order.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Order
 * @package App
 */
class Order extends Model
{
    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

In the Order.php model, I am just covering basic requirements for all type projects. You are free to change and assign them in your application with the relevant type of database fields.

resources/views/form.blade.php

@extends('app')

@section('content')
    <div class="container">
        <div class="gateway--info">
            <div class="gateway--desc">
                @if(session()->has('message'))
                    <p class="message">
                        {{ session('message') }}
                    </p>
                @endif
                <p><strong>Order Overview !</strong></p>
                <hr>
                <p>Item : Yearly Subscription cost !</p>
                <p>Amount : ${{ $order->amount }}</p>
                <hr>
            </div>
            <div class="gateway--paypal">
                <form method="POST" action="{{ route('checkout.payment.paypal', ['order' => encrypt(mt_rand(1, 20))]) }}">
                    {{ csrf_field() }}
                    <button class="btn btn-pay">
                        <i class="fa fa-paypal" aria-hidden="true"></i> Pay with PayPal
                    </button>
                </form>
            </div>
        </div>
    </div>
@stop

In the traditional applications, people used to set values on forms and submit the data. I personally don't prefer that. In the above form.blade.php I have created a form action with a route() helper and encrypted my order id to make it safe.

app/config/paypal.com

<?php

return [
    'credentials' => [
        'username' => env('PAYPAL_USERNAME'),
        'password' => env('PAYPAL_PASSWORD'),
        'signature' => env('PAYPAL_SIGNATURE'),
        'sandbox' => env('PAYPAL_SANDBOX')
    ],
];

If you wish to store the API credentials in the database that is your choice, for the example I have created a config file. So replace the xxx characters with your own credentials.

app/PayPal.php

<?php

namespace App;

use Omnipay\Omnipay;

/**
 * Class PayPal
 * @package App
 */
class PayPal
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('PayPal_Express');

        $gateway->setUsername(config('paypal.credentials.username'));
        $gateway->setPassword(config('paypal.credentials.password'));
        $gateway->setSignature(config('paypal.credentials.signature'));
        $gateway->setTestMode(config('paypal.credentials.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @return mixed
     */
    public function purchase(array $parameters)
    {
        $response = $this->gateway()
            ->purchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param array $parameters
     */
    public function complete(array $parameters)
    {
        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('paypal.checkout.cancelled', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('paypal.checkout.completed', $order->id);
    }

    /**
     * @param $order
     */
    public function getNotifyUrl($order)
    {
        $env = config('paypal.credentials.sandbox') ? "sandbox" : "live";

        return route('webhook.paypal.ipn', [$order->id, $env]);
    }
}

I prefer separation of concerns, in the example, I have a new class PayPal.php as a helper class for PayPalController.php

I think many people also like the clean code in the controller rather than over-complicating things on Controllers.

app/Http/Controllers/PayPalController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\PayPal;
use Illuminate\Http\Request;

/**
 * Class PayPalController
 * @package App\Http\Controllers
 */
class PayPalController extends Controller
{
    /**
     * @param Request $request
     */
    public function form(Request $request, $order_id = null)
    {
        $order_id = $order_id ?: encrypt(1);

        $order = Order::findOrFail(decrypt($order_id));

        return view('form', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function checkout($order_id, Request $request)
    {
        $order = Order::findOrFail(decrypt($order_id));

        $paypal = new PayPal;

        $response = $paypal->purchase([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
        ]);

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $paypal = new PayPal;

        $response = $paypal->complete([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
            'notifyUrl' => $paypal->getNotifyUrl($order),
        ]);

        if ($response->isSuccessful()) {
            $order->update(['transaction_id' => $response->getTransactionReference()]);

            return redirect()->route('app.home', encrypt($order_id))->with([
                'message' => 'You recent payment is sucessful with reference code ' . $response->getTransactionReference(),
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     */
    public function cancelled($order_id)
    {
        $order = Order::findOrFail($order_id);

        return redirect()->route('app.home', encrypt($order_id))->with([
            'message' => 'You have cancelled your recent PayPal payment !',
        ]);
    }

    /**
     * @param $order_id
     * @param $env
     */
    public function webhook($order_id, $env)
    {
        // to do with next blog post
    }
}

The above implementation only covers, make a payment, handle failed, canceled the payment. I have only shown basic stuff to get up and running, you are free to update as per your application requirements.

Thanks for reading up on the end!

If you have any feedback, any typo mistake in the post above please feel free to leave your comments below.

If you want to view the overall code on Github follow this link.

For the PayPal Instant Payment Notification support, please follow the link here to read my another blog post.

Note: The team from Omnipay recently released the new version v3.0 with support for symfony 3,4 components. Also, I published a new fresh blog post to explain the new changes in v3.0 with complete integration guide. 

Happy Coding!

]]>
<![CDATA[Laravel 5.4: Login with Username or Email]]> https://sujipthapa.co/blog/laravel-54-login-with-username-or-password cf953127-faa3-4ef6-9781-e460f16b3997 2017-03-12T11:02:00+05:45 Mr. Sujip Thapa I believe that Laravel is now well-known framework among PHP developers. I hope many of you may have already built an application using it. It is really popular due to its powerful features with expressive, beautiful syntax.

From this tutorial, we want to customize the default auth system in our Laravel 5.4 application to allow users to sign in using their username or email address. As of now Laravel framework only supports sign in with email only. After going through this tutorial your application will be able to allow sign in with both username or email, also you can easily extend this concept to any other version of Laravel framework.

 

Before moving to start the tutorial you should be ready with Laravel 5.4 setup on your machine.
Let's begin with creating authentication scaffolding, migrations for User model.

Run below command to generate authentication scaffolding.

php artisan make:auth

If you already have experience with Laravel, then you may know about auth system, which ships with framework out of the box.

If you want to start from scratch, delete the user's table from database/migrations directory and hit the following command to generate new migration file for the user's table.

php artisan make:migration create_users_table --create="users"

Now, we are adding username field to migration file.

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

Let's migrate the database and go ahead.

php artisan migrate

Now let's look at LoginController.php which implements AuthenticatesUsers.php trait. which also ships with framework out of the box.

If you look at the trait, which includes several methods only related to authenticating the user into the application. Now its time to deal with our actual requirement.

I am going to override the credentials() methods from AuthenticatesUsers.php trait to LoginController.php

Now, my LoginController.php looks like below.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
     */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'logout']);
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
            ? $this->username()
            : 'username';

        return [
            $field => $request->get($this->username()),
            'password' => $request->password,
        ];
    }
}

Also, I changed few things in my login.blade.php, the email input field to be the text for username support.

<input type="text" class="form-control" name="email" placeholder="E-Mail / Username" value="{{ old('email') }}" required autofocus>

Okay! now we have a functionality to allow login with username or email.

Last Updated: 10th, September 2017

 

If you would like to view updated new blog post to cover login/register with Laravel v5.5 here.

Conclusion

This tutorial heavily depends on Laravel 5.4, so if you are going to implement this on another version of framework please have a look at the traits used properly to override the credentials array.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Fixing - PHPUnit Util Deprecated Feature Logger]]> https://sujipthapa.co/blog/fixing-class-phpunit-util-deprecatedfeature-logger 8f954d6b-dddb-4073-9882-e678a564b5b6 2016-11-19T06:03:18+05:45 Mr. Sujip Thapa In this series, i am going to cover a common bug that developers face during running the PHP Unit Tests.

Keep in mind, this error occured while I was using phpunit, version line five.

composer global require "phpunit/phpunit"

Lately, I was working on my open source project on Github, which is an Omnipay based PHP Library for NAB Transact, and stuck with the Class PHPUnit_Util_DeprecatedFeature_Logger PHP Fatal error.

 

This post is to help someone else that tries it – and comes across the same issue like me.

PHP Fatal error: Class PHPUnit_Util_DeprecatedFeature_Logger contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PHPUnit_Framework_TestListener::addRiskyTest) in …vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php on line 202

My phpunit.xml.dist file was like below.

backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
colors="true"
syntaxCheck="false"> 
./tests/ 
./src

My fix was simple – it took some systematic editing of the phpunit.xml.dist file to figure it out.

The actual fix was simple – the problematical line for me was:

colors = "false"

I’ve said for a long time that “you don’t get paid the big bucks for knowing what to do – it’s for knowing how to fix it when you make the inevitable screw-ups”.

Now, when I run my PHPUnit tests it is working normally.

Thanks for reading up to the end.

Happy Coding!

]]>
<![CDATA[Laravel v4.2.20 , a special release.]]> https://sujipthapa.co/blog/laravel-v4220-a-special-release 8e97311a-f83c-4b1a-8352-befaa7d20d43 2016-10-27T13:45:17+05:45 Mr. Sujip Thapa I new version for Laravel v4.2.20 has been released. Yes!, you are absolutely correct, that new version for the Laravel's back version v4.2 is now released with support for PHP 7.0 and updates to the Symfony components to v2.7.

The v4 line of Laravel is already deprecated, but a lot of developers around the world are still using it and haven’t upgraded to the latest releases. This recent release allows them to move to PHP 7.0 and take new advantage of its speed improvements as well as security fixes without having to upgrade their app's code.

Ideally, those who are still on v4 version have plans to upgrade to v5 but the Laravel team understands that not every business has the luxury of being on the latest and greatest at all times.

 

There will always be an exception in their company to their typical policy of no new features on old versions of applications.

Post Credit: The original post was published on https://laravel-news.com and wanted to write few links to refer my readers.

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Hacktoberfest is back, #Hacktoberfest2016]]> https://sujipthapa.co/blog/hacktoberfest-is-back-hacktoberfest2016 df19b099-ce3f-4fea-a8ba-d0ec2c2b9a31 2016-09-29T13:25:00+05:45 Mr. Sujip Thapa Hey Developers !

Its time to celebrate an open source (#Hacktoberfest) in this October by participating with Hacktoberfest 2016. It is a month-long (festival of code) campaign to spread open source resources, which is organized by DigitalOcean and hosted on GitHub.

Rules for developers
To get a Hacktoberfest 2016 t-shirt, you must create four pull requests, starting October 1 - October 31 2016.

Image Courtesy Github

To participate, simply open a pull request and contribute to any open source project. You can fix a bug, add a feature, or even improve some documentation.

 

Don't forget to mention #hacktoberfest hashtag on Twitter, Facebook, or Instagram once you've made your pull requests.

After you create atleast four pull requests by October 31st, you'll get the satisfaction of sharing your code with the world—and a t-shirt, of course.

Last Updated : 26th, Oct 2016

Completed the Hacktoberfest challenge!

Happy Coding !

]]>
<![CDATA[An alternative way for PHP array_column]]> https://sujipthapa.co/blog/an-alternative-way-for-php-array-column f4a76036-1a42-47a5-ab34-766d889a0ac5 2016-09-29T05:45:16+05:45 Mr. Sujip Thapa As a developer i'm pretty much sure, most of the developers use latest version of PHP in their machine, so do I. 

The thing is, sometime with version of PHP on hosting servers, we have to think about the alternative way.

Here with this post I am going to share a pro tip for developers,  I learned when i also got stuck with.

 

Alternative way for to make it working on old version of PHP for method  array_column , this method is available only on (PHP 5 >= 5.5.0, PHP 7) see official doc 

$array = [
   ['developer' => ['name' => 'Sujip Thapa', 'age' => 25, 'expericence' => '+3 years']],
   ['developer' => ['name' => 'Aakash Gurung', 'age' => 25, 'expericence' => '+3 years']],
 ];


In PHP >=5.5 get all developers name only:

$developers = array_column($array, 'name');


In PHP < 5.5 - alternative way to get all developers name only:

$developers = array_map(function ($each) {

 return $each['name'];

 }, $array);


If you are a Laravel developer and using v4.2 you also can do like below, see official doc

$developers = array_fetch($array, 'name');


Output will be same:

$developers = ['Sujip Thapa', 'Aakash Gurung'];


Happy Coding !

]]>
<![CDATA[Laravel Cheat Sheet]]> https://sujipthapa.co/blog/laravel-cheat-sheet 3b7a72fd-098f-4c12-ad16-a63f9160cd72 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa Are you a PHP developer, working using the Lararvel framework?

If your answer is Yes, you are at the right place; here in this post, we are writing about the Laravel cheat sheet to quickly pick up the Laravel syntax for different features available in the Laravel ecosystem.

The cheat sheet guide is a complete reference for different versions of the framework.

The cheat sheet guide supports responsive design, so can be viewed in any devices.

Contribute to the project on github - https://github.com/summerblue/laravel5-cheatsheet

Open the cheat sheet - https://summerblue.github.io/laravel5-cheatsheet

Laravel Cheat Sheet - https://learninglaravel.net/cheatsheet/

Laravel Artisan Cheatsheet by @jbrooksuk a developer at Laravel.

https://artisan.page

Last Updated: 19th Mar, 2022

Happy Coding!

]]>
<![CDATA[Setup Mailgun with Digital Ocean]]> https://sujipthapa.co/blog/setup-mailgun-with-digital-ocean 0ae0db81-44f8-4a48-99b2-a3cae2cc43ab 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa One big panic thing is managing and having to set up the email server of own. It requires a lot of technical things, resources, pricing, security prerequisites and many more. A lot of modern, secure, reliable mail service providers are up in the tech market these days.

Here, in this blog post, we are going to set up a renowned mail service provider to work your project with low hassle. The mail service provider I'm setting up is Mailgun.

I use Digital Ocean as a primary hosting partner for the Laravel projects I work for my clients. We are going to setup Mailgun with Digital Ocean platform to send emails from the web application. In the previous years, Mailgun used to work without doing any configuration side of things to Digital Ocean end. In the following years, they want us the verify the ownership to use their service with any third party application.

Before following this article, I will assume you have following services available to you if you would like to implement the step by step guide.

Prerequisites

  • Register for Digital Ocean, create a droplet, install the platform you want to run your application.
  • Register your account with Mailgun

Before starting let's understand some benefits of using Mailgun.

  • Exciting thing up to now is, it gives you 10,000 emails free every month, no payment hassle for really small to medium-sized companies.
  • Reliable & Powerful APIs that facilitates to send, receive, track your emails seamlessly.
    Trusted by the tech giants in the industry, so kind of no compromise in security side.
  • If your business needs more than the free emails provided by the service provider, you can choose their simple pricing method as per your requirement. The pricing is like pay as you go, as they give you interface to calculate the price based on your needs for your monthly volume of emails you want.

There are a lot of competitors to this service providing the same quality of email services. I am using Mailgun for long with my projects, so due to its simplicity and reliability, I am considering to use it furthermore for now.

We are not going through any large server setup in this article. We are just going to look at the Networking part under the Digital Ocean account to manage a few DNS configuration to make emails stuff work with Mailgun.

Mailgun
After your successful registration with Mailgun service, the first step is to go to the Domains tab. For the fresh new account, you will see one record saying sandbox domain, which allows testing the development phase emails of your application before going to production.

Now, our concern is to setup emails environment for the production application.

Add Mailgun DomainGo through the steps below to add a new domain.

  • Click on the Add New Domain button and enter your production domain name in which you are going to use this Mailgun email service.
  • After adding your production domain, it will appear in the list under the Domains tab.
  • Click on the domain name from the list to view the email service configurations to be added to Digital Ocean service.
  • After clicking your domain from the list, you will see a page with a lot of details, and configuration that can be changed or managed to make the email service up and running.

Review the following headings on the page based on your need according to your application context.

  • Domain Information
  • Domain Settings
  • Tracking Settings
  • Security Settings For Outgoing Mail
  • IP Management
  • Domain Verification & DNS

In this article, we are going to look more in-depth about Domain Verification & DNS.

Before you add a few DNS configuration to the hosting partner end, the Mailgun shows the status of your domain as Unverified.

Note: I am taking Digital Ocean as my hosting partner, but not only Digital Ocean, this article works for any hosting provider which gives you access to manage DNS settings from their user panel based on what type of plan you choose from them.

After completing the above few things in the Mailgun end, we now have to log in into the hosting partner, for me its Digital Ocean.

Digital Ocean

After logging into digital Ocean dashboard, navigate to the Networking tab. It shows you the list of domains attached with droplets you own for your applications.

If you are going to manage the new domain, add it to the list, or select the existing one from the list.

Digital Ocean DNS PanelWe have to add a few DNS records like TXT, MX, CNAME to verify the Mailgun domain form the Digital Ocean DNS management panel.

Add following records under the Networking > yourdomain.com.

  • A record should always point to your droplet's IP Address, not the IP Address given by previous Mailgun configuration page.
  • While adding TXT record given by Mailgun, wrap them in double quotes before saving in Digital Ocean end.
  • TXT, MX, CNAME should be the correct value from Mailgun.

Mailgin Domain VerificationAfter all setup, you have to lastly go to Mailgun, under Domain Verification & DNS click on Check DNS Record Now, to verify the domain configuration page.

Usage

If you are using Mailgun with Laravel, you have to update your .env file or config/mail.php to use mailgun driver, and config/services.php

'mailgun' => [
    'domain' => 'your-mailgun-domain',
    'secret' => 'your-mailgun-api-key',
],


To get the API Key, select the domain from the Domains list, and you will see it on that page.

Testing

To test your production emails, now perform some email sending functionality, the emails log will appear in the Mailgun dashboard as processed, delivered, dropped and so on.

Conclusion

Thanks for reading this article up to the end. If you have any queries on setting up Mailgun, feel free to ask via comment. I will try to reply or even help for other hosting partners including Digital Ocean.

Last Updated: 7th, October 2018

Happy Coding!

]]>
<![CDATA[Install Atom Editor on Ubunto 16.04]]> https://sujipthapa.co/blog/install-atom-editor-on-ubunto-1604 ecd84524-9e1f-4fcb-b6c0-79c1d8fcc26e 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa Press Ctrl+Alt +T open up your teminal, please type these commands.

sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom

Enjoy!

]]>
<![CDATA[Linux: How to show current git branch in Bash prompt]]> https://sujipthapa.co/blog/linux-how-to-show-current-git-branch-in-bash-prompt 6c4edbe0-ad35-43d8-aeab-1e2e81717f55 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa The bash prompt does not show the current active git branch on the project root.

So adding a way to display the active git branch can help developers avoid the branching problems and code conflict with other developers some time.

In this post, you will get a chance to learn how you can show your active git branch on your Linux terminal on the bash prompt.

Open up your terminal and hit cd ~ to stay on the root. Open up the .bashrc file from there.

Example:

nano ~/.bashrc

Scroll to the place where you want to paste the content.

Pickup the below snippet and paste into the .bashrc file and save it.

parse_git_branch() {
     git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\u@\h \[\033[32m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "

Then, save the file and exit from the edit mode.

After the update, you need to restart the terminal or source, and it should start working for you.

Happy Coding!

I hope this was helpful for you, and feel free to share it with other developers around you.

Feel free to share the article on social media to share the knowledge among the developers in your circle.

Last Updated: April 26, 2022

 

 

]]>