定制 bmatovu/laravel-ussd 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

bmatovu/laravel-ussd

Composer 安装命令:

composer require bmatovu/laravel-ussd

包简介

Laravel USSD Builder

README 文档

README

License Unit Tests Code Quality Code Coverage Documentation

Table of Contents

Overview

Effortlessly construct intricate USSD menus with streamlined efficiency by replacing convoluted nests of PHP files with the simplicity of XML-based menu construction. This approach allows for seamless execution similar to standard PHP scripts, minimizing code complexity and enhancing readability.

Let's explore an example of a simple SACCO USSD application.

<menu name="sacco">
    <action name="check_user" />
    <options header="SACCO Services" noback="no">
        <option text="Savings">
            <list header="Saving Accounts" provider="saving_accounts" prefix="account" />
            <options header="Savings">
                <option text="Deposit">
                    <options header="Deposit From:">
                        <option text="My Number">
                            <variable name="sender" value="{{phone_number}}" />
                        </option>
                        <option text="Another Number">
                            <question name="sender" text="Enter Phone Number: " />
                        </option>
                    </options>
                    <question name="amount" text="Enter Amount: " />
                    <action name="deposit" />
                </option>
                <option text="Withdraw">
                    <options header="Withdraw To:">
                        <option text="My Number">
                            <variable name="receiver" value="{{phone_number}}" />
                        </option>
                        <option text="Another Number">
                            <question name="receiver" text="Enter Phone Number: " />
                        </option>
                    </options>
                    <question name="amount" text="Enter Amount: " />
                    <action name="withdraw" />
                </option>
                <option text="Check Balance">
                    <action name="check_balance" text="To see your balance, enter PIN: " />
                </option>
                <option text="Check Transaction">
                    <question name="transaction_id" text="Enter Transaction ID: " />
                    <action name="check_transaction" text="To check transaction, enter PIN: " />
                </option>
            </options>
        </option>
        <option text="Loans">
            <response text="Coming soon." />
        </option>
    </options>
</menu>

Getting started

Installation

composer require bmatovu/laravel-ussd

Publishables

php artisan vendor:publish --provider="Bmatovu\Ussd\UssdServiceProvider"

Usage

Example

menus/menu.xml

<?xml version="1.0" encoding="UTF-8"?>
<menu name="demo">
    <question name="guest" text="Enter Name: " />
    <response text="Hello {{guest}}." />
</menu>

app/Http/Controller/UssdController.php

namespace App\Http\Controllers;

use Bmatovu\Ussd\Exceptions\FlowBreakException;
use Bmatovu\Ussd\Ussd;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

/**
 * @see https://developers.africastalking.com/docs/ussd/overview
 */
class UssdController extends Controller
{
    public function __invoke(Request $request): Response
    {
        try {
            $output = Ussd::make('menu.xml', $request->sessionId)->handle($request->text);
        } catch(FlowBreakException $ex) {
            return response('END ' . $ex->getMessage());
        } catch(\Exception $ex) {
            return response('END ' . get_class($ex));
        }

        return response('CON ' . $output);
    }
}

routes/api.php

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

Route::post('/ussd', [UssdController::class, '__invoke']);

See more examples in the demo repo

Menu validation

The Schema

You can publish the default schema with the following command:

php artisan vendor:publish --provider="Bmatovu\Ussd\UssdServiceProvider" --tag="ussd-schema"

To ensure your menu files conform to the schema, you can validate them with the following command:

php artisan ussd:validate

VSCode integration

For real-time XSD validations and suggestions, you can use the RedHat XML extension in Visual Studio Code. This extension provides helpful features for working with XML schemas, including syntax highlighting and validation.

<?xml version="1.0" encoding="UTF-8"?>
- <menu name="demo">
+ <menu name="demo"
+     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+     xsi:noNamespaceSchemaLocation="menu.xsd">
      <question name="guest" text="Enter Name: " />
      <response text="Hello {{guest}}." />
  </menu>

Simulator

The package includes a CLI USSD simulator that supports several popular aggregators.

To get started, publish the simulator configuration file and update it with your aggregator and USSD service endpoint.

php artisan vendor:publish --provider="Bmatovu\Ussd\UssdServiceProvider" --tag="ussd-simulator"

Usage:

./vendor/bin/ussd --help
./vendor/bin/ussd 256772100103

Aggregators

  • Africastalking
  • Comviva (Airtel & MTN)

If your aggregator is not listed, you can request its addition by contacting us or by submitting a pull request.

Constructs

Variable

$color = 'blue';
<variable name="color" value="blue"/>

Note: This tag has no output

Question

$username = readline('Enter username: ');
<question name="username" text="Enter username: "/>

Response

