moffhub/ussd
| Install | |
|---|---|
composer require moffhub/ussd |
|
| Latest Version: | v0.1.14 |
| PHP: | ^8.4 || ^8.5 |
| License: | MIT |
| Last Updated: | Aug 4, 2026 |
| Links: | GitHub · Packagist |
Moffhub USSD Framework
A powerful, enterprise-grade Laravel package for building scalable USSD applications with support for multiple African mobile network providers.
Features
- Multi-Provider Support: Built-in adapters for Safaricom/Africa's Talking, Airtel, MTN, and a generic fallback
- Menu System: Simple menus, forms, paginated lists, conditional menus, and multi-step wizards
- Session Management: Intelligent session recovery, grace periods, and context preservation
- Security: Rate limiting, input sanitization, audit logging, and blacklist/whitelist support
- Analytics: Track user journeys, menu interactions, and performance metrics
- Caching: Efficient caching layer for menus and data providers
- Data Providers: Pluggable data sources (Array, Database, API)
Requirements
- PHP 8.4+
- Laravel 12.0+
Installation
composer require moffhub/ussd
Publish the configuration file:
php artisan vendor:publish --provider="Moffhub\Ussd\UssdServiceProvider"
Run migrations (optional, for session persistence and analytics):
php artisan migrate
Quick Start
1. Create a USSD Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Moffhub\Ussd\UssdFramework;
use Moffhub\Ussd\Menus\SimpleMenu;
use Moffhub\Ussd\UssdResponse;
class UssdController extends Controller
{
public function handle(Request $request)
{
$framework = new UssdFramework([
'default_menu' => 'main',
]);
// Register menus
$framework->registerMenu('main', $this->mainMenu());
$framework->registerMenu('balance', $this->balanceMenu());
// Handle request and return response
$response = $framework->handle($request);
return response($response->formatForNetwork())
->header('Content-Type', 'text/plain');
}
private function mainMenu(): SimpleMenu
{
return new SimpleMenu('Welcome to MyApp', [
'1' => 'Check Balance',
'2' => 'Send Money',
'3' => 'Buy Airtime',
], [
'1' => function ($input, $session, $framework) {
return $framework->navigateToMenuWithResponse('balance');
},
]);
}
private function balanceMenu(): SimpleMenu
{
return new SimpleMenu('Your balance is KES 1,500.00', [
'0' => 'Back to Main Menu',
], [
'0' => function ($input, $session, $framework) {
return $framework->navigateToMenuWithResponse('main');
},
]);
}
}
2. Register the Route
// routes/api.php
Route::post('/ussd', [UssdController::class, 'handle']);
Menu Types
SimpleMenu
Basic menu with numbered options:
use Moffhub\Ussd\Menus\SimpleMenu;
$menu = new SimpleMenu('Main Menu', [
'1' => 'Option One',
'2' => 'Option Two',
'3' => 'Option Three',
], [
'1' => function ($input, $session, $framework) {
return UssdResponse::end('You selected option one!');
},
]);
FormMenu
Collect multi-step form data:
use Moffhub\Ussd\Menus\FormMenu;
use Moffhub\Ussd\Helpers\FormField;
$formMenu = new FormMenu('Registration');
$formMenu->addField(new FormField(
name: 'name',
prompt: 'Enter your full name:',
validators: [Validators::required(), Validators::minLength(2)]
));
$formMenu->addField(new FormField(
name: 'phone',
prompt: 'Enter phone number:',
validators: [Validators::phone()]
));
$formMenu->setOnComplete(function ($session, $formData) {
return UssdResponse::end("Thank you, {$formData['name']}! Registration complete.");
});
PaginatedMenu
Display large lists with pagination:
use Moffhub\Ussd\Menus\PaginatedMenu;
$menu = new PaginatedMenu('Select Product', $products, [
'items_per_page' => 5,
'item_formatter' => fn($key, $item) => $item['name'] . ' - KES ' . $item['price'],
]);
ConditionalMenu
Show different menus based on conditions:
use Moffhub\Ussd\Menus\ConditionalMenu;
$menu = new ConditionalMenu($defaultMenu);
$menu->addCondition(
fn($session) => $session->getUserData('role') === 'admin',
$adminMenu
);
$menu->addCondition(
fn($session) => $session->getUserData('is_premium'),
$premiumMenu
);
WizardMenu
Multi-step guided processes:
use Moffhub\Ussd\Menus\WizardMenu;
$wizard = new WizardMenu('Money Transfer');
$wizard->addStep('amount', new SimpleMenu('Enter amount:', ['*' => 'Cancel']));
$wizard->addStep('recipient', new SimpleMenu('Enter recipient:', ['*' => 'Cancel']));
$wizard->addStep('confirm', new SimpleMenu('Confirm transfer?', ['1' => 'Yes', '2' => 'No']));
$wizard->setOnComplete(function ($session, $framework) {
// Process the transfer
return UssdResponse::end('Transfer successful!');
});
Using the MenuBuilder
Fluent interface for building menus:
use Moffhub\Ussd\Builders\MenuBuilder;
$menu = (new MenuBuilder('main_menu'))
->title('Welcome')
->option('1', 'Check Balance', fn($input, $s, $f) => $f->navigateToMenuWithResponse('balance'))
->option('2', 'Send Money')
->option('3', 'Exit', fn() => UssdResponse::end('Goodbye!'))
->build();
Type-Safe Menu Names with Enums
For better type safety and IDE autocomplete, use enums for menu names:
Creating Your Menu Enum
<?php
namespace App\Enums;
use Moffhub\Ussd\Interfaces\MenuNameInterface;
use Moffhub\Ussd\Traits\MenuEnumTrait;
enum AppMenu: string implements MenuNameInterface
{
use MenuEnumTrait;
case Main = 'main';
case Balance = 'balance';
case SendMoney = 'send_money';
case BuyAirtime = 'buy_airtime';
case Settings = 'settings';
public function label(): string
{
return match($this) {
self::Main => 'Main Menu',
self::Balance => 'Check Balance',
self::SendMoney => 'Send Money',
self::BuyAirtime => 'Buy Airtime',
self::Settings => 'Settings',
};
}
}
Using Your Enum
use App\Enums\AppMenu;
// Register menus with enum
$framework->registerMenu(AppMenu::Main, new SimpleMenu('Welcome', [...]));
$framework->registerMenu(AppMenu::Balance, new SimpleMenu('Balance', [...]));
// Navigate with enum
$framework->navigateToMenu(AppMenu::Balance);
$response = $framework->navigateToMenuWithResponse(AppMenu::SendMoney);
// Check if menu exists
if ($framework->hasMenu(AppMenu::Settings)) {
// ...
}
// Helper methods from MenuEnumTrait
AppMenu::values(); // ['main', 'balance', 'send_money', ...]
AppMenu::hasValue('main'); // true
AppMenu::fromValue('main'); // AppMenu::Main
AppMenu::toSelectOptions(); // ['main' => 'Main Menu', ...]
Provider Adapters
The framework automatically detects the USSD provider from the request, or you can specify one:
use Moffhub\Ussd\Providers\ProviderFactory;
// Auto-detect from request
$provider = ProviderFactory::detect($request);
// Or create a specific provider
$provider = ProviderFactory::create('safaricom');
$provider = ProviderFactory::create('airtel', ['country_code' => '256']);
$provider = ProviderFactory::create('mtn', ['country_code' => '234']);
Supported Providers
| Provider | Aliases | Field Mapping |
|---|---|---|
| Safaricom | safaricom, africas_talking, at |
phoneNumber, text, sessionId |
| Airtel | airtel |
msisdn, input/text, transactionId |
| MTN | mtn |
msisdn, UserAnswer, sessionId |
| Generic | generic |
Auto-detect multiple field names |
Register Custom Provider
use Moffhub\Ussd\Providers\ProviderFactory;
use Moffhub\Ussd\Providers\AbstractUssdProvider;
class MyCustomProvider extends AbstractUssdProvider
{
protected string $name = 'custom';
public function getPhoneNumber(Request $request): string
{
return $request->input('mobile_number');
}
// ... implement other methods
}
ProviderFactory::register('custom', MyCustomProvider::class);
Data Providers
ArrayDataProvider
use Moffhub\Ussd\DataProviders\ArrayDataProvider;
$provider = new ArrayDataProvider([
['id' => 1, 'name' => 'Product A', 'price' => 100],
['id' => 2, 'name' => 'Product B', 'price' => 200],
]);
$menu = new PaginatedMenu('Products', $provider);
DatabaseDataProvider
use Moffhub\Ussd\DataProviders\DatabaseDataProvider;
$provider = new DatabaseDataProvider(Product::class);
// Or with custom query
$provider = new DatabaseDataProvider(Product::class, function () {
return Product::where('active', true)->orderBy('name');
});
ApiDataProvider
use Moffhub\Ussd\DataProviders\ApiDataProvider;
$provider = new ApiDataProvider(
'https://api.example.com',
['Content-Type: application/json'],
['type' => 'bearer', 'token' => 'your-api-token']
);
Session Management
Accessing Session Data
// In your menu actions
$menu = new SimpleMenu('Menu', ['1' => 'Action'], [
'1' => function ($input, $session, $framework) {
// Get session data
$name = $session->get('name');
$step = $session->get('step', 0);
// Set session data
$session->set('last_action', 'viewed_balance');
// Form data
$formData = $session->getFormData();
// User data (preserved across sessions)
$userId = $session->getUserData('user_id');
return UssdResponse::continue("Hello, $name!");
},
]);
Session Recovery
The framework automatically handles session recovery:
$framework = new UssdFramework([
'grace_period' => 600, // 10 minutes
'enable_intelligent_recovery' => true,
'enable_context_preservation' => true,
]);
Security Features
Rate Limiting
$framework = new UssdFramework([
'security' => [
'rate_limiting' => true,
],
]);
// Configure rate limits with static lists
$rateLimiter = new UssdRateLimiter([
'max_requests_per_minute' => 10,
'max_requests_per_hour' => 100,
'max_requests_per_day' => 500,
'whitelist' => ['+254700000000'],
'blacklist' => ['+254999999999'],
]);
Database-Backed Whitelist/Blacklist
For production applications, use database-backed access lists that can be managed via a UI:
use Moffhub\Ussd\Security\UssdRateLimiter;
use Moffhub\Ussd\Security\DatabaseAccessListProvider;
// Enable database-backed lists
$rateLimiter = new UssdRateLimiter([
'use_database_lists' => true,
'max_requests_per_minute' => 10,
]);
// Or set a custom provider
$rateLimiter->setAccessListProvider(new DatabaseAccessListProvider([
'cache_ttl' => 300, // Cache for 5 minutes
'cache_enabled' => true,
]));
// Manage whitelist/blacklist programmatically
$rateLimiter->addToWhitelist('+254712345678', 'VIP customer', 'admin@example.com');
$rateLimiter->addToBlacklist('+254999999999', 'Spam detected', 'system', now()->addDays(7));
$rateLimiter->removeFromWhitelist('+254712345678');
$rateLimiter->removeFromBlacklist('+254999999999');
Using the UssdAccessList Model Directly
Build your own admin UI using the Eloquent model:
use Moffhub\Ussd\Models\UssdAccessList;
// Add to whitelist with expiration
UssdAccessList::addToWhitelist(
phoneNumber: '+254712345678',
reason: 'VIP customer',
addedBy: auth()->user()->email,
expiresAt: now()->addMonths(6),
metadata: ['customer_id' => 123]
);
// Add to blacklist
UssdAccessList::addToBlacklist(
phoneNumber: '+254999999999',
reason: 'Abuse detected',
addedBy: 'system'
);
// Query for your admin panel
$whitelist = UssdAccessList::whitelist()->active()->paginate(20);
$blacklist = UssdAccessList::blacklist()->active()->paginate(20);
// Check status
UssdAccessList::isWhitelisted('+254712345678'); // true
UssdAccessList::isBlacklisted('+254999999999'); // true
// Get all numbers
$whitelistedNumbers = UssdAccessList::getWhitelistedNumbers();
$blacklistedNumbers = UssdAccessList::getBlacklistedNumbers();
Custom Access List Provider
Implement your own storage backend:
use Moffhub\Ussd\Interfaces\AccessListProviderInterface;
class RedisAccessListProvider implements AccessListProviderInterface
{
public function isWhitelisted(string $phoneNumber): bool
{
return Redis::sismember('ussd:whitelist', $phoneNumber);
}
public function isBlacklisted(string $phoneNumber): bool
{
return Redis::sismember('ussd:blacklist', $phoneNumber);
}
// ... implement other methods
}
$rateLimiter->setAccessListProvider(new RedisAccessListProvider());
Input Sanitization
$framework = new UssdFramework([
'security' => [
'input_sanitization' => true,
'strict_mode' => false, // Set to true to reject suspicious input
],
]);
Validators
Built-in validators for form fields:
use Moffhub\Ussd\Helpers\Validators;
$field = new FormField(
name: 'email',
prompt: 'Enter email:',
validators: [
Validators::required(),
Validators::email(),
]
);
// Available validators:
Validators::required($message)
Validators::minLength($min, $message)
Validators::maxLength($max, $message)
Validators::length($min, $max, $message)
Validators::numeric($message)
Validators::phone($message)
Validators::email($message)
Validators::inOptions($options, $message)
Validators::regex($pattern, $message)
Validators::dob($message, $minAge)
Validators::custom($callback, $message)
Response Types
use Moffhub\Ussd\UssdResponse;
// Continue session
UssdResponse::continue('Select an option:');
// End session
UssdResponse::end('Thank you for using our service!');
// With menu
UssdResponse::menu('Main Menu', ['1' => 'Option 1', '2' => 'Option 2']);
// Form field
UssdResponse::form('Enter your name:', true, $validationError);
// Pagination
UssdResponse::pagination($content, $currentPage, $totalPages, $hasNext, $hasPrevious);
// Progress indicator
UssdResponse::progress('Processing...', $currentStep, $totalSteps);
// Confirmation
UssdResponse::confirmation('Are you sure?');
// Error
UssdResponse::error('Something went wrong', $endSession);
// Success
UssdResponse::success('Operation completed!');
Configuration
Full configuration options:
$framework = new UssdFramework([
// Basic settings
'session_timeout' => 300,
'session_prefix' => 'ussd_session_',
'default_menu' => 'main',
'sms_length' => 160,
// Navigation
'navigation' => [
'back' => '99',
'home' => '0',
'next' => '00',
'search' => '98',
],
'global_navigation' => ['enabled' => true],
// Session management
'grace_period' => 600,
'enable_session_migration' => true,
'enable_context_preservation' => true,
'enable_intelligent_recovery' => true,
// Security
'security' => [
'rate_limiting' => true,
'input_sanitization' => true,
'audit_logging' => true,
'strict_mode' => false,
],
// Analytics
'analytics' => [
'enabled' => true,
'track_user_journey' => true,
'track_performance' => true,
],
// Caching
'cache' => [
'enabled' => true,
'menu_content_ttl' => 3600,
'data_provider_ttl' => 600,
],
// Database
'database' => [
'enabled' => true,
'save_sessions' => true,
'save_analytics' => true,
'anonymize_phone_numbers' => true,
],
// Provider
'provider' => [
'default' => 'generic',
'auto_detect' => true,
'country_code' => '254',
'max_message_length' => 182,
],
]);
Publishing the config
php artisan vendor:publish --tag=ussd-config
This writes config/ussd.php. The published file is the authoritative control
surface: framework defaults are deep-merged with config('ussd') and then with
any array passed to new UssdFramework([...]) (nearest wins, nested blocks merge
recursively rather than replacing wholesale).
Config reference (selected keys)
| Key | Env var | Default | Purpose |
|---|---|---|---|
debug |
USSD_DEBUG |
false |
Rethrow menu exceptions instead of the generic error (dev/test). |
validate_menu_references |
USSD_VALIDATE_MENU_REFERENCES |
true |
UssdBuilder::build() fails fast on navigation to an unregistered menu. |
deduplication.enabled |
USSD_DEDUPE_ENABLED |
true |
Replay the cached response for a retried (session, input). |
deduplication.window |
USSD_DEDUPE_WINDOW |
5 |
Dedupe window, in seconds. |
session_timeout |
USSD_SESSION_TIMEOUT |
300 |
Inactivity (seconds) before a session expires; also the sweeper's default. |
security.rate_limiting |
USSD_RATE_LIMITING_ENABLED |
true |
Enable the rate limiter. |
rate_limiting.max_requests_per_minute |
USSD_RATE_LIMIT_PER_MINUTE |
60 |
Per-minute request cap (tuned for interactive menus). |
rate_limiting.max_requests_per_hour |
USSD_RATE_LIMIT_PER_HOUR |
600 |
Per-hour request cap. |
rate_limiting.max_requests_per_day |
USSD_RATE_LIMIT_PER_DAY |
3000 |
Per-day request cap. |
rate_limiting.use_database_lists |
USSD_RATE_LIMIT_USE_DATABASE_LISTS |
true |
Use the DB-backed whitelist/blacklist (needs the ussd_access_lists table). |
database.enabled |
USSD_DATABASE_ENABLED |
true |
Enable the database layer (sessions, analytics, stats). |
Session tables
Two tables, different jobs:
ussd_user_sessionsis the runtime session store: one row per live/finished session (menu, session data, activity timestamps, completion). This is what the cleanup and sweeper commands operate on, and what you query for live sessions.ussd_sessionsis used by access-management/admin tooling, not the normal request path, so it is empty in a plain menu deployment. If you are looking for "where are my sessions", it isussd_user_sessions.
Debugging: surface the real error
By default the framework catches menu exceptions and returns a generic message.
Set debug (or USSD_DEBUG=true) to rethrow the underlying exception so you see
the real cause in dev/test. The on_error hook fires either way:
$framework->addHook('on_error', function (\Throwable $e) { /* report */ });
Swapping components
Rate limiter, input sanitizer, audit logger and the session store resolve from the container. Bind your own to replace the default without forking:
use Moffhub\Ussd\Interfaces\RateLimiterInterface;
use Moffhub\Ussd\Interfaces\SessionStoreInterface;
app()->bind(RateLimiterInterface::class, MyRateLimiter::class);
// Change where live session state is held (default: CacheSessionStore).
app()->bind(SessionStoreInterface::class, MyRedisSessionStore::class);
Or via the builder:
UssdBuilder::create()
->rateLimiter(new MyRateLimiter())
->inputSanitizer(new MyInputSanitizer())
->provider(new MyProvider())
->build();
Typed config access
getConfig() still returns the raw array. config() additionally gives a typed,
immutable view with dot-notation and a recursive merge() (so a partial override
never wipes sibling keys):
$framework->config()->deduplicationWindow(); // int, default 5
$framework->config()->get('security.rate_limiting'); // dot-notation
$merged = $framework->config()->merge(['security' => ['audit_logging' => false]]);
Metrics
Bind MetricsRecorderInterface to receive funnel metrics (menu entered,
completed, abandoned, dwell). Everything is keyed by menu only (never by phone
or session) to keep cardinality bounded. The default is a no-op.
Testing menus
UssdTester drives a session without a gateway and with test-friendly defaults:
use Moffhub\Ussd\Testing\UssdTester;
UssdTester::fake(['default_menu' => 'main'])
->register('main', $mainMenu)
->register('airtime', $airtimeMenu)
->drive(['3', '1']) // dial, then send each input
->assertEnded()
->assertSee('successful');
Facade
Use the Ussd facade for convenient access:
use Moffhub\Ussd\Facades\Ussd;
$response = Ussd::handle($request);
Internationalization (i18n)
Built-in multi-language support:
// Using the helper function
$message = __ussd('errors.invalid_option');
$message = __ussd('navigation.page_info', ['current' => '1', 'total' => '5']);
$message = __ussd('validation.min_length', ['min' => '3'], 'sw');
// Per-session language selection
use Moffhub\Ussd\Services\TranslationService;
$translation = app(TranslationService::class);
$translation->setSessionLocale($session, 'sw');
$message = $translation->translateForSession('navigation.back', $session);
Publish and customize language files:
php artisan vendor:publish --tag=ussd-lang
Configure in config/ussd.php:
'localization' => [
'default_locale' => 'en',
'supported_locales' => ['en', 'sw', 'fr'],
],
Signature Verification
Verify incoming provider requests using HMAC signatures:
USSD_VERIFY_SIGNATURES=true
USSD_SAFARICOM_SECRET=your-api-key
USSD_AIRTEL_SECRET=your-callback-token
USSD_MTN_SECRET=your-hmac-secret
Circuit Breaker
Protect external API calls from cascading failures:
USSD_CIRCUIT_BREAKER_THRESHOLD=5
USSD_CIRCUIT_BREAKER_COOLDOWN=60
The circuit breaker is automatically integrated into ApiDataProvider. It supports three states:
- Closed: All requests pass through normally
- Open: Requests return fallback data (after threshold failures)
- Half-open: One test request allowed after cooldown period
Session Encryption
Encrypt sensitive session data at rest:
USSD_ENCRYPT_SESSION_DATA=true
Configure which fields to encrypt in config/ussd.php:
'security' => [
'encrypt_session_data' => true,
'encrypted_fields' => ['form_data.pin', 'form_data.account_number'],
],
Events
The framework dispatches Laravel events at key lifecycle points:
| Event | Payload |
|---|---|
SessionStarted |
sessionId, phone, provider |
SessionResumed |
sessionId, phone, wasRecovered |
SessionEnded |
sessionId, phone, duration, menusVisited |
SessionExpired |
sessionId, phone, lastMenu |
MenuEntered |
sessionId, menuName, fromMenu |
MenuExited |
sessionId, menuName, toMenu, selection |
FormSubmitted |
sessionId, menuName, formData |
InputReceived |
sessionId, menuName, rawInput |
NavigationPerformed |
sessionId, action |
Listen to events:
use Moffhub\Ussd\Events\FormSubmitted;
Event::listen(FormSubmitted::class, function (FormSubmitted $event) {
ProcessRegistration::dispatch($event->formData);
});
Artisan Commands
# Clean up expired sessions (delete old rows beyond retention)
php artisan ussd:cleanup-sessions --older-than=24h
php artisan ussd:cleanup-sessions --dry-run
# Finalize abandoned sessions: record drop-off, emit SessionExpired, mark ended.
# Schedule this (e.g. every minute) so abandonment is measurable from events.
php artisan ussd:sweep-sessions
php artisan ussd:sweep-sessions --timeout=180 --dry-run
# Interactive local simulator (walk the menu in the terminal, no phone needed)
php artisan ussd:simulate --phone=254700000000 --provider=safaricom
# List active sessions
php artisan ussd:list-sessions
php artisan ussd:list-sessions --provider=safaricom
# Manage whitelist/blacklist
php artisan ussd:manage-access add --type=whitelist --phone=+254712345678
php artisan ussd:manage-access list --type=blacklist
# Health check
php artisan ussd:health
Middleware
Two middleware are registered automatically:
// Gateway authentication (IP + signature verification)
Route::post('/ussd', [UssdController::class, 'handle'])->middleware('ussd.auth');
// Per-phone rate limiting
Route::post('/ussd', [UssdController::class, 'handle'])->middleware('ussd.rate-limit');
// Combined
Route::post('/ussd', [UssdController::class, 'handle'])->middleware(['ussd.auth', 'ussd.rate-limit']);
Production Deployment
See docs/PRODUCTION.md for a detailed production deployment guide including:
- Deployment checklist
- Cache driver selection (Redis recommended)
- Database migration verification
- Provider credential setup
- Rate limit tuning
- Session timeout configuration
- Analytics retention policy
- Performance tuning guide
- Troubleshooting guide
Quick Checklist
- Set cache driver to Redis:
CACHE_DRIVER=redis - Run migrations:
php artisan migrate - Configure provider secrets in
.env - Enable signature verification:
USSD_VERIFY_SIGNATURES=true - Enable session encryption if handling PINs:
USSD_ENCRYPT_SESSION_DATA=true - Schedule session cleanup:
ussd:cleanup-sessions - Start queue worker for async analytics:
USSD_ANALYTICS_FLUSH_STRATEGY=async - Configure gateway IPs:
USSD_GATEWAY_AUTH_ENABLED=true - Verify health:
php artisan ussd:health
Local Development
USSD Simulator
Test your USSD flows interactively from the command line without needing a real gateway:
php artisan ussd:simulate
This starts an interactive CLI session that simulates a USSD flow. It sends requests to your registered menus, displays responses, and prompts for input just like a real USSD session.
Options
# Use a specific phone number
php artisan ussd:simulate --phone=254712345678
# Use a specific provider adapter (safaricom, airtel, mtn, generic)
php artisan ussd:simulate --provider=safaricom
# Use a specific service code
php artisan ussd:simulate --service-code=*456#
# Combine options
php artisan ussd:simulate --phone=254712345678 --provider=safaricom --service-code=*456#
How It Works
- The simulator generates a unique session ID and sends an initial request to
UssdFramework::handle() - The response is displayed in the terminal with
[CON](continue) or[END](end) indicators - If the response is
CON, you are prompted to enter input - The simulator sends a follow-up request with your input
- This loop continues until the response is
ENDor you typeexit - A session summary is displayed at the end with total steps and duration
Testing
Run the test suite:
composer test
Or with PHPUnit directly:
./vendor/bin/phpunit
Contributing
- Fork the repository
- Create a feature branch
- Write tests for your changes
- Submit a pull request
License
This package is open-sourced software licensed under the MIT license.
Credits
Related Packages
Unified SMS gateway for Laravel with multi-provider support (Advanta, Africa's T...
Feature-based billing for Laravel with African payment provider support (M-Pesa,...
Laravel package to manage current pesapal payment api, can be used for web and r...
Instant mobile detection access directly from within Blade templates.
Laravel Auth Checker allows you to log users authentication, devices authenticat...
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v0.1.14 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.13 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.12 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.11 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.10 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.9 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.8 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.7 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.6 | ^8.4 | | ^12.0 | | MIT | |
| v0.1.5 | ^8.5 | ^12.0 | MIT | |
| v0.1.4 | ^8.5 | ^12.0 | MIT | |
| v0.1.3 | ^8.5 | ^12.0 | MIT | |
| v0.1.2 | ^8.5 | ^12.0 | MIT | |
| v0.1.1 | ^8.5 | ^12.0 | MIT | |
| v0.1.0 | ^8.5 | ^12.0 | MIT |