bmatovu / laravel-ussd
Laravel USSD 构建器
Requires
- php: ^8.1
- illuminate/container: ^10.0|^11.0
- illuminate/contracts: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
- nesbot/carbon: ^2.3|^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- guzzlehttp/guzzle: ^7.5
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0|^11.0
- symfony/console: ^6.2|^7.0
README
目录
概述
通过用基于 XML 的菜单构造的简单性替换复杂的 PHP 文件嵌套,轻松高效地构建复杂的 USSD 菜单。这种方法可以实现与标准 PHP 脚本类似的执行,最小化代码复杂性并提高可读性。
让我们探索一个简单的 SACCO USSD 应用程序的示例。
<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>
入门指南
安装
composer require bmatovu/laravel-ussd
可发布文件
php artisan vendor:publish --provider="Bmatovu\Ussd\UssdServiceProvider"
用法
示例
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']);
更多示例请参阅 demo 仓库
菜单验证
模式
您可以使用以下命令发布默认模式
php artisan vendor:publish --provider="Bmatovu\Ussd\UssdServiceProvider" --tag="ussd-schema"
为了确保您的菜单文件符合模式,您可以使用以下命令进行验证
php artisan ussd:validate
VSCode 集成
为了实时 XSD 验证和建议,您可以在 Visual Studio Code 中使用 RedHat XML 扩展。此扩展提供了一些有助于使用 XML 模式的功能,包括语法高亮和验证。
<?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>
模拟器
此软件包包含一个 CLI USSD 模拟器,支持几个流行的聚合器。
要开始,发布模拟器配置文件,并用您的聚合器和 USSD 服务端点更新它。
php artisan vendor:publish --provider="Bmatovu\Ussd\UssdServiceProvider" --tag="ussd-simulator"
用法
./vendor/bin/ussd --help ./vendor/bin/ussd 256772100103
聚合器
- Africastalking
- Comviva (Airtel & MTN)
如果您的聚合器未列出,您可以通过联系我们或提交拉取请求来请求添加它。
结构
变量
$color = 'blue';
<variable name="color" value="blue"/>
注意:此标签没有输出
问题
$username = readline('Enter username: ');
<question name="username" text="Enter username: "/>
响应
exit('Thank you for banking with us.');
<response text="Thank you for banking with us."/>
注意:此标签抛出 FlowBreakException
以标记正常流程的断裂。
选项
选项类似于命名的分组 if, else-if
语句,允许用户导航到预定义的路径。
$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>
禁用向后导航
默认情况下,渲染选项中会添加一个 0) Back
选项。要禁用此行为,请使用 noback
属性。
注意:顶级选项应使用 noback
属性,因为没有先前的级别可以返回。
<options header="Choose service" noback="no"> <!-- ... --> </options>
如果
它可以包含任何其他标签,包括嵌套的 <if>
标签。
if($role == 'treasurer') { // ... }
<if key="role" value="treasurer"> <!-- ... --> </if>
选择
此结构旨在处理通常由 if
、else if
、else
和 switch
语句覆盖的场景。
示例 #1
if($role == 'treasurer') { // ... } else { // ... }
<choose> <when key="role" value="treasurer"> <!-- ... --> </when> <otherwise> <!-- ... --> </otherwise> </choose>
示例 #2
if($role == 'treasurer') { // ... } elseif($role == 'member') { // ... } else { }
<choose> <when key="role" value="treasurer"> <!-- ... --> </when> <when key="role" value="member"> <!-- ... --> </when> <otherwise> <!-- ... --> </otherwise> </choose>
示例 #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>
操作
操作标签使您能够执行更定制的操作。
$userInfo = \App\Ussd\Actions\GetUserInfoAction('256732000000');
参数
您可以通过属性或定义变量为这些操作提供参数。
<!-- 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>
获取用户输入
当在操作标签中包含 text
属性时,它会以与 <question>
标签相同的方式提示用户输入。
<!-- 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: "/>
列表
列表旨在显示动态项。
要使用此功能,您的提供者必须提供包含 id
(唯一标识符)和 label
(显示给用户的文本)的每个项目的列表。
$listItems = (new \App\Ussd\Providers\SavingAccountsProvider)->load(); [ [ 'id' => 4364852, // account_id 'label' => '01085475262', // account_number ], ];
<list header="Saving Accounts" provider="saving_accounts" prefix="account"/>
访问列表中选定的项目
<!-- Format: {prefix}_<id, label> --> <response text="{{account_id}}"/><!-- 4364852 --> <response text="{{account_label}}"/><!-- 01085475262 -->
注意:与操作类似,您可以通过属性或作为变量传递参数给列表。
高级
异常
《<response>
标签抛出 FlowBreakException
异常,必须在您的控制器中处理以管理 USSD 交互的流程。
此外,您可以捕获其他异常,并将它们可选地转换为用户友好的消息,如下所示...
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." }
注意:为了最小化日志记录,FlowBreakException 不应通过您的应用程序报告。参考
重试
您还可以配置重试次数并指定自定义错误消息。
问题
使用正则表达式模式。
<question name="pin" text="Enter PIN: " + retries="1" + pattern="^[0-9]{5}$" + error="You entered the wrong PIN. Try again" />
选项 & 列表
还可以根据列表中可用的选项执行验证。
<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:"/>
注意:在 <action>
标签中使用重试不被推荐,因为这些标签无法看到前面标签提供的环境。
比较
《<if>
和 <when>
标签支持各种类型的比较。
如果未指定比较条件(cond
)或条件不受支持,则默认比较为 eq
(等于)
<if key="age" value="18"> <if key="age" cond="eq" value="18">
本地化
在您的项目中创建翻译文件,并在菜单文件中引用键。以下是一个示例
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 模拟
ussd-demo$ vendor/bin/ussd 250723000123
Entrez le nom:
John
Boujour John
注意:
- 使用
set_locale
操作来直接从 USSD 菜单更改区域设置, - 使用
App::setLocale
从您的控制器中更改区域设置
缓存
此包将 USSD 会话数据存储在缓存中。每个键都以前缀 session_id
开头,并将根据配置的 ttl
(生存时间)自动过期。
访问变量
<variable name="color" value="blue"/>
$this->store->get('color'); // blue Cache::store($driver)->get("{$sessionId}color"); // blue
重用现有变量
<variable name="msg" value="Bye bye."/> <response text="{{msg}}"/> <!-- Bye bye -->
解析器
保存默认变量
以下是从传入的 USSD 请求中保存变量的示例
Ussd::make($menu, $request->session_id) ->save([ 'phone_number' => $request->phone_number, ]) ->handle(...);
使用自定义菜单入口点
默认情况下,解析从您的菜单文件中的第一个元素开始,对应于 /menu/*[1]
。
如果您想从不同的点开始解析或使用自定义菜单结构,您可以在代码中指定入口点
Ussd::make($menu, $request->session_id) ->entry("/menus/menu[@name='sacco']/*[1]") ->handle(...);
模拟
您可以通过添加您喜欢的聚合器来增强 USSD 模拟器。
为此,在模拟器配置文件中注册聚合器。确保提供者类实现了 Bmatovu\Ussd\Contracts\Aggregator
接口。
simulator.json
{ + "aggregator": "africastalking", "aggregators": { + "africastalking": { + "provider": "App\\Ussd\\Simulator\\Africastalking", + "uri": "https://:8000/api/ussd", + "service_code": "*123#" + } } }
JSON
由于 XML 具有强大的模式验证功能以及清晰、分层的结构,因此它通常用于构建类似于编程逻辑的配置。XML 的格式对于复杂配置和数据结构特别有用,因为它保持了可读性,并提供了对定义的模式的直接验证。
<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}}." } }
测试
要运行包的单元测试,请运行以下命令
composer test
替代方案
- sparors/laravel-ussd 提供了构建 USSD 菜单的不同方法。