exit('Thank you for banking with us.');
<response text="Thank you for banking with us."/>

Note: this tag throws a FlowBreakException to mark a break in the normal flow.

Options

Options are like named grouped if, else-if statements that allow a user to navigate to a predefined path.

$choice = readline('Choose service [1. Deposit, 2. Withdraw]: ');

if($choice === 1) {
    // deposit...
} elseif($choice === 2) {
    // withdraw...
}
<options header="Choose service">
    <option text="Deposit">
        <!-- ... -->
    </option>
    <option text="Withdraw">
        <!-- ... -->
    </option>
</options>

Disable backward navigation

By default, a 0) Back option is added to the rendered options. To disable this behaviour, use the noback attribute.

Note: Top-level options should use the noback attribute as there’s no previous level to return to.

<options header="Choose service" noback="no">
    <!-- ... -->
</options>

If

It can contain any other tags, including nested <if> tags.

if($role == 'treasurer') {
    // ...
}
<if key="role" value="treasurer">
    <!-- ... -->
</if>

Choose

This construct is intended to handle scenarios typically covered by if, else if, else, and switch statements.

Example #1

if($role == 'treasurer') {
    // ...
} else {
    // ...
}
<choose>
    <when key="role" value="treasurer">
        <!-- ... -->
    </when>
    <otherwise>
        <!-- ... -->
    </otherwise>
</choose>

Example #2

if($role == 'treasurer') {
    // ...
} elseif($role == 'member') {
    // ...
} else {

}
<choose>
    <when key="role" value="treasurer">
        <!-- ... -->
    </when>
    <when key="role" value="member">
        <!-- ... -->
    </when>
    <otherwise>
        <!-- ... -->
    </otherwise>
</choose>

Example #3

switch ($role) {
    case "treasurer":
        // ...
        break;
    case "member":
        // ...
        break;
    default:
        // ...
}
<choose>
    <when key="role" value="treasurer">
        <!-- ... -->
    </when>
    <when key="role" value="memeber">
        <!-- ... -->
    </when>
    <otherwise>
        <!-- ... -->
    </otherwise>
</choose>

Action

Action tags enable you to execute more tailored operations.

$userInfo = \App\Ussd\Actions\GetUserInfoAction('256732000000');

Arguments

You can provide arguments for these actions either through attributes or by defining variables.

<!-- Read from cache -->
<!-- $msisdn = $this->store->get('msisdn'); -->
<action name="get_user_info"/>

<!-- Pass as attribute -->
<action name="get_user_info" msisdn="{{msisdn}}"/>

<!-- Pass as variable -->
<action name="get_user_info">
    <variable name="msisdn" value="{{msisdn}}"/>
</action>

Getting user input

When the text attribute is included in an action tag, it prompts the user for input in the same way that a <question> tag would.

<!-- Approach #1 - user input handled by a qn tag -->
<question name="pin" text="To check balance, enter PIN: "/>
<action name="validate_pin"/>

<!-- Approach #2 - user input handled by the action -->
<action name="validate_pin" text="To check balance, enter PIN: "/>

List

Lists are designed to show dynamic items.

To use this feature, your provider must supply a list where each item includes both an id (a unique identifier) and a label (the text displayed to the user).

$listItems = (new \App\Ussd\Providers\SavingAccountsProvider)->load();

[
    [
        'id' => 4364852, // account_id 
        'label' => '01085475262', // account_number
    ],
];
<list header="Saving Accounts" provider="saving_accounts" prefix="account"/>

Accessing the selected item on the list

<!-- Format: {prefix}_<id, label> -->
<response text="{{account_id}}"/><!-- 4364852 -->
<response text="{{account_label}}"/><!-- 01085475262 -->

Note: Similar to actions, you can pass arguments to lists via attributes or as variables.

Advanced

Exceptions

The <response> tag throws a FlowBreakException which MUST be handled in your controller to manage the flow of the USSD interaction.

Additionally, you can catch other exceptions and optionally translate them into user-friendly messages, as demonstrated below...

try {
    $output = Ussd::make('menu.xml', $request->session_id)
        ->handle($request->text);
} catch(FlowBreakException $ex) {
    return response('END ' . $ex->getMessage());
} catch(\Exception $ex) {
    // return response('END ' . get_class($ex));
    return response('END ' . trans(get_class($ex)));
}

return response('CON ' . $output);

resources/lang/en.json

{
    "RequestException": "Sorry, we failed to process your request.",
    "TimeoutException": "Your request has timed out.",
    "AuthenticationException": "Invalid user credentials.",
    "AuthorizationException": "You are not authorized to perform this action."
}

Note: To minimize logging, the FlowBreakException should not be reported by your application. Ref

