Building Referral System with Laravel

If you reading this you probably already know what referral is, so I'm not explaing that. But I have a drawing of it :)

Laravel Referral System

This tutorial will show how to build a system to support referral programs. It allows you to track links, programs (you may want to have more than one program) and for how long will cookie last. Also allows to easily decide how to reward users after referred user triggers an action, for example sign up.

Note: In my projects I'm importing used classes on top of the file. In this tutorial I'm using longer version of every class to make things clearer. For example instead of:

new \App\User();

usually I would do this:

use App\User;
// ...
new User();

I tried to make it kind of modular, not intrusive so old code will be almost untouched.

Referral System Model

  1. We need ReferralProgram model which will represent a single referral campaign. For example, invite friend to register and win free credits. Referral program model will have a name and uri to where link will point. Field lifetime_minutes will define for how long will cookie last. Default will be 7 days.

    php artisan make:model ReferralProgram --migration
    
    Schema::create('referral_programs', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('uri');
        $table->integer('lifetime_minutes')->default(7 * 24 * 60);
        $table->timestamps();
    });
    
    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class ReferralProgram extends Model
    {
        protected $fillable = ['name', 'uri', 'lifetime_minutes'];
    }
    
  2. Referral Link is a link which user will share to get benefits for particular program.

    php artisan make:model ReferralLink --migration
    
    Schema::create('referral_links', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->unsigned();
        $table->integer('referral_program_id')->unsigned();
        $table->string('code', 36)->index();
        $table->unique(['referral_program_id', 'user_id']);
        $table->timestamps();
    });
    

    Install uuid package for generating unique IDs. Run composer require ramsey/uuid.

    When we create referral link for user we are going to generate unique referral code as well. This code is used to track the link and relate it to original user who shared it. Each user can be involved in program using a single link for that program. Builder method getReferral is creating only one referral link for each user/program pair.

    Relationships method is for relation between models used in the next step.

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    use Ramsey\Uuid\Uuid;
    
    class ReferralLink extends Model
    {
        protected $fillable = ['user_id', 'referral_program_id'];
    
        protected static function boot()
        {
            static::creating(function (ReferralLink $model) {
                $model->generateCode();
            });
        }
    
        private function generateCode()
        {
            $this->code = (string)Uuid::uuid1();
        }
    
        public static function getReferral($user, $program)
        {
            return static::firstOrCreate([
                'user_id' => $user->id,
                'referral_program_id' => $program->id
            ]);
        }
    
        public function getLinkAttribute()
        {
            return url($this->program->uri) . '?ref=' . $this->code;
        }
    
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
        public function program()
        {
            // TODO: Check if second argument is requried
            return $this->belongsTo(ReferralProgram::class, 'referral_program_id');
        }
    
        public function relationships()
        {
            return $this->hasMany(ReferralRelationship::class);
        }
    
    }
    
  3. ReferralRelationship is a model which will store a relationship between user providing referral link and user using it.

    php artisan make:model ReferralRelationship --migration
    
    Schema::create('referral_relationships', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('referral_link_id');
        $table->integer('user_id');
        $table->timestamps();
    });
    
    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class ReferralRelationship extends Model
    {
        protected $fillable = ['referral_link_id', 'user_id'];   
    }
    

Displaying Referrals

Until now referral code is separated from the rest of the application. Here for the first time User model is introduced to Referral concept. This can be also decoupled, depending on your preferences, for simplicity I use it this way.

List Referral links

To get links for every referral program available, add this method to User model:

public function getReferrals()
{
    return ReferralProgram::all()->map(function ($program) {
        return ReferralLink::getReferral($this, $program);
    });
}

And list available referrals to user with this template:

@forelse(auth()->user()->getReferrals() as $referral)
    <h4>
        {{ $referral->program->name }}
    </h4>
    <code>
        {{ $referral->link }}
    </code>
    <p>
        Number of referred users: {{ $referral->relationships()->count() }}
    </p>
@empty
    No referrals
@endforelse 

Now generate tables

php artisan migrate

And create first referral from tinker console

php artisan tinker

App\ReferralProgram::create(['name'=>'register', 'uri' => 'register']);

You can create UI for creating new referral campaigns, and for editing. I didn't have need to create these too often.

If you have requirement to have only one referral program for whole application, then you can create database seed to create initial referral program.

Handling Referral Links

Now we need somehow capture ref parameter and store it in cookie, so when desired user action is triggered you can reward both participants.

In order to capture referral link code regardless of the page user lands on we will create a middleware:

php artisan make:middleware StoreReferralCode

This solution is not handling multiple referrals at the same time. Because cookie is overriding previous one.

We will check for ref get parameter and put it into cookie for predefined time.

use App\ReferralLink;

// ...

public function handle($request, Closure $next)
{
    $response = $next($request);

    if ($request->has('ref')){
        $referral = ReferralLink::whereCode($request->get('ref'))->first();
        $response->cookie('ref', $referral->id, $referral->lifetime_minutes);
    }

    return $response;
}

In app/Http/Kernel.php file add new middleware to the web group:

'web' => [
    // ...
    \App\Http\Middleware\StoreReferralCode::class,
]

Now application is able to capture referral links and store them in a cookies.

Rewarding Users

After desired action is triggered (user registration) we need to make sure that users are rewarded for using referral program. To decouple code which is doing it's thing like before (registering user) and code which will reward users we can use even broadcasting. This way we just need to fire an event and listen for it.

In event provider app/Providers/EventServiceProvider.php define event and listener under $listen property.

'App\Events\UserReferred' => [
    'App\Listeners\RewardUser',
],

and type

php artisan event:generate

Now open new event and add user and referral code to the event.

class UserReferred
{
    use SerializesModels;

    public $referralId;
    public $user;

    public function __construct($referralId, $user)
    {
        $this->referralId = $referralId;
        $this->user = $user;
    }


    public function broadcastOn()
    {
        return [];
    }
}

To broadcast event just add following at appropriate place. For example under create method of RegisterController.

protected function create(array $data)
{

    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => bcrypt($data['password']),
    ]);

    event(new \App\Events\UserReferred(request()->cookie('ref'), $user));

    return $user;
}

And any custom logic of rewarding user in the app/Listeners/RewardUser.php listener. First we are going to store referral relationship, and handle any other special behavior.

$referral = \App\ReferralLink::find($event->referralId);
if (!is_null($referral)) {
    \App\ReferralRelationship::create(['referral_link_id' => $referral->id, 'user_id' => $event->user->id]);

    // Example...
    if ($referral->program->name === 'Bonus Credits') {
        // User who was sharing link
        $provider = $referral->user;
        $provider->addCredits(15);
        // User who used the link
        $user = $event->user;
        $user->addCredits(20);
    }

}
  • TODO: store cookies in array so multiple cookies at the same time are possible

Author

I plan to write more articles about common laravel components. If you are interested let’s stay in touch.
comments powered by Disqus