prinx/ussd-lib

该软件包已被废弃且不再维护。作者建议使用 rejoice/rejoice 软件包。

轻松编写您的 USSD 应用程序。

dev-master 2020-03-27 10:01 UTC

This package is auto-updated.

Last update: 2020-10-23 09:10:36 UTC


README

这个库可以帮助您轻松编写 USSD 应用程序。

免责声明

1.

这是一个进行中的工作。教程会定期更新。

2.

该库在加纳开发、测试和使用 USSD 应用程序时创建。由于作者不知道其他国家移动运营商如何处理 USSD 流(我们真正想知道的),它可能在其他国家工作或不工作。如果您遇到任何问题,请随时提交问题或联系作者。欢迎对代码做出贡献或进行分支以提高其性能。

安装

composer require prinx/ussd-lib:dev-master

用法

库需要一个我们称之为 菜单管理器 的类。菜单管理器提供菜单、菜单函数、菜单参数和数据库参数给库,并运行库。菜单管理器可以是连接到 USSD 应用程序可用路由的控制器,或者是一个将在 USSD 应用程序可用路由上运行的定制类。

因此,创建您的 USSD 应用程序通常遵循以下模式

  • 数据库配置;
  • 菜单构建;
  • 菜单函数开发;
  • 菜单参数配置

您在具有数据库配置、菜单和应用程序参数的类中创建应用程序(菜单管理器)。菜单管理器将具有一些处理菜单逻辑的方法。

查看 index.sample.php 以获取示例应用程序。

菜单管理器类

创建一个名为 ussd_menu_manager.php 的文件(文件名不重要)。文件的位置由您决定。只需记住您需要将文件导入到您的 index.php 或控制器(如果您正在使用框架)中。在文件中创建一个类。

// use Prinx\UssdLib\Lib\UssdLib;

// The name of the class does not matter.
class USSDApp
{
  // The parameters of the application that will be passed to the USSD library
  protected $params = [];

  // A property to store the instance of the library so that you can call some
  protected $ussd;

  // Will contain the menus logic (the typical ussd flow)
  protected $menus = [];

  // Let's define the getters (or you can just make the properties public and skip the getters)

  public function params()
  {
      return $this->params;
  }

  public function menus()
  {
      return $this->menus;
  }
}

数据库配置

现在让我们定义数据库参数。 为什么我们需要一些数据库? 我们需要一个数据库来存储用户会话。对于每个拨打短码的用户,都会创建一个会话。这个会话跟踪用户对各个菜单的回答,并返回针对特定用户的响应。

通过提供数据库参数,您第一次运行库时,会自动创建一个会话表。

define('ENV', 'development');
use Prinx\UssdLib\Lib\UssdLib;

class USSDApp
{
  // PROPERTIES
  //...

  // GETTERS
  //...

  // DB PARAMS
  public function db_params()
  {
    // It's highly recommended to use a .env file to store the database credentials
    $config = [
        'username' => '',
        'password' => '',
    ];

    if (ENV !== 'production') {
        $config['hostname'] = '';
        $config['dbname'] = '';
    } else {
        $config['hostname'] = '';
        $config['password'] = '';
        $config['port'] = '';
        $config['dbname'] = '';
    }

    return $config;
  }
}

我们强烈建议您使用 .env 文件来存储数据库凭据。如果您不使用框架或您的框架没有 env 解析器,您可以使用库的 php env 函数来获取您的环境变量。

在项目的根目录中创建一个 .env 文件。在 .env 文件中定义参数

DEV_DB_USER=root
DEV_DB_PASS=password
DEV_DB_HOST=db_ip_address
DEV_DB_PORT=3306
DEV_DB_NAME=ussd_session_db_name_or_your_app_db_name

PROD_DB_USER=root
PROD_DB_PASS=password
PROD_DB_HOST=db_ip_address
PROD_DB_PORT=3306
PROD_DB_NAME=ussd_session_db_name_or_your_app_db_name

该函数属于命名空间 Prinx\Dotenv\env;。您可以在本文档中了解更多关于 env() 函数的信息。

use function Prinx\Dotenv\env;

class USSDApp
{
  //...
  public function db_params()
  {
    // It's highly recommended to use a .env file to store the database credentials
    $config = [];
    
    if (ENV !== 'production') {
        $config['username'] = env('DEV_DB_USER');
        $config['password'] = env('DEV_DB_PASS');
        $config['hostname'] = env('DEV_DB_HOST');
        $config['port'] = env('DEV_DB_PORT');
        $config['dbname'] = env('DEV_DB_NAME');
    } else {
        $config['username'] = env('PROD_DB_USER');
        $config['password'] = env('PROD_DB_PASS');
        $config['hostname'] = env('PROD_DB_HOST');
        $config['port'] = env('PROD_DB_PORT');
        $config['dbname'] = env('PROD_DB_NAME');
    }

    return $config;
  }
}

