Support the ongoing development of Laravel.io →

Develop Redeem Code API With Laravel For Mobile App/Game

Richard Fu

29 min read
1

Have you ever wanted to add a redeem code feature to your game or app? A redeem code system is always useful for rewarding your users and boost user retention in events, or compensate the angry users for any particular bugs. While there are quite a few free or paid third-party solutions on the market, but making your own gives you more control and customized features as you would like. This article will walk you through how to create API calls and simple console pages with Laravel step-by-step.

Laravel Redeem Code API App/Game

TLDR; You can use review the code or simply install my Laravel Redeem Code package.

Database migrations

By using Laravel's database migrations, we need 4 databases as below:

Events table

First, we create an events table, each event can have multiple redeem codes. Few event examples are: 1-st anniversary giveaway, login bug compensation, etc.

php artisan make:migration create_events_table

/database/migrations/{datetime}_create_events_table.php:

<?php

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

class CreateEventsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('events', function(Blueprint $table) {
            $table->increments('id');

            $table->tinyInteger('type')->unsigned()->default('0');
            $table->string('name', 127)->nullable();
            $table->date('start_at')->nullable();
            $table->date('end_at')->nullable();

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('events');
    }
}
  • name - the name of the event, for internal console display only.
  • start_at, end_at - used to redeem codes only within the valid date.

Redeem Codes table

Next, we'll have the redeem_codes table, of course:

php artisan make:migration create_redeem_codes_table

/database/migrations/{datetime}_create_redeem_codes_table.php:

<?php

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

class CreateRedeemCodesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('redeem_codes', function(Blueprint $table) {
            $table->increments('id');

            $table->integer('event_id')->nullable();
            $table->string('code', 12);
            $table->boolean('reusable')->default(false);
            $table->boolean('redeemed')->default(false);

            $table->timestamps();
            
            $table->index(['code']);
            $table->foreign('event_id')->references('id')->on('events')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('redeem_codes');
    }
}
  • code - we've set the redeem code fixed length of 12.
  • reusable - a redeem code can be reusable and can be used by multiple users, default no.
  • redeemed - set the true if a user redeemed the code. A reusable code will never be set to redeemed.

Redeem Code Rewards table

Then the redeem_codes table is for the rewards of the redeem codes:

php artisan make:migration create_redeem_code_rewards_table

/database/migrations/{datetime}_create_redeem_code_rewards_table.php:

<?php

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

class CreateRedeemCodesRewardsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('redeem_code_rewards', function(Blueprint $table) {
            $table->increments('id');

            $table->integer('redeem_code_id')->unsigned()->nullable();
            $table->integer('event_id')->unsigned()->nullable();
            $table->tinyInteger('type')->unsigned()->default('1');
            $table->integer('amount')->unsigned()->default('1');
            $table->integer('item_id')->unsigned()->nullable();

            $table->timestamps();

            $table->foreign('redeem_code_id')->references('id')->on('redeem_codes')->onDelete('cascade');
            $table->foreign('event_id')->references('id')->on('events')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('redeem_code_rewards');
    }
}
  • type - the reward type in integer, e.g. can be coins, gems or whatever in your app.
  • amount - the reward amount.
  • item_id - optional, used for giving a specific item, such as weapon ID.

Redeem Code Histories table

Last, we have a redeem_code_histories table. It's optional for keeping track the redeeming record:

php artisan make:migration create_redeem_code_histories_table

/app/database/migrations/{datetime}_create_redeem_code_histories_table.php:

<?php

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

class CreateRedeemCodeHistoriesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('redeem_code_histories', function(Blueprint $table) {
            $table->increments('id');
            $table->integer('redeem_code_id')->unsigned()->index();
            $table->string('ip', 15);
            $table->text('agent');
            $table->timestamps();
            
            $table->foreign('redeem_code_id')->references('id')->on('redeem_codes')->onDelete('cascade');
        });
    }

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

Models

Then, we simply map each database table as its own Laravel Eloquent model:

Event model

php artisan make:model Event

/app/Models/Event.php:

<?php

namespace Furic\RedeemCodes\Models;

