ExUssd

Actions Status Hex.pm Hex.pm Coverage Status

Introduction

ExUssd lets you create simple, flexible, and customizable USSD interface. Under the hood ExUssd uses Elixir Registry to create and route individual USSD session.

Sections

Installation

If available in Hex, the package can be installed by adding ex_ussd to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_ussd, "~> 0.1.1"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/ex_ussd.

Providers

ExUssd currently supports

Africastalking API

Infobip API

Configuration

To Use One of the above gateway providers for your project Create a copy of config/dev.exs or config/prod.exs from config/dev.sample.exs Use the gateway key to set the ussd vendor.

AfricasTalking

Add below config to dev.exs / prod.exs files

config :ex_ussd, :gateway, AfricasTalking

Infobip

Add below config to dev.exs / prod.exs files

config :ex_ussd, :gateway, Infobip

Documentation

ExUssd supports Ussd customizations through Menu struct via the render function

  • handler - This is a callback function that returns the menu struct, ussd api_parameters map and should_handle boolean.

    • menu - The menu struct is modified to produce ussd menu struct
    • api_parameters - This a map of ussd response call
    • should_handle - a check value, where ExUssd allows the developer to handle client input, more on handle, default is false
  • name - This is the value display when Menu is rendered as menu_list. check more on menu_list.

  • title - Outputs the ussd's title,

ExUssd.Menu.render(
        name: "Home",
        handler: fn menu, _api_parameters, _should_handle ->
          menu |> Map.put(:title, "Welcome")
        end
        )
{:ok, "CON Welcome"}
  • menu_list - Takes a list of Ussd Menu
  ExUssd.Menu.render(
          name: "Home",
          handler: fn menu, _api_parameters, _should_handle ->
            menu |> Map.put(:title, "Welcome")
            |> Map.put(:menu_list,
            [
              Menu.render(
              name: "Product A",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product a")
            end),
            Menu.render(
              name: "Product B",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product b")
            end)]
          )
      end)
  {:ok, "CON Welcome\n1:Product A\n2:Product B"}
  # simulate 1
  {:ok, "CON selected product a\n0:BACK"}
  • should_close - This triggers ExUssd to end the current registry session and correct preffix the menu string
  ExUssd.Menu.render(
          name: "Home",
          handler: fn menu, _api_parameters, _should_handle ->
            menu |> Map.put(:title, "Welcome")
            |> Map.put(:menu_list,
            [
              Menu.render(
              name: "Product A",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product a")
                |> Map.put(:should_close, true)
            end),
            Menu.render(
              name: "Product B",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product b")
                |> Map.put(:should_close, true)
            end)]
          )
      end)
  {:ok, "CON Welcome\n1:Product A\n2:Product B"}
  # simulate 1
  {:ok, "END selected product a"}
  • default_error_message - This the default error message shown on invalid input. default "Invalid Choice\n"
  ExUssd.Menu.render(
          name: "Home",
          handler: fn menu, _api_parameters, _should_handle ->
            menu
            |> Map.put(:default_error_message, "Invalid selection, try again\n")
            |> Map.put(:title, "Welcome")
            |> Map.put(:menu_list,
            [
              Menu.render(
              name: "Product A",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product a")
            end),
            Menu.render(
              name: "Product B",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product b")
            end)]
          )
      end)
  {:ok, "CON Welcome\n1:Product A\n2:Product B"}
  # simulate 11
  {:ok, "CON Invalid selection, try again\nWelcome\n1:Product A\n2:Product B"}
  • display_style - Used change the default's display style ":"
  ExUssd.Menu.render(
          name: "Home",
          handler: fn menu, _api_parameters, _should_handle ->
            menu
            |> Map.put(:display_style, ")")
            |> Map.put(:title, "Welcome")
            |> Map.put(:menu_list,
            [
              Menu.render(
              name: "Product A",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product a")
            end),
            Menu.render(
              name: "Product B",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product b")
            end)]
          )
      end)
  {:ok, "CON Welcome\n1)Product A\n2)Product B"}
  • split - This is used to set the chunk size value when rendering menu_list. default value size 7
  ExUssd.Menu.render(
          name: "Home",
          handler: fn menu, _api_parameters, _should_handle ->
            menu
            |> Map.put(:split, 2)
            |> Map.put(:title, "Welcome")
            |> Map.put(:menu_list,
            [
              Menu.render(
              name: "Product A",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product a")
            end),
            Menu.render(
              name: "Product B",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product b")
            end),
            Menu.render(
              name: "Product C",
              handler: fn menu, _api_parameters, _should_handle ->
                menu |> Map.put(:title, "selected product c")
            end)]
          )
      end)
  {:ok, "CON Welcome\n1:Product A\n2:Product B\n98:MORE"}
  # simulate 98
  {:ok, "CON Welcome\n3:Product C\n0:BACK"}
  • next - Used render the next menu chunk, default "98"
  • previous - Ussd to navigate to the previous menu, default "0"
  • handle - To let ExUssd allow the developer to validate the client input, before navigating to the next menu.
  iex> ExUssd.Menu.render(
        name: "Home",
        handler: fn menu, _api_parameters, _should_handle ->
          menu
          |> Map.put(:title, "Enter Pin Number")
          |> Map.put(:handle, true)
          |> Map.put(
            :validation_menu,
            ExUssd.Menu.render(
              name: "",
              handler: fn menu, api_parameters, should_handle ->
                case should_handle do
                  true ->
                    case api_parameters.text == "5555" do
                      true ->
                        menu
                        |> Map.put(:title, "success, thank you.")
                        |> Map.put(:should_close, true)
                        |> Map.put(:success, true)

                      _ ->
                        menu |> Map.put(:error, "Wrong pin number\n")
                    end

                  false ->
                    menu
                end
              end
            )
          )
        end
      )
    {:ok, "CON Enter Pin Number"}
    ## simulate 5555
    {:ok, "END success, thank you."}
    ## simulate 2342
    {:ok, "CON Wrong pin number\nEnter Pin Number"}
  • error - custom error message on failed validation/handling,
  • success - allows ExUssd to Render next menu on successful validation/handling
  • show_options - hides menu list on false
  • show_navigation - set to false to hide navigation menu