菜单构建

现在让我们集中精力构建菜单。这是应用程序最重要的部分。

菜单参数配置

应用程序的参数将直接指定在菜单管理类中的 params 属性中,或者在菜单管理类的构造函数中。

class USSDApp
{
  //...
  public function __construct()
  {
    // The id parameter is required. It will be used to create the ussd session table. Therefore, only letters and underscore will be accepted.
    $this->params['id'] = 'first_ussd_app';
  }
}

id 是唯一一个没有默认值的参数,因此是必需的。

其他参数(可选参数)

class USSDApp
{
  //...

  public function __construct()
  {
    $this->params['id'] = 'first_ussd_app';

    // Optional parameters
    $this->params['environment'] = 'dev';

    $this->params['end_on_user_error'] = false;

    $this->params['always_start_new_session'] = false;
    $this->params['ask_user_before_reload_last_session'] = true;

    $this->params['always_send_sms'] = true;
    $this->params['sms_sender_name'] = '';
    $this->params['sms_endpoint'] = '';

    $this->params['back_action_thrower'] = '0';
    $this->params['back_action_display'] = 'Back';

    $this->params['splitted_menu_next_thrower'] = '99';
    $this->params['splitted_menu_display'] = 'More';

    $this->params['default_end_msg'] = 'Thank you.';
    $this->params['default_error_msg'] = 'Invalid Input.';
  }
}

或者

class USSDApp
{
  protected $params = [
    'id' => 'first_ussd_app',
    'environment' => 'dev',
    'end_on_user_error' => false,

    'always_start_new_session' => false,
    'ask_user_before_reload_last_session' => true,

    'always_send_sms' => true,
    'sms_sender_name' => '',
    'sms_endpoint' => '',

    'back_action_thrower' => '0',
    'back_action_display' => 'Back',
    'splitted_menu_next_thrower' => '99',
    'splitted_menu_display' => 'More',

    'default_end_msg' => 'Thank you.',
    'default_error_msg' => 'Invalid Input.',
  ];

  public function __construct()
  {
  }
}

always_start_new_session: 布尔值

如果设置为 true,每次用户拨打简码时,欢迎菜单将呈现给用户。如果设置为 false,如果用户会话超时,而用户无法完成其请求或用户自己取消会话,那么下次用户拨打简码时,用户将被带到他/她离开的菜单。 默认值是 true

注意:如果 always_start_new_session 设置为 false,您可以使用 ask_user_before_reload_last_session 参数来控制是否需要向用户发送提示,以决定他/她是否要从离开的地方重新开始或从欢迎菜单开始。

ask_user_before_reload_last_session: 布尔值

如果此参数为 true,则用户将收到提示,选择他/她是否要从离开的地方继续或从欢迎菜单开始。 默认值是 false

注意ask_user_before_reload_last_session 将仅在 always_start_new_session 设置为 false 时生效。

back_action_thrower: 字符串或整数

将用户带到上一个菜单的输入。 默认值是 0(零)。

back_action_display

指示用户他/她可以返回的提示。 默认值是 "Back"

splitted_menu_next_thrower: 字符串或整数

如果特定菜单被分割,将用户带到下一个菜单的输入。 默认值是 99

splitted_menu_display

指示用户有同一菜单的另一页。 默认值是 "More"

default_end_msg

默认的告别信息。如果您的菜单中没有提供信息,则使用它。 默认值是 "Goodbye"

default_error_msg

默认的错误信息,如果用户输入无效。可以通过库中的 set_error 方法进行修改。 默认值是 "Invalid input"

environment

必须是 "prod" 或 "dev"。记得在生产环境中将其修改为 "production"。为了使应用程序更快,许多检查都被绕过了。 默认值是 "dev"

end_on_user_error: 布尔值

如果设置为 true,当用户输入无效响应时,会终止会话并显示错误消息。默认错误消息是 default_error_msg 的值。

always_send_sms: 布尔值

如果为 true,则用户最后看到的消息也会作为短信发送,前提是已经设置了 SMS API 端点。如果没有设置,你可以使用自己的函数来发送短信。在任何时候,你可以使用库中的 send_sms 方法向用户发送短信(前提是已经设置了 SMS API 端点)。默认值是 false

