Let's build a super simple referral system with Laravel
Tags: php, laravel
Published: 2019-08-06 10:08:11

Sometime ago, I was asked to build a referral system as part of the feature set of a Laravel web application. Referral systems were not new to me but it was my first time building one. Referral systems are used to encourage users of an application to invite other users and get rewarded when people register for that application using their referral link.

I really wanted to keep things super simple but most of what I read online about referral systems were either too complicated or complex for my use case. After some tinkering around, I came up with something so simple I’m surprised it works. Together, we are going to build a referral system and examine some decisions and trade-offs we will have to make along the way.
  
To start, create a new Laravel project. I’ll name mine `simple-referral`. Allow composer or the laravel installer do its magic and you should have a barebones Laravel application at your fingertips. Configure the application database, run `php artisan make:auth` and let’s get cracking.

A referral flow looks something like this
1. A user clicks a link that takes them to the application register page.
2. When they register, their registration should be linked to the user whose referral link they used in registering.
3. The user whose referral link was used to register should get notified that someone registered using their referral link.
4. The new user should have his or her own referral link.

We could use this scenario to model our eloquent relationships which would look like the following
- A user can be referred by another user.
- A user can refer many users.

In essence, we have a `One-To-Many` relationship. Open the `create_users_table` migration and make the change below.

```php
$table->unsignedBigInteger('referrer_id');
```

But wait! Not all users will have a referrer, so let’s make the referrer column `nullable`. While at it, let's also add a `foreign_key` constraint to the `users` table. `create_users_table` migration should now look like this.

```php
    $table->bigIncrements('id');
    $table->unsignedBigInteger('referrer_id')->nullable();
    $table->foreign('referrer_id')->references('id')->on('users');
    $table->string('name');    
    $table->string('email')->unique();            
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();   
```
Migrate the database and make the changes below in `User.php`. In case you didn’t know, you just set up a self-referencing table.

```php

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

/**
 * A user has a referrer.
 *
 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
 */
public function referrer()
{
    return $this->belongsTo(User::class, 'referrer_id', 'id');
}

/**
 * A user has many referrals.
 *
 * @return \Illuminate\Database\Eloquent\Relations\HasMany
 */
public function referrals()
{
    return $this->hasMany(User::class, 'referrer_id', 'id');
}
```
#### Now let's think about referral links and how we want our referral links to look like.

For the application I was building, one of the business requirements was that users have a referral link that could be easily remembered. Over time, I have learned that URLs that can be remembered easily are always short. There are exceptions to this but as I said, they are exceptions. After some back and forth, we agreed that referral links should look like this `https://simple-referral/register?ref=username`. This would require users to have a `unique`, `unchanging` username.

To satisfy this business requirement, amend `create_users_table` migration like so
```php
//...
$table->string('username')->unique();
```
Then open `User.php` and make the following changes

```php
protected $fillable = [
    // ...
    'username',
];

/**
 * The accessors to append to the model's array form.
 *
 * @var array
 */
protected $appends = ['referral_link'];

/**
 * Get the user's referral link.
 *
 * @return string
 */
public function getReferralLinkAttribute()
{
    return $this->referral_link = route('register', ['ref' => $this->username]);
}

```
Rather than storing the user's referral link in the database, we are using an accessor to compute it. Then we append the attribute to the User model's array or JSON form. To read more about accessors and eloquent serialization, click [here](https://laravel.com/docs/5.8/eloquent-mutators#defining-an-accessor) and [here](https://laravel.com/docs/5.8/eloquent-serialization#appending-values-to-json).

Your requirements for a referral link might be different and here is another option you can consider `https://simple-referral/register?ref=referral_token`. In this case, edit `create_users_table` migration like so
 ```php
 //...
 $table->string('referral_token')->unique();
 ```
 Then open `User.php` and
 - Add `referral_token` to the fillable fields.
 - Append `referral_link` to the model's array or JSON form.
 - Compute the referral link attribute using the user's `referral_token` instead of the `username`.
 
This method would allow users to have editable usernames but you would have to think of how to generate unique referral tokens for your users. I'll leave you to figure that out.

The User model has been prepped, and the database structure supports our referral system; let's move on to our Controller!

Open `App\Http\Controllers\Auth\RegisterController.php`. The Controller uses `Illuminate\Foundation\Auth\RegistersUsers` trait to perform most of it's functions meaning we can override the methods of the trait in the Controller.

```php
/**
 * Show the application registration form.
 *
 * @return \Illuminate\Http\Response
 */
public function showRegistrationForm(Request $request)
{
    if ($request->has('ref')) {
        session(['referrer' => $request->query('ref')]);
    }

    return view('auth.register');
}

/**
 * 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', 'unique:users', 'alpha_dash', 'min:3', 'max:30'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
    ]);
}

/**
 * Create a new user instance after a valid registration.
 *
 * @param array $data
 *
 * @return \App\Models\User
 */
protected function create(array $data)
{
    $referrer = User::whereUsername(session()->pull('referrer'))->first();

    return User::create([
        'name'        => $data['name'],
        'username'    => $data['username'],
        'email'       => $data['email'],
        'referrer_id' => $referrer ? $referrer->id : null,
        'password'    => Hash::make($data['password']),
    ]);
}
```
It looks like a lot but we are only doing three things
* Set a session variable named 'referrer' if the register route contains a `ref` query parameter.
* Form field validation before creating the user.
* Retrieve the referrer from the session when creating a user then set its id as the referrer_id of the user being created.

To complete our simple referral system, users should get a notification when new users register using their referral link. To be as flexible and powerful as possible, I'll be using a `Notification` instead of a `Mailable`. Run `php artisan make:notification nameOfTheNotification` to generate a Notification. I'll be naming mine `ReferralBonus`. I'll leave you to decide the channel(s) through which the notification will be sent.

Open `RegisterController.php` and override the `registered` method like so

```php
/**
 * The user has been registered.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  mixed  $user
 * @return mixed
 */
protected function registered(Request $request, $user)
{
    if ($user->referrer !== null) {
        Notification::send($user->referrer, new ReferrerBonus($user));
    }

    return redirect($this->redirectPath());
}
```
Here we send a notification to the newly registered user if it has a referrer. We then redirect to the redirect path defined on the `RegisterController`. **Note: Do not forget to import all necessary classes.**

Now let's deal with out front-end. Open `register.blade.php` and add a form input field for username.

```
//...
<div class="form-group row">
    <label for="name" 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 @error('name') is-invalid @enderror" name="username" value="{{ old('username') }}" required autocomplete="username">

        @error('username')
            <span class="invalid-feedback" role="alert">
                <strong>{{ $message }}</strong>
            </span>
        @enderror
    </div>
</div>
```
Now, let's modify `home.blade.php` to display some user information

```blade
<div class="card-body">
    @if (session('status'))
        <div class="alert alert-success" role="alert">
            {{ session('status') }}
        </div>
    @endif

    You are logged in!

    <ul class="list-group mt-3">
        <li class="list-group-item">Username: {{ Auth::user()->username }}</li>
        <li class="list-group-item">Email: {{ Auth::user()->email }}</li>
        <li class="list-group-item">Referral link: {{ Auth::user()->referral_link }}</li>
        <li class="list-group-item">Referrer: {{ Auth::user()->referrer->name ?? 'Not Specified' }}</li>
        <li class="list-group-item">Refferal count: {{ count(Auth::user()->referrals)  ?? '0' }}</li>
    </ul>
</div>
```
Our simple referral system is now complete. In case you are having challenges following along, you can browse through the repository I created for this tutorial on Github {% github simioluwatomi/simple-referral %}