State

States are the core building blocks of your USSD application. Each state represents a screen or step in your USSD flow.

Creating a State

Use the Artisan command to create a new state:

php artisan ussd:state WelcomeState

This will create a new state class in app/Ussd/States/WelcomeState.php.

State Structure

A typical state class looks like this:

<?php

namespace App\Ussd\States;

use Vendor\LaravelUssd\Support\AbstractState;
use Vendor\LaravelUssd\Support\Context;
use Vendor\LaravelUssd\Support\UssdResponse;
use Vendor\LaravelUssd\Menu\Menu;

class WelcomeState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        return (new Menu())
            ->text('Welcome to our service!')
            ->option('1', 'View Balance')
            ->option('2', 'Transfer Money')
            ->option('3', 'Exit')
            ->expectsInput();
    }

    public function next(Context $context, string $input): ?string
    {
        // IMPORTANT: Must return a State class name (string) or null
        // Never return an Action class name
        return $this->decision($input)
            ->equal('1', BalanceState::class)  // ✅ State class name
            ->equal('2', TransferState::class) // ✅ State class name
            ->equal('3', null) // End session
            ->any(WelcomeState::class); // ✅ State class name (default fallback)
    }
}

State Methods

buildMenu()

Builds and returns a Menu instance for this state. This method is called when the user enters the state.

next()

Must return a State class name (string) or null. Processes user input and returns the fully qualified class name of the next State to navigate to, or null to end the session. Never return an Action class name.

decision()

Returns a Decision instance for creating fluent decision trees based on user input.

record

Access the Record instance for storing and retrieving session data throughout the flow.

Setting the Initial State

The initial state is the entry point of your USSD application. It's the first state that users encounter when they dial your USSD code and start a new session. This state is configured in your config/ussd.php file.

Configuration

The initial state is set using the initial_state key in your configuration file:

return [
    'initial_state' => 'App\\Ussd\\States\\WelcomeState',
    
    'state_namespace' => 'App\\Ussd\\States',
    
    // ... other configuration
];

Important Requirements:

  • The value must be a fully qualified class name (including the complete namespace)
  • The class must exist and implement the State interface
  • Use double backslashes (\\) to escape namespace separators in PHP strings

Changing the Initial State

To change the initial state, simply update the initial_state value to point to a different state class:

return [
    // Change from WelcomeState to MainMenuState
    'initial_state' => 'App\\Ussd\\States\\MainMenuState',
    
    // Or use a different namespace
    'initial_state' => 'App\\Services\\Ussd\\States\\HomeState',
    
    // ... other configuration
];

How Initial State Works

The initial state plays a crucial role in the USSD session flow:

New Sessions

When a user dials your USSD code for the first time, the Machine creates a new session context and sets the current state to the configured initial state. The user then sees the menu from this state.

Session Restart

When a user chooses to restart their session (via session continuity), the Machine resets the context back to the initial state, clearing all stored data and starting fresh.

State Flow

From the initial state, users navigate through your application by selecting options or providing input, which transitions them to other states. The initial state serves as the root of your state tree.

Best Practices

  • Choose a state that provides a clear main menu or welcome message as your initial state
  • Ensure your initial state always expects input (expectsInput(true)) to allow users to navigate
  • Keep the initial state simple and focused - it's the first impression users have of your service
  • Test your initial state thoroughly, as it's the most frequently accessed state in your application

Response Types: CON vs END

USSD responses must be prefixed with either CON (continue) or END (end session). The framework automatically determines this based on whether your menu expects user input.

CON (Continue) - Expects User Input

Use CON when you want the user to provide input. This keeps the session active and waits for the user's response.

When to Use CON:

  • Displaying menus with options for the user to select
  • Asking for user input (phone numbers, amounts, PINs, etc.)
  • Any state where the user needs to make a selection or provide data
<?php

class WelcomeState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        return (new Menu())
            ->text('Welcome to our service!')
            ->option('1', 'View Balance')
            ->option('2', 'Transfer Money')
            ->option('3', 'Exit')
            ->expectsInput(true); // CON response - expects input
    }
}