Render Menu

ExUssd to render Menu struct for different ussd providers. ExUssd provides goto function that starts and manages the ussd sessions. The goto function receives the following parameters.

  • internal_routing - it takes a map with ussd text, session_id and serive_code
  • menu - Menu struct
  • api_parameters - api_parameters
  iex> menu = ExUssd.Menu.render(
        name: "Home",
        handler: fn menu, _api_parameters, _should_handle ->
          menu |> Map.put(:title, "Welcome")
        end
        )
  iex> ExUssd.goto(
        internal_routing: %{text: "", session_id: "session_01", service_code: "*544#"},
        menu: menu,
       api_parameters: %{
        "sessionId" => "session_01",
        "phoneNumber" => "254722000000",
        "networkCode" => "Safaricom",
        "serviceCode" => "*544#",
        "text" => ""
        }
      )
  {:ok, "CON Welcome"}

Testing

To test your USSD menu, ExUssd provides a simulate function that helps you test menu rendering and logic implemented by mimicking USSD gateway callback.

  iex> menu = ExUssd.Menu.render(
        name: "Home",
        handler: fn menu, _api_parameters, _should_handle ->
          menu |> Map.put(:title, "Welcome")
        end
        )
  iex> ExUssd.simulate(menu: menu, text: "")

  {:ok, "CON Welcome"}

Contribution

If you'd like to contribute, start by searching through the issues and pull requests to see whether someone else has raised a similar idea or question. If you don't see your idea listed, Open an issue.

Check the Contribution guide on how to contribute.

Contributors

Auto-populated from: contributors-img

<img src="https://contributors-img.firebaseapp.com/image?repo=beamkenya/ex_ussd" />

Licence

ExPesa is released under MIT License

license