If you reading this you probably already know what referral is, so I'm not explaining that. But I have a drawing of it :)
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 you to easily decide how will user benefit after referring other user to 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
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. Fieldlifetime_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']; }
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. Factory method
getReferral
is creating single referral link for each user for a given program.Method
relationships
will be used later to store relation between users who initiated referral and ones that accepted it.<?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 required return $this->belongsTo(ReferralProgram::class, 'referral_program_id'); } public function relationships() { return $this->hasMany(ReferralRelationship::class); } }
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.
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'=>'Sign-up Bonus', '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 to capture ref
get parameter from referral link 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
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 cookie.
Rewarding Users
After desired action is triggered (new sign-up) we need to make sure that users are rewarded for using referral program. To decouple code for normal flow of application and code which will reward users we can use event broadcasting. And then we just need to fire event and listener will to the rest.
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 === 'Sign-up Bonus') {
// 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