bmatovu/laravel-ussd

Laravel USSD 构建器

v2.0.0 2024-08-18 13:47 UTC

This package is auto-updated.

Last update: 2024-09-19 16:32:13 UTC


README

License Unit Tests Code Quality Code Coverage Documentation

目录

概述

通过用基于 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>

选择

此结构旨在处理通常由 ifelse ifelseswitch 语句覆盖的场景。

示例 #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(...);

参见:xpath playground

模拟

您可以通过添加您喜欢的聚合器来增强 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

替代方案