Skip to content

Docs ‐ State

Isaac Sai edited this page Jan 20, 2024 · 7 revisions

Introduction to States

USSD state help as create pages for users and exaluate the input they choose after.

Creating USSD States

You just need to run the following command to create a new USSD State.

php artisan ussd:state AvailableCountriesState

If the state should be the first state, you can use the init option

php artisan ussd:state WelcomeState --init

If the state should be a continuing state, you can use the cont option. Continue state is used to resume uncompleted USSD state by first asking the user if they want to continue an old session or start a new one.

php artisan ussd:state WouldYouLikeToContinueState --cont

Ask how the user if they will like to continue and specify which decision should be used to validate the input.

<?php

namespace App\Ussd\States;

use Sparors\Ussd\Contracts\ContinueState;
use Sparors\Ussd\Contracts\Decision;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Menu;

class WouldYouLikeToContinueState implements ContinueState
{
    public function render(): Menu
    {
        return Menu::build()->text('Enter 1 to continue or any key start over');
    }

    public function confirm(): Decision
    {
        return new Equal(1);
    }
}

Working with USSD States

States should have a render function that returns a USSD menu.

Transitioning between States

You can use Transition attributes to indicate the transitioning from one state to the other. Transition attributes allows you to define a callback that should be run before performing the transitioning.

<?php

namespace App\Ussd\States;

use App\Models\Customer;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Decisions\Fallback;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;


#[Transition(to: RegisterState::class, match: new Equal(1), callback: [self::class, 'callback'])]
#[Transition(to: HelplineState::class, match: new Fallback())]
class WelcomeState implements State
{
    public function render(): Menu
    {
        return Menu::build()
            ->line('Banc')
            ->listing([
                'Register',
                'Helpline',
            ])
            ->text('Powered by Sparors');
    }

    public function callback(Record $record): void
    {
        $details = Customer::query()
            ->where('phone_number', $context->get('phone_number'))
            ->latest()
            ->first();

        $record->set('details', $details);
    }
}

Paginating a States

You can use Paginate attributes to paginate a very long list of items that would not fit on a page. Paginate is actually just transition, back to the same state.

<?php

namespace App\Ussd\States;

use App\Models\Customer;
use Sparors\Ussd\Attributes\Paginate;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Decisions\Fallback;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Context;
use Sparors\Ussd\Record;


#[Paginate(next: new Equal('#'), previous: new Equal('0'), callback: [self::class, 'callback'])]
#[Transition(to: HelplineState::class, match: new Fallback())]
class AvailableCountriesState implements State
{
    public function render(Record $record): Menu
    {
        $page = $record->get('countries_page', 1);

        return Menu::build()
            ->line('Banc')
            ->listing([
                'Angola',
                'Algeria',
                'Cameroon',
                'DR Congo',
                'Ghana',
                'Egypt',
                'Kenya',
                'Uganda',
                'Zimbabwe',
            ], page: $page, perPage: 3)
            ->text('Powered by Sparors');
    }

    public function callback(Context $context, Record $record): void
    {
        $page = $record->get('countries_page', 1);

        if ('#' === $context->input()) {
            $record->set('countries_page', $page + 1);
        } else {
            $record->set('countries_page', $page - 1);
        }
    }
}

WithPagination Helper

Pagination is common for USSD application so there are inbuilt helper traits to help make it simple.

<?php

namespace App\Ussd\States;

use App\Models\Customer;
use Sparors\Ussd\Attributes\Paginate;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Decisions\Fallback;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;
use Sparors\Ussd\Traits\WithPagination;


#[Paginate(next: new Equal('#'), previous: new Equal('0'))]
#[Transition(to: HelplineState::class, match: new Fallback())]
class AvailableCountriesState implements State
{
    use WithPagination;

    public function render(Record $record): Menu
    {
        $page = $record->get('countries_page', 1);

        return Menu::build()
            ->when($this->isFirstPage(), fn (Menu $menu) => $menu->line('Banc'))
            ->listing($this->getItems(), page: $this->currentPage(), perPage: $this->perPage())
            ->when($this->hasPreviousPage(), fn (Menu $menu) => $menu->line('0. Previous Page'))
            ->when($this->hasNextPage(), fn (Menu $menu) => $menu->line('#. Next Page'))
            ->when($this->isLastPage(), fn (Menu $menu) => $menu->line('Powered by Sparors'));
    }

    public function getItems(): array
    {
        return [
            'Angola',
            'Algeria',
            'Cameroon',
            'DR Congo',
            'Ghana',
            'Egypt',
            'Kenya',
            'Uganda',
            'Zimbabwe',
        ];
    }

    public function perPage(): int
    {
        return 3;
    }
}

Truncating a States

You can use Truncate attributes to limit the number of characters that should be return to the USSD application. This helps ensure your USSD application is in conformity with the character limit of your USSD Provider. This is very useful for dynamic content where you can not be certain on the total characters.

<?php

namespace App\Ussd\States;

use App\Models\Customer;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Decisions\Fallback;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;


#[Transition(to: RegisterState::class, match: new Equal(1))]
#[Truncate(limit: 80, end: '#. More.', more: new Equal('#'))]
class DetailsState implements State
{
    public function render(Record $record): Menu
    {
        return Menu::build()
            ->line('Banc')
            ->format('Name: %s', $record->name)
            ->lineBreak()
            ->line('Your post')
            ->line($record->get('post'))
            ->text('Powered by Sparors');
    }
}

Terminating a States

To indicate that a USSD application should Terminate, use the Terminate attribute.

<?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: info@banc.co',
                'phone: +233 241 122 333'
            ]);
    }
}