Building a Referral System with Laravel

I recently launched a small product for Amazon Affiliates built with Laravel. At the heart of the application is a referral system. Users get bonus credits when they refer another user while getting recurring credit every time their referral uses the site. To tackle this the Laravel way, I got to use Requests, Middleware, and Cookies. I also integrated Google login using Socialite but that’s another post entirely.

Side Note For Context: Since Amazon Affiliates cannot use their own affiliate codes for purchases they make themselves, I built Associate Swap. When you search Swap for Amazon products it will grab a random Affiliate code from the database and add it to the URL. The more you use swap and refer other affiliates to swap the greater chance your code will be picked. The referral system is the heart of the growth and retention model.

Referral Use Case

When a user signs up, they are automatically generated a unique affiliate_id. This id is will be used to generate unique URLs they can share to be credited for another user signing up and using the site – ie https://associateswap.com/?ref=17FbCjzIzj.

Getting Started

I’m going to assume you’ve already scaffolded your Authentication using the make:auth artisan command.

$ php artisan make:auth

Using this command you will now have a User model and associated Migrations. In order to track who referred who we need to add a couple of fields to our User object and associated database table. Time to generate a Migration.

$ php artisan make:migration AddAffiliateTrackingToUsersTable

Open up the migration file that was just generated, and add the fields we need to track users in the referral system.

<?php

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

class AddAffiliateTrackingToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function(Blueprint $table)
        {
            $table->string('referred_by')->nullable();
            $table->string('affiliate_id')->unique();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function(Blueprint $table)
        {
            $table->dropColumn('referred_by');
            $table->dropColumn('affiliate_id');
        });
    }
}

Now is a good time to run the Migrations on your database… If you haven’t run this since make:auth then it will create the User Model and Table. And with your additional Migration it will add your new fields referred_by and affiliate_id.

$ php artisan migrate

If you stop reading now and skip ahead you are going to run into an annoying error. If you jump to the Cookie generation and the Middleware part of the post (I mean c’mon who can’t wait until the Middleware section) you are going to bypass a commonly missed Laravel step. Update your model to make your new fields fillable which I’ve forgotten on every project.

That should look something like this…

<?php

namespace App;

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

class User extends Authenticatable
{
    use Notifiable;

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

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

Cool. Now what? Now we need to generate that new affiliate_id when someone registers on the site. You probably have a file called RegisterController that make:auth scaffolds for you. In there should be a create method which you’ll want to change to auto generate an affiliate code for that user.

protected function create(array $data) {
    return User::create([
        'email' => $data['email'],
        'password' => bcrypt($data['password']),
        'affiliate_id' => str_random(10),
    ]);
}

For brevity, we’ve just updated this to generate a random string for the affiliate_id field. This has a unique database constraint but you should probably do layer of validation here. If it was me I’d built an affiliate id helper function to generate a suitably unique string here that doesn’t toss a DB constraint error in your user’s faces. Just sayin.

Alright now we are rolling – now how do we track when someone uses your code to register? Here’s where it starts getting fun. We want to give the referrer credit when someone uses their unique URL in registration. First off – we need to tell them what URL to share. Lets update a Blade template.

@if(!Auth::user()->affiliate_id)
    <input type="text" readonly="readonly" 
           value="{{url('/').'/?ref='.Auth::user()->affiliate_id}}">
@endif

Now your user can copy and paste the URL they need to share to get credit.

You’ve reached the fun part. It’s time to play with Laravel Middleware.

Middleware provide a convenient mechanism for filtering HTTP requests entering your application. For example, Laravel includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

Scaffolding a Middleware is another handy artisan command.

$ php artisan make:middleware CheckReferral

The CheckReferral middleware’s job is to inspect every HTTP request to the application to see if it has the ?ref query string in the URL. In a more complex application this would allow your referrer to link a potential signup to any URL on the site (whether it be your signup page, pricing page, homepage, etc).

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Response;
use Closure;

class CheckReferral
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if( $request->hasCookie('referral')) {
            return $next($request);
        }
        else {
            if( $request->query('ref') ) {
                return redirect($request->fullUrl())->withCookie(cookie()->forever('referral', $request->query('ref')));
            }
        }

        return $next($request);
    }
}

There’s an affiliate industry standard of persisting referrals in a cookie so there’s an increased chance of getting credit for the conversion. For Amazon Affiliates, the cookie expires in 24 hours. For Associate Swap it’s a forever cookie. First step in the Middleware code above is to check if the cookie already exists and move on returning the Request if it does.

If the cookie doesn’t already exist but the ref parameter exists in the query string we return the Request object as normal while attaching our referral Cookie to it.