use Illuminate\Database\Eloquent\Model;

class Event extends Model
{

    protected $fillable = ['type', 'name', 'started_at', 'ended_at'];

    public function redeemCodes()
    {
        return $this->hasMany('Furic\RedeemCodes\Models\RedeemCode');
    }

    public function redeemCodeRewards()
    {
        return $this->hasMany('Furic\RedeemCodes\Models\RedeemCodeReward');
    }

}

Event has hasMany relationship to both RedeemCode and RedeemCodeReward models.

RedeemCode model

php artisan make:model RedeemCode

/app/Models/RedeemCode.php:

<?php

namespace Furic\RedeemCodes\Models;

use Illuminate\Database\Eloquent\Model;

class RedeemCode extends Model
{

    protected $fillable = ['event_id', 'code', 'redeemed', 'reusable'];
    protected $hidden = ['created_at', 'updated_at'];
    protected $appends = ['rewards'];
    protected $casts = ['redeemed' => 'boolean', 'reusable' => 'boolean'];

    public static function findByCode($code)
    {
        return SELF::where('code', $code)->first();
    }

    public function event()
    {
        return $this->belongsTo('Furic\RedeemCodes\Models\Event');
    }
    
    public function rewards()
    {
        if ($this->event != null) {
            return $this->event->hasMany('Furic\RedeemCodes\Models\RedeemCodeReward');
        } else {
            return $this->hasMany('Furic\RedeemCodes\Models\RedeemCodeReward');
        }
    }
    
    public function getRewardsAttribute()
    {
        return $this->rewards()->get();
    }

    public function setRedeemed()
    {
        $this->redeemed = true;
        $this->save();
    }

}

RedeemCode has belongsTo relationship to Event model, and hasMany relationship to RedeemCodeReward model. Note that a redeem code can be created without an event, and simply contains its redeem rewards.

RedeemCodeReward model

php artisan make:model RedeemCodeReward

/app/Models/RedeemCodeReward.php:

<?php

namespace Furic\RedeemCodes\Models;

use Illuminate\Database\Eloquent\Model;

class RedeemCodeReward extends Model
{

    protected $fillable = ['redeem_code_id', 'type', 'amount', 'item_id'];
    protected $visible = ['type', 'amount', 'item_id'];

    public function redeemCode()
    {
        return $this->belongsTo('Furic\RedeemCodes\Models\RedeemCode');
    }

    public function event()
    {
        return $this->belongsTo('Furic\RedeemCodes\Models\Event');
    }
    
}

RedeemCodeReward model simply has belongsTo relationship to both RedeemCode and Event models.

RedeemCodeHistory model

php artisan make:model RedeemCodeHistory

/app/Models/RedeemCodeHistory.php:

<?php

namespace Furic\RedeemCodes\Models;

use Illuminate\Database\Eloquent\Model;

class RedeemCodeHistory extends Model
{

    protected $fillable = ['redeem_code_id', 'ip', 'agent'];

    public function redeemCode()
    {
        return $this->belongsTo('Furic\RedeemCodes\Models\RedeemCode');
    }

}

Lastly, RedeemCodeHistory model belongsTo RedeemCode model.

Routing

We simply has two routes in Laravel Routing, one for API and one for our admin console web.

API route

In /routes/api.php, add:

<?php

use Illuminate\Support\Facades\Route;
use Furic\RedeemCodes\Http\Controllers\RedeemController;

Route::prefix('api')->group(function() {
    Route::get('redeem/{code}', [RedeemController::class, 'redeem'])->name('redeem-codes.redeem');
});

This creates an API route as {api-url}/redeem/{code} for your client app to access when redeeming the code.

Web route

In /routes/web.php, add:

<?php

use Illuminate\Support\Facades\Route;
use Furic\RedeemCodes\Http\Controllers\RedeemCodeController;

Route::resource('redeem-codes', RedeemCodeController::class);

This creates a web route as {url}/redeem-codes for you to access the redeem console page, to create/edit/delete redeem codes there. Note that we don't have authorization here and anyone can access the page. You can always add the authorization middleware here, but this wouldn't be covered by this article which aims to demonstrate in the simplest way.

