Skip to content

Tutorial ‐ Part 1

Isaac Sai edited this page Dec 28, 2023 · 7 revisions

Let us create a new laravel project with the name banc. This will be our personal banking ussd for clients. Ensure you change the directory to where you keep your projects.

composer create-project laravel/laravel banc

Open the new application in the edit of your choice.

In the project root run

pwd

You should get a response like /Users/cybersai/Personal/banc

Change the following values in your .env

DB_CONNECTION=sqlite
DB_DATABASE=/Users/cybersai/Personal/banc/sqlite.db

where /Users/cybersai/Personal/banc/sqlite.db is the absolute path to your sqlite db

Lets install laravel-ussd package. Make sure you are in the same directory as your banc project and you are using the shell.

composer require sparors/laravel-ussd:3.x-dev

lets create a new controller to expose the application to the web.

php artisan make:controller UssdController --invokable

let us edit the routes/api.php file to add the USSD endpoint at the end of the file.

<?php

use App\Http\Controllers\UssdController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('ussd', UssdController::class)->name('ussd');

Let us create our first USSD state that guest users will see.

php artisan ussd:state GuestMenuState --init

Go to the generated file app/Ussd/States/GuestMenuState and edit the content to look like this:

<?php

namespace App\Ussd\States;

use Sparors\Ussd\Contracts\InitialState;
use Sparors\Ussd\Menu;

class GuestMenuState implements InitialState
{
    public function render(): Menu
    {
        return Menu::build()
            ->line('Banc')
            ->listing([
                'Register',
                'Helpline',
            ])
            ->text('Powered by Sparors');
    }
}

Back to the USSD controller app/Http/Controllers/UssdController.php, let's create a USSD app inside __invoke function.

<?php

namespace App\Http\Controllers;

use App\Ussd\States\GuestMenuState;
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(GuestMenuState::class)
        ->run();
    }
}

Now access your USSD controller from a rest client, simulating how africastalking send the request.

You should see a response like this:

{
	"message": "Banc\n1.Register\n2.Helpline\nPowered by Sparors",
	"terminating": false
}

This does not match the response africastalking is expecting. Let fix that by creating a new response.

php artisan ussd:response AfricasTalkingResponse

Update the generated file app/Ussd/Responses/AfricasTalkingResponse.php to:

<?php

namespace App\Ussd\Responses;

use Sparors\Ussd\Contracts\Response;

class AfricasTalkingResponse implements Response
{
    public function respond(string $message, bool $terminating): mixed
    {
        return response(
            ($terminating ? 'END' : 'CON') . ' ' . $message,
            200,
            ['Content-Type' => 'text/plain']
        );
    }
}

Then update ussd controller to:

<?php

namespace App\Http\Controllers;

use App\Ussd\Responses\AfricasTalkingResponse;
use App\Ussd\States\GuestMenuState;
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(GuestMenuState::class)
        ->useResponse(AfricasTalkingResponse::class)
        ->run();
    }
}

You should now see the following response:

CON Banc
1.Register
2.Helpline
Powered by Sparors

Let us create the helpline state.

php artisan ussd:state HelplineState

Update the generated file app/Ussd/States/HelplineState to look like this.

<?php

namespace App\Ussd\States;

use Sparors\Ussd\Attributes\Terminate;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Menu;

#[Terminate]
class HelplineState implements State
{
    public function render(): Menu
    {
        return Menu::build()
            ->line('Helpline')
            ->listing([
                'email: [email protected]',
                'phone: +233 241 122 333'
            ]);
    }
}

Then edit app/Ussd/States/GuestMenuState 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(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');
    }
}
Clone this wiki locally