okaforpeter / ussd
Laravel PHP USSD适配器
Requires
- php: ^7.3|^8.0
- ext-json: *
- ext-simplexml: *
- laravel/framework: >=5.8
- tnmdev/ussd-simulator: dev-master
- dev-master
- v2.0.0
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.1
- v1.1.0
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- v0.13.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-development
This package is auto-updated.
Last update: 2024-09-30 01:21:47 UTC
README
此包创建了一个适配器、样板代码和功能,允许您与USSDC交互,并向您的API提供USSD通道。该接口是开放的,并已记录,可用于实现各种USSD接口。
安装
composer require okaforpeter/ussd
然后安装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响应的手段。
Request
对象
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
。
请求类的实现细节可能有所不同。然而,我们强烈建议有一个构造函数,该构造函数将来自移动运营商的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()
应该返回请求的类型。
示例响应实现
以下是一个示例响应类实现。它有一个必需的公共方法:respond
,它必须以网络运营商所需格式返回一条消息。
use TNM\USSD\Http\UssdResponseInterface; use TNM\USSD\Screen; class TruRouteResponse implements UssdResponseInterface { public function respond(Screen $screen) { return sprintf( "<ussd><type>%s</type><msg>%s</msg><premium><cost>0</cost><ref>NULL</ref></premium></ussd>", $screen->type(), $screen->getResponseMessage() ); } }
路由
您可以使用路由参数adapter
区分来自不同移动运营商的请求。
所有使用Flares
适配器的网络请求应路由到api/ussd/flares
。因此,当您创建自己的扩展时,运营商的路由应为api/ussd/{adapter}
。
这不是神奇地解决的。您需要定义在TNM\USSD\Factories\RequestFactory
和TNM\USSD\Factories\ResponseFactory
中的实现。
示例请求工厂
namespace TNM\USSD\Factories\RequestFactory; class RequestFactory { public function make(): UssdRequestInterface { switch (request()->route('adapter')) { case 'flares' : return resolve(FlaresRequest::class); default: return resolve(TruRouteRequest::class); } } }
示例响应工厂
namespace TNM\USSD\Factories\ResponseFactory; class ResponseFactory { public function make(): UssdResponseInterface { switch (request()->route('adapter')) { case 'flares': return resolve(FlaresResponse::class); default: return 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); } }