sms_sender_name

显示为短信发送者的名称。默认值是 ""(空字符串)。

sms_endpoint

发送短信的 API 端点。你可以选择不使用库的 SMS 接口,而是使用自己的接口。默认值是 ""(空字符串)。

运行库

__construct 方法中

class USSDApp
{
  //...

  public function __construct()
  {
    // Application parameters...
    // ...

    $this->ussd = new USSDLib();
    $this->ussd->run($this);
  }
}

运行应用程序

在你的 index.php 文件中或控制器内部(如果你使用的是框架),放置以下代码。

// Top of the file
require_once 'path/to/ussd/app.php';

// Place the following line inside the controller if your are using a framework.
// If not, just place it in the index.php.
$app = new USSDApp();

这是 ussp 应用程序运行的最小要求。但通常你需要在用户发送响应之前或之后验证用户响应,或进行其他操作,例如调用 API 获取用户的余额,从数据库检索一些数据以显示给用户等。让我们看看我们如何做到这一点。

菜单函数开发(钩子)

before_ 函数

三个主要用途

  • 提供菜单消息;
  • 提供菜单操作;
  • 在菜单显示给用户之前运行特定的代码。

如果你想在菜单消息之前运行代码。before_ 函数允许你在菜单页面发送和显示给用户之前运行代码。因此,它允许你修改菜单消息。要修改菜单消息,你可以返回一个字符串,或者一个包含占位符的数组。如果你返回一个字符串,该字符串将被显示给用户。如果你返回一个数组,数组中的值将替换你消息中指定的占位符。

因此

after_ 函数

after_ 函数是在用户向应用程序发送响应之后运行的函数。因此,你可以使用 validate_ 函数验证用户响应。你可以在通用的 after_ 函数中做其他事情。

验证用户响应

库通过函数传递最后用户的响应。该函数必须返回一个布尔值:true 表示验证通过,false 表示未通过。如果返回 false,则将再次运行相同的菜单,但顶部将显示错误消息。错误消息是 default_error_msg 参数。您可以使用库中的 set_error 函数更改特定菜单的错误消息。

class USSDApp
{
    //...

    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }

    public function create_date_from_format($date, $format = '')
    {
        $format = $format !== '' ? $format : $this->default_date_format;

        return DateTime::createFromFormat($format, $date);
    }

    public function is_valid_date($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        $day = $date->format('d');

        return checkdate($month, $day, $year);
    }

    public function age_within(int $min, int $max, int $age)
    {
        return $min <= $age && $age < $max;
    }

    public function calculate_age($birthdate, $birthdate_format = 'd/m/Y')
    {
        return DateTime::createFromFormat($birthdate_format, $birthdate)
            ->diff(new DateTime('now'))
            ->y;
    }
}

您会发现我们还创建了一些其他辅助方法(calculate*age, age_within, is_valid_date, create_date_from_format)。这些方法将不会被库使用。库只会搜索钩子(以 before\_validate\_after\_ 开头的函数)将使用。

更改默认错误消息

通常在验证函数内部使用它来定义如果响应未通过验证将显示给用户的错误消息。此函数不是必需的。如果您不使用它,将使用 default_error_msg 参数。

class USSDApp
{
  // ...
    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }
}

退出应用程序

您可以使用此函数在任何点决定退出应用程序。通常,如果用户发送了错误答案,您会使用它来退出应用程序。如果您没有传递要显示给用户的消息,将使用 default_end_msg 参数。

class USSDApp
{
  // ...



    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }


    public function create_date_from_format($date, $format = '')
    {
        $format = $format !== '' ? $format : $this->default_date_format;

        return DateTime::createFromFormat($format, $date);
    }

    public function is_valid_date($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        $day = $date->format('d');

        return checkdate($month, $day, $year);
    }

    public function age_within(int $min, int $max, int $age)
    {
        return $min <= $age && $age < $max;
    }

    public function calculate_age($birthdate, $birthdate_format = 'd/m/Y')
    {
        return DateTime::createFromFormat($birthdate_format, $birthdate)
            ->diff(new DateTime('now'))
            ->y;
    }

}

代码

.env 文件

DEV_DB_USER=root
DEV_DB_PASS=password
DEV_DB_HOST=db_ip_address
DEV_DB_PORT=3306
DEV_DB_NAME=ussd_session_db_name_or_your_app_db_name

PROD_DB_USER=root
PROD_DB_PASS=password
PROD_DB_HOST=db_ip_address
PROD_DB_PORT=3306
PROD_DB_NAME=ussd_session_db_name_or_your_app_db_name