Side Note: Building Middleware in Laravel is an excellent example of utilizing the simplicity of the Laravel Service Container – ie dependency injection.

We aren’t done yet, though. The referrer is still not getting credit. Back to the RegisterController.

<?php

namespace App\Http\Controllers\Auth;

use DB;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Cookie;

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, [
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
            'tracking_id' => 'required'
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    protected function create(array $data)
    {
        $referred_by = Cookie::get('referral');

        return User::create([
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'affiliate_id' => str_random(10),
            'referred_by'   => $referred_by
        ]);
    }
}

Now when we create the user in RegistrationController we check for that Cookie. If it has a value it gets inserted into our database. Otherwise the column gets a null value as we determined in our original migration.

Your use case might award affiliates differently so I wont go into details on how credits are applied on Associate Swap. But now you can track if a user was referred by another user. If you are using Laravel Cashier you may hook into a Stripe Webhook to pay out an affiliate in realtime after a sale is made by someone they referred. For me it was simply incrementing a counting mechanism every time they perform an action in the web application.

 

Building A Slack Bot With Node.js And WordPress REST API

Some of my first experiments in the world of building a slack bot was to develop a basic Slash command using the WordPress REST API as a backend. If you or your team are the only users that intend to use it, you can just set it up as a simple integration. However if you wish to distribute it for others to use you can package it into a Slack bot.

An advantage of building a Slack bot versus a slash command is the additional features of the Slack API you can leverage – in this example, the RTM (Real Time Messaging) API. That allows you to listen and respond to messages in real-time. I started out building this layer as a WordPress plugin so anyone could install the plugin and build a WP powered bot. Not only was that out of scope for a fun side project, Node is really the right tool for the job – asynchronous, great packages for the Slack API, easy to deploy and scale (because who doesn’t wan’t a Kramer bot).

Bot Slack RTM API
Kramer Bot using Slack RTM API

Continue reading Building A Slack Bot With Node.js And WordPress REST API

Slack Slash Command With WordPress REST API Backend

I’ve spent the last several weeks building Slack bots and other custom integrations. One of the simplest types of Slack integration to build is the Slash Command – which can be a way for a user to interact with a bot or to provide functionality to a team or channel. Slack provides built in commands (like /topic and /remind) or you can develop custom commands either through a Slack app or just a one off implementation.

Setting up a slash command is pretty easy, because all you need to provide is a RESTful endpoint that returns data to post to a channel.

Slack Slash Command
Hey Kramer bot slash command integration

Slack will do a POST or GET to your endpoint with either JSON or query string parameters giving you information about the command that was given.

Slash Command POST Data
Slash Command POST Data

And your endpoint can simply return a string to send back to the user who initiated the slash command. Or you can send back a formatted message which is essentially a JSON package with the message to send back and some options along with it.

Kramer Slack Bot

This is where WordPress and its REST API come in. This is also where WordPress starts to become a platform. I’m not designing a theme or even building a website with WordPress. I’m creating a RESTful API with a nice simple way for users to enter data. In less than 5 minutes you can have a fully functional API outputting JSON via RESTful URLs backed by an easy to use and extend CMS.

Out of the box you have access to all data in your WP backend with a simple URL schema…. ie ‘/posts’ get a paged list of posts, ‘/posts/{id}’ to get a specific post. Here is an example from the Seinfeld API I am developing for the Kramer bot. Notice the URL structure: https://heykramer.com/wp-json/wp/v2/posts

WordPress REST API JSON Output
WordPress REST API JSON Output

Great! But not so fast … Slack is expecting something specific back. Well thankfully the REST API is easily extendible. Below is an example plugin I created to show how easy it is to extend the WP REST API to provide Slack what it needs back. (here it is on Github

To extend WP REST API, I register a custom endpoint and define a callback function to execute when that URL is requested. In this instance we are expecting a GET request with query string parameters from Slack. It expects one of two commands: gif or image. Based on the command, it will query different categories of content on WP backend and return one random result to be posted in the Slack channel where the slash command was initiated.

Kramer Bot Responds To Slash Command

Next up – building out a full Slack bot on NodeJS with a WordPress backend…

WordPress Single Page Template For Categories

If you are familiar with building WordPress themes you’ve likely caught onto the naming convention used for page templates. These conventions are things like ‘page-{slug}.php’, ‘single-{slug}.php’, and ‘category-{slug}.php’.

The conventions are very handy and I wanted something similar for posts in a category. This method is much cleaner than having conditional logic all over your ‘single.php’ file to do have different design elements or functionality for posts in different categories.

A simple one-liner to drop into your theme’s functions file does the trick…

Now, you can create ‘single-news.php’ for a post in the News category and ‘single-blog.php’ for a post in the Blog category and have completely different designs and layouts.