Laravel PHP USSD适配器

v2.0.0 2022-10-03 18:31 UTC

README

此包创建了一个适配器、样板代码和功能,允许您与USSDC交互,并向您的API提供USSD通道。该接口是开放的,并已记录,可用于实现各种USSD接口。

使用markdown-toc生成的目录表

安装

composer require okaforpeter/ussd

然后安装ussd脚手架。这将运行迁移以创建会话跟踪表。

php artisan ussd:install

安装此包后,USSD应用将通过/api/ussd端点可用。将在App\Screens\Welcome.php为您创建一个着陆屏幕。

使用方法

创建USSD屏幕

php artisan make:ussd <name>

这将为您创建一个样板USSD屏幕对象。您可以继续编辑messageoptionsexecute方法的内容。该屏幕扩展了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');

在负载中使用数组

有时您有用于选项的关联数组。例如,您可以有一个包含idpricenamehumanized的产品列表。其中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 类的 RELEASERESPONSE 常量。如果没有覆盖,则默认为 RESPONSERESPONSE 渲染一个带有输入字段的屏幕,而 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\UssdRequestInterfaceTNM\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请求的msisdn
  • getMessage()应该返回与请求一起发送的消息
  • 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\RequestFactoryTNM\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);
    }
}