Retries

You can also configure the number of retry attempts and specify a custom error message.

Question

Using regex patterns.

  <question
      name="pin"
      text="Enter PIN: "
+     retries="1"
+     pattern="^[0-9]{5}$"
+     error="You entered the wrong PIN. Try again" />

Options & Lists

Validation can also be performed based on the available options in the list.

  <options
      header="Choose a test"
+     retries="1"
+     error="Choose the correct number:">
      ...
  </option>
  <list 
      header="Saving Accounts" 
      provider="saving_accounts" 
      prefix="account" 
+     retries="1"
+     error="Choose the correct number:"/>

Note: Using retries in <action> tags is not recommended because these tags do not have visibility into the context provided by preceding tags.

Comparisons

The <if> and <when> tags support various types of comparisons.

If a comparison condition (cond) is not specified or is unsupported, the default comparison is eq (equals)

<if key="age" value="18">
<if key="age" cond="eq" value="18">
Type Conditions
Numbers - lt
- gt
- lte
- gte
- eq
- ne
- btn
Strings - str.equals
- str.not_equals
- str.starts
- str.ends
- str.contains
Regex - regex.match
Arrays - arr.in
- arr.not_in
Dates - date.equals
- date.before
- date.after
- date.between
Time - time.equals
- time.before
- time.after
- time.between

Localization

Create translation files within your project and reference the keys in your menu files. Here’s an example:

menus/menu.xml

<menu name="demo">
    <action name="set_locale" locale="fr" />
    <question name="guest" text="AskForName" />
    <response text="GreetGuest" />
</menu>

resources/lang/fr.json

{
    "AskForName": "Entrez le nom:",
    "GreetGuest": "Boujour {{guest}}"
}

USSD simulation

ussd-demo$ vendor/bin/ussd 250723000123
Entrez le nom: 
John

Boujour John

Note:

  • use the set_locale action to change locale directly from the USSD menu, and
  • use App::setLocale to change the locale from your controller

Cache

This package stores USSD session data in the cache. Each key is prefixed with the session_id and will automatically expire based on the configured ttl (time-to-live).

Accessing variables

<variable name="color" value="blue"/>
$this->store->get('color'); // blue

Cache::store($driver)->get("{$sessionId}color"); // blue

Reusing existing variables

<variable name="msg" value="Bye bye."/>

<response text="{{msg}}"/> <!-- Bye bye -->

Parser

Save default variables

Here's an example of how to save a variable from an incoming USSD request:

Ussd::make($menu, $request->session_id)
    ->save([
        'phone_number' => $request->phone_number,
    ])
    ->handle(...);

Use custom menu entry point

By default, parsing begins at the first element in your menu file, which corresponds to /menu/*[1].

If you want to start parsing from a different point or use a custom menu structure, you can specify the entry point in your code:

Ussd::make($menu, $request->session_id)
    ->entry("/menus/menu[@name='sacco']/*[1]")
    ->handle(...);

See: xpath playground

Simulation

You can enhance the USSD simulator by adding your preferred aggregator.

To do this, register the aggregator in the simulator configuration file. Ensure that the provider class implements the Bmatovu\Ussd\Contracts\Aggregator interface.

simulator.json

  {
+     "aggregator": "africastalking",
      "aggregators": {
+         "africastalking": {
+             "provider": "App\\Ussd\\Simulator\\Africastalking",
+             "uri": "http://localhost:8000/api/ussd",
+             "service_code": "*123#"
+         }
      }
  }

JSON

XML is often preferred for constructing configurations that resemble programming logic due to its robust schema validation capabilities and its clear, hierarchical structure. XML’s format is particularly useful for complex configurations and data structures, as it maintains readability and provides straightforward validation against defined schemas.

<menu name="demo">
    <question name="guest" text="Enter Name: "/>
    <response text="Hello {{guest}}."/>
</menu>
{
    "@name": "demo",
    "question": {
        "@name": "guest",
        "@text": "Enter Name:"
    },
    "response": {
        "@text": "Hello {{guest}}."
    }
}

Testing

To run the package's unit tests, run the following command:

composer test

Alternatives

bmatovu/laravel-ussd 适用场景与选型建议

bmatovu/laravel-ussd 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 453 次下载、GitHub Stars 达 15, 最近一次更新时间为 2022 年 05 月 27 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「package」 「xml」 「menu」 「builder」 「laravel」 「lumen」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 bmatovu/laravel-ussd 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 bmatovu/laravel-ussd 我们能提供哪些服务?
定制开发 / 二次开发

基于 bmatovu/laravel-ussd 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 453
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 15
  • 点击次数: 14
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 15
  • Watchers: 0
  • Forks: 10
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2022-05-27