Controllers

Moving to the most important and logic part, we will have two Laravel Controllers, one for API and one for web just like routing:

RedeemController

/Http/Controllers/RedeemController.php:

<?php

namespace Furic\RedeemCodes\Http\Controllers;

use Furic\RedeemCodes\Models\RedeemCode;
use Furic\RedeemCodes\Models\RedeemCodeHistory;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Validator;

// The controller for redeem code api calls.
class RedeemController extends Controller
{

    /**
     * Check validation and redeem a given redeem code.
     *
     * @param  string  $code
     * @return \Illuminate\Http\Response
     */
    public function redeem($code)
    {
        $validator = Validator::make(['code' => $code], ['code' => 'exists:redeem_codes,code']);
        if ($validator->fails()) {
            return response(['error' => $validator->errors()->first()], 400); // "The selected code is invalid."
        }

        $redeemCode = RedeemCode::findByCode($code);

        if ($redeemCode->redeemed !== false) {
            return response(['error' => 'The selected code has already been redeemed.'], 400);
        }

        // Check event valid date
        $event = $redeemCode->event;
        if ($event != null) {
            if ($event->start_at != null) {
                $validator = Validator::make($event->toArray(), ['start_at' => 'before:tomorrow']);
                if ($validator->fails()) {
                    return response(['error' => 'The selected code cannot be used yet.'], 400);
                }
            }
            if ($event->end_at != null) {
                $validator = Validator::make($event->toArray(), ['end_at' => 'after:yesterday']);
                if ($validator->fails()) {
                    return response(['error' => 'The selected code has expired.'], 400);
                }
            }
        }

        if ($redeemCode->reusable === false) {
            $redeemCode->setRedeemed();
        }

        // Add a redeem code history
        $data = array();
        $data['redeem_code_id'] = $redeemCode->id;
        $data['ip'] = filter_input(INPUT_SERVER, "REMOTE_ADDR");
        $data['agent'] = filter_input(INPUT_SERVER, "HTTP_USER_AGENT");
        RedeemCodeHistory::create($data);

        return response($redeemCode, 200);
    }

}

In RedeemConstroller, we only have one function to check and validate the inputted redeem code:

  • First, check if the redeem code exists
  • Check if the redeem code previously redeemed
  • Check if the redeem code belongs to an event, if yes then check the event valid date range
  • Set the code as redeemed, if not a reusable code
  • Create a redeem history record

RedeemCodeController

/Http/Controllers/RedeemCodeController.php:

<?php

namespace Furic\RedeemCodes\Http\Controllers;

use Furic\RedeemCodes\Models\Event;
use Furic\RedeemCodes\Models\RedeemCode;
use Furic\RedeemCodes\Models\RedeemCodeReward;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Validator;

// The controller for redeem code web console.
class RedeemCodeController extends Controller
{

    /**
     * Display a listing of the redeem code resource.
     *
     * @return \Illuminate\View\View
     */
    public function index()
    {
        $redeemCodes = RedeemCode::orderBy('created_at', 'desc')->get();
        foreach ($redeemCodes as $redeemCode) {
            $event = Event::find($redeemCode->event_id);
            if (!is_null($event)) {
                $redeemCode->description = $event->name;
            }
        }
        return view('redeem-codes::index', compact('redeemCodes'));
    }

    /**
     * Show the form for creating a new redeem code resource.
     * No need for the time being, simply redirect to index page.
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function create(Request $request)
    {
        return redirect()->route('redeem-codes.index');
    }

    /**
     * Generate a random string with given length.
     *
     * @param  int  $length
     * @return string
     */
    private function generateRandomString($length = 10)
    {
        $characters = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }

    /**
     * Store a newly created redeem code resource in storage.
     *
     * @param  Request  $request
     * @return \Illuminate\View\View
     */
    public function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'count' => 'required|numeric|min:1|max:500',
        ]);

        if ($validator->fails()) {
            return view('redeem-codes::index')->with(['redeemCode' => $request->all(), 'message' => 'Data not valid']);
        }

        $event = new Event;
        $event->name = $request->description;
        $event->save();

        $codes = [];

        if ($request->has('reusable')) { // Make sure reusable only generate one code only
            $request->merge(['count' => 1]);
        }

        for ($i = 0; $i < $request->count; $i++) {
            $redeemCode = new RedeemCode;
            $redeemCode->event_id = $event->id;
            if ($request->has('reusable')) {
                $redeemCode->reusable = 1;
            }
            if (empty($request->prefix)) {
                $redeemCode->code = $this->generateRandomString(12);
            } else {
                $redeemCode->code = strtoupper($request->prefix).$this->generateRandomString(12 - strlen($request->prefix));
            }
            array_push($codes, $redeemCode->code);
            $redeemCode->save();
        }

        $rewardTypesCount = count($request->reward_types);
        for ($i = 0; $i < $rewardTypesCount; $i++) {
            $redeemCodeReward = new RedeemCodeReward;
            $redeemCodeReward->event_id = $event->id;
            $redeemCodeReward->type = $request->reward_types[$i];
            $redeemCodeReward->amount = $request->reward_amounts[$i];
            $redeemCodeReward->save();
        }

        return view('redeem-codes::added', compact('codes'));
    }

    /**
     * Display the specified redeem code resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function show($id)
    {
        return redirect()->route('redeem-codes.edit');
    }

    /**
     * Show the form for editing the specified redeem code resource.
     *
     * @param  int  $id
     * @return \Illuminate\View\View
     */
    public function edit($id)
    {
        $redeemCode = RedeemCode::findOrFail($id);
        $redeemCodesInEvent = $redeemCode->event->redeemCodes;
        return view('redeem-codes::edit', compact('redeemCode', 'redeemCodesInEvent'));
    }

    /**
     * Update the specified redeem code resource in storage.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(Request $request, $id)
    {
        $redeemCode = RedeemCode::findOrFail($id);

        $redeemCode->fill($request->all());
        if ($request->has('reusable')) {
            $redeemCode->reusable = true;
        } else {
            $redeemCode->reusable = false;
        }
        if ($request->has('redeemed')) {
            $redeemCode->redeemed = $request->redeemed;
        } else {
            $redeemCode->redeemed = false;
        }
        $redeemCode->save();

        if ($request->has('description')) {
            $redeemCode->event->name = $request->description;
            $redeemCode->event->save();
        }
        if ($request->has('reward_types')) {
            $redeemCodeRewards = $redeemCode->rewards;
            $rewardTypesCount = count($request->reward_types);
            for ($i = 0; $i < $rewardTypesCount; $i++) {
                $redeemCodeReward = $i < $redeemCodeRewards->count() ? $redeemCodeRewards->slice($i, 1)->first() : new RedeemCodeReward;
                $redeemCodeReward->event_id = $redeemCode->event->id;
                $redeemCodeReward->type = $request->reward_types[$i];
                $redeemCodeReward->amount = $request->reward_amounts[$i];
                $redeemCodeReward->save();
            }
            for ($i = $rewardTypesCount; $i < $redeemCodeRewards->count(); $i++) {
                $redeemCodeReward = $redeemCodeRewards->slice($i, 1)->first();
                $redeemCodeReward->delete();
            }
        }
        return redirect()->route('redeem-codes.index')->with('message', 'Redeem code {$redeemCode->code} updated successfully.');
    }

    /**
     * Remove the specified redeem code resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy($id)
    {
        $redeemCode = RedeemCode::findOrFail($id);
        $redeemCode->delete();
        return redirect()->route('redeem-codes.index')->with('message', 'Redeem code {$redeemCode->code} deleted successfully.');
    }
    
}

RedeemCodeController is more complicated since it contains all redeem code listing, create, edit and delete functions here. Most functions are self-descriptive, we will just go through the create function here:

  • Takes an input of integer: count
  • Creates a new Event entry with the given name
  • Creates a number of RedeemCode entries with the count value
  • Creates a number of RedeemCodeReward entries with the number of the given reward_types array

Views

Finally, we need Laravel Views pages for our web console. Again, for simplicity, we only have 3 pages here: index, edit and added.

Layout blade page

/resources/views/vendor/redeem-codes/layout/app.blade.app:

<!DOCTYPE html>
<html lang="en">
	<head>
	    <meta charset="utf-8">
	    <meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Redeem Codes - Furic</title>
        <link href="https://fonts.googleapis.com/css?family=Lato:100,300,400,700" rel='stylesheet' type='text/css'>
	    <!-- Styles -->
		<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
		{{-- <link href="{{ elixir('css/app.css') }}" rel="stylesheet"> --}}
    </head>
	<body id="app-layout">
	    <nav class="navbar navbar-default">
	        <div class="container">
	            <div class="navbar-header">
	                <!-- Branding Image -->
	                <a class="navbar-brand" href="{{ route('redeem-codes.index') }}">
	                    Redeem Codes
	                </a>
	            </div>
	        </div>
	    </nav>
	    @yield('content')
	    <!-- JavaScripts -->
	    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
	    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
		<script src="https://use.fontawesome.com/2155f17c08.js"></script>
	    @yield('scripts')
	    {{-- <script src="{{ elixir('js/app.js') }}"></script> --}}
	</body>
</html>

This is simply a style layout page with Bootstrap and Font Awesome, you can use your own style if you wish.

Index blade page

/resources/views/vendor/redeem-codes/index.blade.app:

@extends('vendor.redeem-codes.layouts.app')
@section('content')
@if (session('message'))
<div class="alert alert-info">
	{{ session('message') }}
</div>
@endif
@if (session('success'))
<div class="alert alert-success">
	{{ session('success') }}
</div>
@endif
@if (session('danger'))
<div class="alert alert-danger">
	{{ session('danger') }}
</div>
@endif
<div class="container">
	<div class="col-sm-offset-2 col-sm-8">
		<div class="panel panel-default">
			<div class="panel-heading">
				New Redeem Code
			</div>
			<div class="panel-body">
				<!-- New Redeem Code Form -->
				<form action="{{ route('redeem-codes.store') }}" method="POST" class="form-horizontal">
					<!-- Redeem Prefix -->
					<div class="form-group">
						<label for="redeem-code-prefix" class="col-sm-3 control-label">Code Prefix (Optional)</label>
						<div class="col-sm-9">
							<input type="text" name="prefix" id="redeem-code-prefix" maxlength="11" class="form-control">
						</div>
					</div>
					<div class="form-group">
						<label for="redeem-code-reusable" class="col-sm-3 control-label">Reusable</label>
						<div class="col-sm-9">
							<input type="checkbox" name="reusable" value="1" id="redeem-code-reusable" class="form-control">
						</div>
					</div>
					<div class="form-group" id="redeem-code-count-row">
						<label for="redeem-code-count" class="col-sm-3 control-label">Count</label>
						<div class="col-sm-9">
							<input type="number" name="count" value="1" max="500" id="redeem-code-count" class="form-control">
						</div>
					</div>
					<div class="form-group">
						<label for="redeem-code-description" class="col-sm-3 control-label">Description (Optional)</label>
						<div class="col-sm-9">
							<input type="text" name="description" id="redeem-code-description" class="form-control">
						</div>
					</div>
					<div class="form-group" id="rewards">
						<label for="redeem-code-reward-type-1" class="col-sm-3 control-label">Rewards</label>
						<div id="reward-0">
							<div class="col-sm-4">
								<select name="reward_types[]" id="redeem-code-reward-type-1" class="form-control">
									<option value="1" selected="selected">Coins</option>
									<option value="2">Gems</option>
									<option value="4">Remove Ads</option>
									<option value="7">Character</option>
									<option value="10">Energy</option>
									<option value="18">World</option>
									<option value="22">Revive</option>
								</select>
							</div>
							<div class="col-sm-5">
								<input type="number" name="reward_amounts[]" min="1" id="redeem-code-reward-amount-1" class="form-control">
							</div>
						</div>
						<div id="reward-1"></div>
					</div>
					<div class="form-group">
						<div class="col-sm-4 col-sm-offset-3">
							<a id="add-reward" class="btn btn-default pull-left">Add Reward</a>
						</div>
						<div class="col-sm-5">
							<a id='delete-reward' class="pull-right btn btn-default">Delete Reward</a>
						</div>
					</div>
					<!-- Add Redeem Code Button -->
					<div class="form-group">
						<div class="col-sm-offset-1 col-sm-10">
							<button type="submit" class="btn btn-primary btn-block">
								<i class="fa fa-plus"></i> Add
							</button>
						</div>
					</div>
					{{ csrf_field() }}
				</form>
			</div>
		</div>
	</div>
	@if (count($redeemCodes) > 0)
	<div class="col-sm-12">
		<div class="panel panel-default">
			<div class="panel-heading">
				Current Redeem Codes
			</div>
			<div class="panel-body">
				<table class="table table-striped task-table">
                    <!-- Table Headings -->
                    <thead>
                        <th>Redeem Code</th>
                        <th>Description</th>
                        <th align="center">#Rewards</th>
                        <th align="center">Reusable</th>
                        <th align="center">Redeemed</th>
                        <th>&nbsp;</th>
                        <th>&nbsp;</th>
                    </thead>
                    <!-- Table Body -->
                    <tbody>
                        @foreach ($redeemCodes as $redeemCode)
						<tr>
							<td class="table-text">
								<div>{{ $redeemCode->code }}</div>
							</td>
							<td class="table-text">
								<div>{{ $redeemCode->description }}</div>
							</td>
							<td class="table-text" align="center">
								<div>{{ $redeemCode->rewards->count() }}</div>
							</td>
							<td class="table-text" align="center">
								<div>
								@if ($redeemCode->reusable)
								<i class="fa fa-check"></i>
								@endif
								</div>
							</td>
							<td class="table-text" align="center">
								<div>
								@if ($redeemCode->redeemed)
								<i class="fa fa-check"></i>
								@endif
								</div>
							</td>
							<td align="right">
								<a href="{{ route('redeem-codes.edit', $redeemCode->id) }}" class="btn btn-default">
									<i class="fa fa-btn fa-edit"></i> Edit
								</a>
							</td>
							<td align="right">
								@if ($redeemCode->redeemed)
								<form action="{{ route('redeem-codes.update', $redeemCode->id) }}" method="POST">
									{{ method_field('PUT') }}
									{{ csrf_field() }}
									<input type="hidden" name="redeemed" value="0">
									<button type="submit" class="btn btn-danger">
										<i class="fa fa-btn fa-undo"></i> Reset Redeemed
									</button>
								</form>
								@endif
							</td>
							<td align="right">
								<form action="{{ route('redeem-codes.destroy', $redeemCode->id) }}" method="POST">
									{{ method_field('DELETE') }}
									{{ csrf_field() }}
									<button type="submit" class="btn btn-danger">
										<i class="fa fa-btn fa-trash"></i> Delete
									</button>
								</form>
							</td>
						</tr>
                        @endforeach
                    </tbody>
                </table>
			</div>
		</div>
	</div>
	@endif
</div>
@endsection
@section('scripts')
<script>
	$(document).ready(function() {
		$('#redeem-code-reusable').change(function() {
			$('#redeem-code-count-row').toggle(!this.checked);
		});
		var i = 1;
		$('#add-reward').click(function() {
			$('#reward-' + i).html(`
			<div class="col-sm-4 col-sm-offset-3">
				<select name="reward_types[]" class="form-control">
					<option value="1" selected="selected">Coins</option>
					<option value="2">Gems</option>
					<option value="4">Remove Ads</option>
					<option value="7">Character</option>
					<option value="10">Energy</option>
					<option value="18">World</option>
					<option value="22">Revive</option>
				</select>
			</div>
			<div class="col-sm-5">
				<input type="number" name="reward_amounts[]" min="1" class="form-control">
			</div>
			`);
			$('#rewards').append('<div id="reward-' + (i + 1) + '"></div>');
			i++;
		});
		$('#delete-reward').click(function() {
			if (i > 1) {
				$('#reward-' + (i - 1)).html('');
				i--;
			}
		});
	});
</script>
@endsection
Laravel Redeem Codes console
The web console Index page of Redeem Code.

The index page is a bit long, bascially it does the following:

  • A new redeem code form, it will send a POST request and run the create() in RedeemCodeController.php.
  • List all redeem codes, with the links to edit, reset and delete.

Added blade page

/resources/views/vendor/redeem-codes/added.blade.app:

@extends('vendor.redeem-codes.layouts.app')
@section('content')
<div class="container">
	<div class="col-sm-offset-2 col-sm-8">
		<div class="panel panel-default">
			<div class="panel-heading">
				Redeem Code Added
			</div>
			<div class="panel-body">
				<div id="codes" class="col-sm-12">
					@foreach($codes as $v)
					{{ $v }}<br />
					@endforeach
				</div>
				<div class="col-sm-6" style="margin-top: 20px">
					<a id="select-all" class="btn btn-default pull-left">Select All</a>
				</div>
				<div class="col-sm-6" style="margin-top: 20px">
					<a class="btn btn-default pull-right" href="{{ route('redeem-codes.index') }}">Back</a>
				</div>
			</div>
		</div>
	</div>
</div>
@endsection

@section('scripts')
<script>
	$(document).ready(function() {
		var selected = false;
		$("#select-all").click(function() {
			if (selected) {
				if (document.selection) {
					document.selection.empty();
				} else if (window.getSelection) {
					window.getSelection().removeAllRanges();
				}
			} else {
				if (document.body.createTextRange) {
					var range = document.body.createTextRange();
					range.moveToElementText(document.getElementById('codes'));
					range.select();
				} else if (window.getSelection) {
					var selection = window.getSelection();
					var range = document.createRange();
					range.selectNodeContents(document.getElementById('codes'));
					selection.removeAllRanges();
					selection.addRange(range);
				}
			}
			selected = !selected;
		});
	}); 
</script>
@endsection

This code simply shows a confirm page after adding redeem codes and lists all newly generated codes so you can send them to your users.

Edit blade page

/resources/views/vendor/redeem-codes/edit.blade.app:

@extends('vendor.redeem-codes.layouts.app')
@section('content')
<div class="container">
	<div class="col-sm-offset-2 col-sm-8">
		<div class="panel panel-default">
			<div class="panel-heading">
				Edit Redeem Code - {{ $redeemCode->code }}
			</div>
			<div class="panel-body">
				<!-- New Redeem Code Form -->
				<form action="{{ route('redeem-codes.update', $redeemCode->id) }}" method="POST" class="form-horizontal">
					{{ method_field('PUT') }}
					{{ csrf_field() }}
					<div class="form-group">
						<label for="code" class="col-sm-3 control-label">Redeem Code</label>
						<div class="col-sm-9">
							<input type="text" id="code" class="form-control" placeholder="{{ $redeemCode->code }}" disabled>
						</div>
					</div>
					<div class="form-group">
						<label for="redeem-code-reusable" class="col-sm-3 control-label">Reusable</label>
						<div class="col-sm-9">
							<input type="checkbox" name="reusable" value="1" id="redeem-code-reusable" class="form-control" {{ $redeemCode->reusable ? 'checked' : '' }}>
						</div>
					</div>
					<div class="form-group">
						<label for="redeem-code-redeemed" class="col-sm-3 control-label">Redeemed</label>
						<div class="col-sm-9">
							<input type="checkbox" name="redeemed" value="1" id="redeem-code-redeemed" class="form-control" {{ $redeemCode->redeemed ? 'checked' : '' }}>
						</div>
					</div>
					@if ($redeemCodesInEvent->count() > 1)
					<div class="alert alert-info">
						Changing info below also affect Redeem Code {{ $redeemCodesInEvent->implode('code', ', ') }}
					</div>
					@endif
					<div class="form-group">
						<label for="redeem-code-description" class="col-sm-3 control-label">Description (Optional)</label>
						<div class="col-sm-9">
							<input type="text" name="description" id="redeem-code-description" class="form-control" value="{{ $redeemCode->event->name }}">
						</div>
					</div>
					<div class="form-group" id="rewards">
						<label for="redeem-code-reward-type-1" class="col-sm-3 control-label">Rewards</label>
						@foreach ($redeemCode->rewards as $i => $reward)
						<div id="reward-{{ $i }}">
							<div class="col-sm-4 {{ $i > 0 ? 'col-sm-offset-3' : '' }}">
								<select name="reward_types[]" id="redeem-code-reward-type-{{ $i + 1 }}" class="form-control">
									<option value="1" {{ $reward->type == 0 ? 'selected' : '' }}>Coin</option>
									<option value="2" {{ $reward->type == 1 ? 'selected' : '' }}>Gem</option>
									<option value="4" {{ $reward->type == 2 ? 'selected' : '' }}>Remove Ads</option>
									<option value="7" {{ $reward->type == 100 ? 'selected' : '' }}>Character</option>
									<option value="10" {{ $reward->type == 999 ? 'selected' : '' }}>Energy</option>
									<option value="18" {{ $reward->type == 999 ? 'selected' : '' }}>World</option>
									<option value="22" {{ $reward->type == 999 ? 'selected' : '' }}>Revive</option>
								</select>
							</div>
							<div class="col-sm-5">
								<input type="number" name="reward_amounts[]" min="1" id="redeem-code-reward-amount-1" class="form-control" value="{{ $reward->amount }}">
							</div>
						</div>
						@endforeach
						<div id="reward-{{ $redeemCode->rewards->count() }}"></div>
					</div>
					<div class="form-group">
						<div class="col-sm-4 col-sm-offset-3">
							<a id="add-reward" class="btn btn-default pull-left">Add Reward</a>
						</div>
						<div class="col-sm-5">
							<a id='delete-reward' class="pull-right btn btn-default">Delete Reward</a>
						</div>
					</div>
					<!-- Add Redeem Code Button -->
					<div class="form-group">
						<div class="col-sm-offset-1 col-sm-10">
							<button type="submit" class="btn btn-primary btn-block">
								<i class="fa fa-edit"></i> Edit
							</button>
						</div>
					</div>
				</form>
			</div>
		</div>
	</div>
</div>
@endsection

@section('scripts')
<script>
	$(document).ready(function() {
		$('#redeem-code-reusable').change(function() {
			$('#redeem-code-count-row').toggle(!this.checked);
		});
		var i = {{ $redeemCode->rewards->count() }};
		$('#add-reward').click(function() {
			$('#reward-' + i).html(`
			<div class="col-sm-4 col-sm-offset-3">
				<select name="reward_types[]" class="form-control">
					<option value="1" selected="selected">Coins</option>
					<option value="2">Gems</option>
					<option value="4">Remove Ads</option>
					<option value="7">Character</option>
					<option value="10">Energy</option>
					<option value="18">World</option>
					<option value="22">Revive</option>
				</select>
			</div>
			<div class="col-sm-5">
				<input type="number" name="reward_amounts[]" min="1" class="form-control">
			</div>
			`);
			$('#rewards').append('<div id="reward-' + (i + 1) + '"></div>');
			i++;
		});
		$('#delete-reward').click(function() {
			if (i > 1) {
				$('#reward-' + (i - 1)).html('');
				i--;
			}
		});
	});
</script>
@endsection

This page shows the loaded redeem code and contains a form to update the code. it will send a PUT request and run the update() in RedeemCodeController.php.

Conclusion

That's it! Although it seems to require quite a few scripts to implement, most of the code follow Laravel MVC architecture and makes things tidier and much easier to maintain in long run.

Again, you can read all code in the GitHub repo, while the project is still pretty simple and there's few TODOs.

Oh, that's the server back-end part, I will write another post on how to call the API and handle the response later. 😀

Lastly, leave a comment if you got any questions and wish this article and the package may help you.

1
Like this article?
Let the author know and give them a clap!

We'd like to thank these amazing companies for supporting us

Your logo here?

The Laravel portal for problem solving, knowledge sharing and community building.

© 2021 Laravel.io - All rights reserved.