USSD 应用程序代码

define('ENV', 'development');
require_once __DIR__ . '/vendor/prinx/ussd-lib/src/USSD.php';

use function Prinx\Dotenv\env;
use Prinx\USSD\USSD;

class USSDApp
{
    protected $default_date_format = 'd/m/Y';

    protected $app_params = [
        'id' => 'first_ussd_app',
        'environment' => 'dev',
        'end_on_user_error' => false,

        'always_start_new_session' => false,
        'ask_user_before_reload_last_session' => true,

        'always_send_sms' => false,
        'sms_sender_name' => 'BASICAPP',
        // 'sms_endpoint' => '',

        'back_action_thrower' => '0',
        'back_action_display' => 'Back',
        'splitted_menu_next_thrower' => '99',
        'splitted_menu_display' => 'More',

        'default_end_msg' => 'Thank you.',
        'default_error_msg' => 'Invalid Input.',
    ];

    protected $ussd;

    protected $menus = [
        'welcome' => [
            'message' => "Welcome.\nSelect an option",
            'actions' => [
                '1' => [
                    'display' => 'Am I working ?',
                    'next_menu' => 'verify_working',
                ],
                '2' => [
                    'display' => 'What is the date?',
                    'next_menu' => 'show_date',
                ],
                '3' => [
                    'display' => 'Caluculate age',
                    'next_menu' => 'get_birthdate',
                ],
                '4' => [
                    'display' => 'Say Goodbye',
                    'next_menu' => 'say_goodbye',
                ],
            ],
        ],

        'verify_working' => [
            'message' => "Of course, I'm working!"
        ],

        'show_date' => [
            'message' => 'Today is :date:!',
            'actions' => [
                '1' => [
                    'display' => 'Back',
                    'next_menu' => '__back',
                ],
                '0' => [
                    'display' => 'End',
                    'next_menu' => '__end',
                ],
            ],
        ],

        'get_birthdate' => [
            'message' => "Enter your birthdate (dd/mm/yyyy)\nOr enter 0 to go back:",
            'actions' => [
                '0' => [
                    'display' => 'Back',
                    'next_menu' => '__back',
                ],

                'default_next_menu' => 'show_age',
            ],
        ],

        'show_age' => [
            'message' => "You are :age: years old!",
            'actions' => [
                '0' => [
                    'display' => 'Back',
                    'next_menu' => '__back',
                ],
                '1' => [
                    'display' => 'Main menu',
                    'next_menu' => '__welcome',
                ],
                '2' => [
                    'display' => 'End',
                    'next_menu' => '__end',
                ],
            ],
        ],

        'say_goodbye' => [
            'message' => "Goodbye",
        ],
    ];

    public function __construct()
    {
        $this->ussd = new USSD();
        $this->ussd->run($this);
    }

    public function before_show_date()
    {
        return ['date' => date('D-m-Y')];
    }

    public function create_date_from_format($date, $format = '')
    {
        $format = $format !== '' ? $format : $this->default_date_format;

        return DateTime::createFromFormat($format, $date);
    }

    public function is_valid_date($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        $day = $date->format('d');

        return checkdate($month, $day, $year);
    }

    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }

    public function age_within(int $min, int $max, int $age)
    {
        return $min <= $age && $age < $max;
    }

    public function calculate_age($birthdate, $birthdate_format = 'd/m/Y')
    {
        return DateTime::createFromFormat($birthdate_format, $birthdate)
            ->diff(new DateTime('now'))
            ->y;
    }

    public function before_show_age($user_previous_response)
    {
        $birthdate = $user_previous_response['get_birthdate'][0];
        $age = $this->calculate_age($birthdate);

        return ['age' => $age];
    }

    public function db_params()
    {
        $config = [];

        if (ENV !== 'production') {
            $config['username'] = env('DEV_DB_USER');
            $config['password'] = env('DEV_DB_PASS');
            $config['hostname'] = env('DEV_DB_HOST');
            $config['port'] = env('DEV_DB_PORT');
            $config['dbname'] = env('DEV_DB_NAME');
        } else {
            $config['username'] = env('PROD_DB_USER');
            $config['password'] = env('PROD_DB_PASS');
            $config['hostname'] = env('PROD_DB_HOST');
            $config['port'] = env('PROD_DB_PORT');
            $config['dbname'] = env('PROD_DB_NAME');
        }

        return $config;
    }

    public function app_params()
    {
        return $this->app_params;
    }

    public function menus()
    {
        return $this->menus;
    }
}

$app = new USSDApp();