tnmdev / ussd
Laravel PHP的USSD适配器
Requires
- php: ^8.0
- ext-json: *
- ext-simplexml: *
- tnmdev/msisdn-helpers: dev-main
- tnmdev/ussd-simulator: dev-master
Requires (Dev)
- orchestra/testbench: ^7.0
- phpunit/phpunit: ^9.0
- tnmdev/utils: @dev
- dev-main
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- v0.12.13
- v0.12.12
- v0.12.11
- v0.12.10
- v0.12.9
- v0.12.8
- v0.12.7
- v0.12.6
- v0.12.5
- v0.12.4
- v0.12.3
- v0.12.2
- v0.12.1
- v0.12.0
- v0.11.0
- v0.10.13
- v0.10.12
- v0.10.11
- v0.10.10
- v0.10.9
- v0.10.8
- v0.10.7
- v0.10.6
- v0.10.5
- v0.10.4
- v0.10.3
- v0.10.2
- v0.10.1
- v0.10.0
- v0.9.11
- v0.9.10
- v0.9.9
- v0.9.8
- v0.9.7
- v0.9.6
- v0.9.5
- v0.9.4
- v0.9.3
- v0.9.2
- v0.9.1
- v0.9.0
- v0.8.1
- v0.8.0
- v0.7.1
- v0.6.0
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.3
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.2
- v0.1.1
- v0.1.0
- dev-dev
This package is auto-updated.
Last update: 2024-09-15 22:32:15 UTC
README
此包创建了一个适配器、样板代码和功能,允许您与USSDC交互,并向您的API提供USSD通道。接口是开放的并且已经文档化,可以与各种USSD接口实现。
安装
composer require tnmdev/ussd
然后发布包的迁移和配置文件。
php artisan vendor:publish --provider="TNM\USSD\USSDServiceProvider"
然后安装ussd脚手架。这也会运行迁移以创建会话跟踪表。
php artisan ussd:install
一旦安装了包,USSD应用程序将在/api/ussd
端点上可用。将在App\Screens\Welcome.php
中为您创建一个着陆屏幕。
用法
创建USSD屏幕
php artisan make:ussd <name>
这将为您创建一个样板USSD屏幕对象。您可以继续编辑message
、options
和execute
方法的内 容。该屏幕扩展了TNM\USSD\Screen
类,这为您提供了访问请求详细信息和使用USSD响应编码的方法。
请求对象
Screen
有一个公共属性$request
。这是一个TNM\USSD\Http\Request
类的对象。
请求类公开了USSDC传递的XML请求中的四个属性。
发送给用户的USSD屏幕由Screens
表示,它们扩展了TNM\USSD\Screen
类。
请求有效载荷
您可以通过请求有效载荷在屏幕之间移动有效载荷。会话中的任何请求都可以访问添加到请求有效载荷中的任何数据。
设置请求有效载荷
可以通过在请求的对象链上调用addPayload
方法来添加请求有效载荷。它接受键值对参数。
$this->addPayload('key', $this->value());
检索请求有效载荷
$this->payload('key');
在有效载荷中使用数组
有时您有用于选项的关联数组。例如,您可以有一个包含产品列表,其中包含id
、price
、name
和humanized
。其中name是在您的系统中对产品的引用,而humanized
是您希望在屏幕上显示的方式。
可以带有第三个布尔参数的此类项目的数组可以推送到有效载荷中。这告诉跟踪对象在存储之前对输入进行序列化。
$this->addPayload('products', $array, true);
通过TNM\USSD\Traits
命名空间中的HasBundledOptions
特质,可以实现对数组有效载荷的操纵。因此,要在您的有效载荷中使用数组,您需要在您的Screen
中使用HasBundledOptions
特质。
以下是捆绑选项特质的一些用途:要将关联数组作为USSD选项列表/映射,可以使用map
方法映射到您选择的数组键。
public function options(): array { return $this->map('humanized', 'products'); }
map
方法接受两个参数。第一个是要映射的数组键,第二个是要从有效载荷中列出的有效载荷键。
当用户在USSD屏幕上做出选择时,您可以通过调用find
方法回溯到关联数组选项的任何键。
$this->addPayload('chosenProduct', $this->find('id', 'products'));
上述代码片段中的实现会将所选产品的 ID
分配给有效载荷键 chosenProduct
。特性会查找作为第二个参数传递给用户的选项。您可以通过传递第三个参数来指定要查找的字段,默认为 humanized
。因此,假设您的选项关联数组将有一个用于显示内容的字段。您可以将其重命名为任何适合您的名称。只需确保传递第三个参数来告诉方法在哪里查找。
在其他情况下,您可能只想获取特定有效载荷键上的整个数组。方法与普通有效载荷相同,再次带有第二个布尔参数。
$this->payload('products', true);
必选方法
Screen
类将要求您实现以下方法。
message()
必须返回一个将在屏幕上显示的字符串消息。options()
必须返回一个选项数组,这些选项将暴露给用户。对于不需要选项的屏幕,请返回空数组。execute()
应用于实现应用程序应如何处理请求数据。请求数据由屏幕对象中的getRequestValue()
返回。您可以使用它来访问请求数据。如果您想将用户重定向到另一个屏幕,请返回目标屏幕的render()
方法:return (new Register($this->request))->render();
。屏幕初始化需要一个参数,即request
对象。previous()
这应该返回一个Screen
类的对象。它告诉会话当用户选择后退选项时导航到哪里。
可选方法
您可以通过以下方法扩展来更改屏幕的一些属性。
type()
应返回一个整数,委托给TNM\USSD\Response
类的常量RELEASE
和RESPONSE
。如果没有重写,则默认为RESPONSE
。RESPONSE
渲染一个带有输入字段的屏幕,而RELEASE
渲染一个不带输入字段的屏幕,用于指示 USSD 网关关闭 USSD 会话。acceptsResponse()
,与type()
方法的复杂性相比,您可以调用acceptsResponse()
。它应该返回一个布尔值,指示屏幕是否渲染输入字段或发送一个标记 USSD 会话结束的屏幕。goesBack()
返回一个布尔值,定义屏幕是否应具有back
导航选项。除非您正在定义登录屏幕,否则请保留它。
异常处理
USSD 适配器有一个自渲染的异常处理器。要使用它,请抛出 TNM\USSD\Exceptions
命名空间的 UssdException
。它接受两个参数:请求对象和您想要传递给用户的消息。异常处理器将渲染一个带有错误消息的 USSD 屏幕,并终止会话。
输入数据验证
您可以通过使用 TNM\USSD\Http
命名空间的 Validates
特性来设置规则以验证用户输入。轨迹将要求您实现 rules()
方法,它应该返回一个验证规则字符串。
要验证输入,请在该 Screen
类的 execute()
方法中调用 $this->validate($this->request, $label)
。
如果输入有验证错误,将抛出 TNM\USSD\Exceptions
命名空间的 ValidationException
,并将为您自动渲染错误屏幕。
namespace App\Screens; use TNM\USSD\Screen; use TNM\USSD\Http\Validates; class EnterPhoneNumber extends Screen { use Validates; protected function message() : string { return 'Enter your phone number'; } //... protected function execute() { $this->validate($this->request, 'phone'); $this->addPayload('phone', $this->value()); return (new NextScreen($this->request))->render(); } protected function rules() : string { return 'regex:/(088)[0-9]{7}/'; } }
扩展以支持多种实现
此适配器是考虑扩展性而设计的。目前,它支持 TNM 和 Airtel Malawi 分别使用的 TruRoute 和 Flares USSD 接口。然而,凭借可插拔的接口,它可以扩展以支持任何移动网络运营商。
要扩展功能,请创建一个请求类和响应类。这些类必须分别实现TNM\USSD\Http\UssdRequestInterface
和TNM\USSD\Http\UssdResponseInterface
接口。响应类已经进一步简化,您只需要扩展XMLResponse
类。
请求类的实现细节可能有所不同。然而,我们强烈建议您有一个构造函数,该构造函数将来自移动运营商的USSD请求解码为数组,并将其分配给$request
私有属性,接口方法应基于私有属性返回它们的值。
示例请求实现
use TNM\USSD\Http\UssdRequestInterface; class TruRouteRequest implements UssdRequestInterface { /** * @var array */ private $request; public function __construct() { $this->request = json_decode(json_encode(simplexml_load_string(request()->getContent())), true); } public function getMsisdn(): string { return $this->request['msisdn']; } // ... }
必需方法
请求接口要求您实现以下方法
getSession()
应返回由USSD网关分配的会话id
getMsisdn()
应返回发起USSD请求的msisdngetMessage()
应返回与请求一起发送的消息getType()
应返回请求类型。
示例响应实现
以下是一个响应类实现的示例。您需要在响应目录中添加一个示例响应XML文件。此文件将由MNO提供。对于TNM和Airtel Malawi,我们已经为您准备好了。
TNM响应XML模板
<ussd> <type>{{type}}</type> <msg>{{message}}</msg> <premium> <cost>0</cost> <ref>NULL</ref> </premium> </ussd>
TNM响应类
use TNM\USSD\Http\UssdResponseInterface; use TNM\USSD\Screen; class TruRouteResponse extends XMLResponse { protected function getPayload(): array { return [ 'type' => $this->screen->type(), 'message' => $this->screen->getResponseMessage(), ]; } protected function getTemplate(): string { return __DIR__ . '/response.xml'; } }
路由
您可以使用路由参数adapter
来区分来自不同移动运营商的请求。
使用Flares
适配器的网络的全部请求应路由到api/ussd/flares
。因此,当您创建自己的扩展时,运营商的路由应该是api/ussd/{adapter}
。
这不是魔法般地解决的。您需要在TNM\USSD\Factories\RequestFactory
和TNM\USSD\Factories\ResponseFactory
中定义实现。
示例请求工厂
namespace TNM\USSD\Factories; class RequestFactory { public function make(): UssdRequestInterface { return match (request()->route('adapter')) { 'flares' => resolve(FlaresRequest::class), default => resolve(TruRouteRequest::class), }; } }
示例响应工厂
namespace TNM\USSD\Factories; class ResponseFactory { public function make(): UssdResponseInterface { return match (request()->route('adapter')) { 'flares' => resolve(FlaresResponse::class), default => resolve(TruRouteResponse::class), }; } }
本地化
您可以在应用的任何屏幕上设置会话语言。以下屏幕将以新选定的语言显示。
$this->request->trail->setLocale('en');
此功能实现了Laravel的本地化,使用语言文件。有关更多详细信息,请参阅Laravel文档。因此,您的实现可能如下所示
public function message(): string { return __("screens.welcome_message"); }
示例本地化实现
public function execute() { $locale = $this->value() == 'English' ? 'en' : 'fr'; $this->request->trail->setLocale($locale); return (new NextScreen($this->request))->render(); }
审计
您可以使用CLI工具跟踪用户会话、系统消息和用户响应。
php artisan ussd:list <phone>
此命令为您提供了一个列表,其中包含一个号码执行的所有交易。列表包含会话ID和时间戳。
php artisan ussd:audit <session-id>
此命令提供从会话开始到结束的交易的所有详细信息。轨迹包括系统消息、用户对每个消息的响应及其按时间顺序的时间戳。
当用户响应是一个选项时,它报告一个字符串值,该值由选定的数字表示,从而节省了您查找哪个选项位于数字1、2等的麻烦。
会话数据清理
该软件包使用数据库表跟踪会话。此数据库表可能需要一段时间后进行清理。要清理,请在应用目录中运行以下命令。
php artisan ussd:clean-up --days=30
它接受保留天数的数据选项。如果没有传递选项,则删除60天以上的所有内容。
- 关于审计的注意事项:清理后的数据将不可用审计轨迹。
示例屏幕实现
// app/Screens/Subscribe.php namespace App\Screens; use TNM\USSD\Screen; class Subscribe extends Screen { public function message(): string { return "Please select a plan you want to subscribe to"; } public function options(): array { return ['Plan 1', 'Plan 2', 'Plan 3']; } public function execute() { // save the request value to session object // to access it in the next screen with $this->payload($key) $this->addPayload('plan', $this->value()); return (new ConfirmSubscription($this->request))->render(); } public function previous(): Screen { return new Welcome($this->request); } }
// app/Screens/ConfirmSubscription.php namespace App\Screens; use Exception;use TNM\USSD\Screen; use TNM\USSD\Exceptions\UssdException; class ConfirmSubscription extends Screen { public function message(): string { return sprintf("Please confirm subscription to %s", $this->payload('plan')); } public function options(): array { return ['Confirm', 'Cancel']; } public function execute() { if ($this->value() === 'Cancel') return $this->previous()->render(); $service = new SubscriptionService($this->request->msisdn); try { $service->subscribe($this->payload('plan')); return (new Subscribed($this->request))->render(); } catch (Exception $exception) { throw new UssdException($this->request, "Subscription failed. Please try again later"); } } public function previous(): Screen { return new Subscribe($this->request); } }