The response will be: CON Welcome to our service!\n1. View Balance\n2. Transfer Money\n3. Exit

END (End Session) - Information Only

Use END when you're displaying information only and don't need user input. This terminates the USSD session.

When to Use END:

  • Displaying account balance or transaction results
  • Showing confirmation messages (e.g., "Transfer successful")
  • Error messages that don't require user action
  • Final states in a flow (success/error screens)
<?php

class BalanceState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        $this->setContext($context);
        $balance = $this->record->get('balance', '0.00');
        
        return (new Menu())
            ->text('Account Balance')
            ->line('')
            ->text("Your balance: {$balance}")
            ->line('')
            ->text('Thank you for using our service.')
            ->expectsInput(false); // END response - no input needed
    }
}

The response will be: END Account Balance\n\nYour balance: 1000.00\n\nThank you for using our service.

Using the response() Method

The response() method automatically determines CON or END based on the menu's expectsInput() setting:

<?php

class MyState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        $menu = (new Menu())
            ->text('Select an option:')
            ->option('1', 'Option 1')
            ->option('2', 'Option 2');
        
        // Option 1: Set expectsInput on the menu
        $menu->expectsInput(true); // Will generate CON response
        
        // Option 2: Pass expectsInput to response() method
        // This overrides the menu's setting
        return $this->response($menu, true); // CON response
        // or
        return $this->response($menu, false); // END response
    }
}

Direct Response Creation

You can also create responses directly using UssdResponse class for more control:

<?php

use Vendor\LaravelUssd\Support\UssdResponse;

class CustomState extends AbstractState
{
    public function entry(Context $context): UssdResponse
    {
        // Create a CON response (expects input)
        return UssdResponse::continue('Please enter your PIN:');
        
        // Or create an END response (info only)
        return UssdResponse::end('Transaction completed successfully!');
    }
}

Important Notes

  • CON responses keep the session alive. The user can continue interacting with your application.
  • END responses terminate the session immediately. The user must start a new session to continue.
  • If you use expectsInput(false), make sure your next() method returns null since the session will end.
  • The default behavior is expectsInput(true), so menus will generate CON responses unless explicitly set to false.

Common Mistakes

❌ Mistake 1: Returning Action Instead of State

The most common error is returning an Action class name from next() instead of a State class name.

public function next(Context $context, string $input): ?string
{
    // ❌ WRONG: Returning an Action class name
    return ProcessPaymentAction::class; // Error: Must return State, not Action
}
public function next(Context $context, string $input): ?string
{
    $this->setContext($context);
    
    // ✅ CORRECT: Call the action, then return a State
    $action = new ProcessPaymentAction();
    $result = $action->handle($context, $input);
    
    return $result['success'] 
        ? PaymentSuccessState::class  // Return State class name
        : PaymentErrorState::class;   // Return State class name
}

❌ Mistake 2: Returning Action Instance

Never return an Action instance from next().

public function next(Context $context, string $input): ?string
{
    // ❌ WRONG: Returning an Action instance
    return new ProcessPaymentAction(); // Error: Must return string (State class name)
}

❌ Mistake 3: Forgetting to Return State Class Name

Always return a fully qualified State class name as a string, or use the ::class constant.

public function next(Context $context, string $input): ?string
{
    // ❌ WRONG: Returning the class without ::class or as object
    return 'NextState'; // Missing namespace
    return new NextState(); // Returning instance, not class name
}
public function next(Context $context, string $input): ?string
{
    // ✅ CORRECT: Return State class name using ::class
    return NextState::class;
    
    // ✅ CORRECT: Or return fully qualified string
    return 'App\\Ussd\\States\\NextState';
}

Remember: next() Return Type

  • Return type is ?string - a State class name or null
  • Always return a State class name, never an Action class name
  • Use StateClass::class or fully qualified string like 'App\\Ussd\\States\\StateClass'
  • Return null to end the USSD session
  • If you need to call an Action for business logic, call it first, then return the appropriate State based on the result