-
Notifications
You must be signed in to change notification settings - Fork 36
Tutorial ‐ Part 2
Let us create a model to save customer details.
php artisan make:model Customer -mf
Let us create a model to save account details.
php artisan make:model Account -mf
Edit the generated migration file database/migrations/****_create_cutomers_table
to look like this:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('full_name');
$table->string('phone_number')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('customers');
}
};
Edit app/Models/Customer.php
to look like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
use HasFactory;
protected $fillable = ['full_name', 'phone_number'];
public function accounts()
{
return $this->hasMany(Account::class);
}
}
Edit the generated migration file database/migrations/****_create_accounts_table
to look like this:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('accounts', function (Blueprint $table) {
$table->id();
$table->string('type')->default('savings');
$table->decimal('balance')->default(0);
$table->foreignId('customer_id')->constrained();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('accounts');
}
};
Edit app/Models/Account.php
to look like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Account extends Model
{
use HasFactory;
protected $fillable = ['type', 'phone_number'];
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
Now let create a new state to get the customer name, and account type and show registration completion.
php artisan ussd:state RegistrationCustomerNameState
php artisan ussd:state RegistrationAccountTypeState
php artisan ussd:state RegistrationSucceededState
Edit the generated file app/Ussd/States/RegistrationCustomerNameState.php
to look like
<?php
namespace App\Ussd\States;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Context;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Regex;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;
#[Transition(RegistrationAccountTypeState::class, new Regex('/^[a-zA-Z\s]+$/'), [self::class, 'callback'])]
class RegistrationCustomerNameState implements State
{
public function render(): Menu
{
return Menu::build()->text('What is your full name?');
}
public function callback(Context $context, Record $record)
{
$record->set('full_name', $context->input());
}
}
Edit the generated file app/Ussd/States/RegistrationAccountTypeState.php
to look like
<?php
namespace App\Ussd\States;
use App\Models\Customer;
use Illuminate\Support\Facades\DB;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Context;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Between;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;
#[Transition(RegistrationSucceededState::class, new Between(1, 2), [self::class, 'callback'])]
class RegistrationAccountTypeState implements State
{
public function render(): Menu
{
return Menu::build()
->line('Account Type')
->listing([
'Savings',
'Current',
]);
}
public function callback(Context $context, Record $record)
{
$accountType = '1' === $context->input() ? 'savings' : 'current';
$fullName = $record->get('full_name');
$phoneNumber = $context->get('phone_number');
DB::transaction(function () use ($accountType, $fullName, $phoneNumber) {
$customer = Customer::query()->create(['full_name' => $fullName, 'phone_number' => $phoneNumber]);
$customer->accounts()->create(['type' => $accountType]);
}, 5);
}
}
Edit the generated file app/Ussd/States/RegistrationSucceededState.php
to look like
<?php
namespace App\Ussd\States;
use Sparors\Ussd\Attributes\Terminate;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Menu;
#[Terminate]
class RegistrationSucceededState implements State
{
public function render(): Menu
{
return Menu::build()
->line('Banc')
->lineBreak()
->text('Thank you for registering with us.');
}
}
Finally edit app/Ussd/States/GuestMenuState.php
to look like this:
<?php
namespace App\Ussd\States;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Contracts\InitialState;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Menu;
#[Transition(RegistrationCustomerNameState::class, new Equal(1))]
#[Transition(HelplineState::class, new Equal(2))]
class GuestMenuState implements InitialState
{
public function render(): Menu
{
return Menu::build()
->line('Banc')
->listing([
'Register',
'Helpline',
])
->text('Powered by Sparors');
}
}
Although we have already registered, when we start the process, it going to ask us to register. Let fix that.
Let create a menu for registered customers.
php artisan ussd:state CustomerMenuState
Edit the generated file app/Ussd/States/CustomerMenuState.php
to look like
<?php
namespace App\Ussd\States;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Menu;
class CustomerMenuState implements State
{
public function render(): Menu
{
return Menu::build()
->line('Banc')
->listing([
'Transfer',
'Deposit',
'Withdraw',
'New Account',
'Helpline',
])
->text('Powered by Sparors');
}
}
Let us make an action to decide which menu should be shown to the user
php artisan make:action MenuAction --init
Edit the generated file app/Ussd/Actions/MenuAction.php
to look like this
<?php
namespace App\Ussd\Actions;
use App\Models\Customer;
use App\Ussd\States\CustomerMenuState;
use App\Ussd\States\GuestMenuState;
use Sparors\Ussd\Context;
use Sparors\Ussd\Contracts\InitialAction;
class MenuAction implements InitialAction
{
public function execute(Context $context): string
{
$isRegistered = Customer::query()
->where('phone_number', $context->get('phone_number'))
->exists();
return $isRegistered ? CustomerMenuState::class : GuestMenuState::class;
}
}
Now edit app/Ussd/States/GuestMenuState.php
to
<?php
namespace App\Ussd\States;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Menu;
#[Transition(RegistrationCustomerNameState::class, new Equal(1))]
#[Transition(HelplineState::class, new Equal(2))]
class GuestMenuState implements State
{
public function render(): Menu
{
return Menu::build()
->line('Banc')
->listing([
'Register',
'Helpline',
])
->text('Powered by Sparors');
}
}
Now edit app/Http/Controllers/UssdController.php
to
<?php
namespace App\Http\Controllers;
use App\Ussd\Actions\MenuAction;
use App\Ussd\Responses\AfricasTalkingResponse;
use Illuminate\Http\Request;
use Sparors\Ussd\Context;
use Sparors\Ussd\Ussd;
class UssdController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request)
{
$lastText = $request->input('text') ?? '';
if (strlen($lastText) > 0) {
$lastText = explode('*', $lastText);
$lastText = end($lastText);
}
return Ussd::build(
Context::create(
$request->input('sessionId'),
$request->input('phoneNumber'),
$lastText
)
->with(['phone_number' => $request->input('phoneNumber')])
)
->useInitialState(MenuAction::class)
->useResponse(AfricasTalkingResponse::class)
->run();
}
}
Now you should see a different menu when you dial with either a registered or non-registered number.
We will leave the implementation for the registered customer for you to play around with.