ExUssd
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.2"}
]
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
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
name
: (Public) This is the value display when Menu is rendered as menu_list. check more onmenu_list
,handler
: (Public) A callback that modifies the current menu struct. Implemented via ExUssd.Handlercallback
: (Internal) A callback function that takes thehandler
callback. This function is triggered when the client is at that menu position.title
: (Public) Outputs the ussd's title,menu_list
: (Public) Takes a list of Ussd Menu struct,error
: (Public) A custom validation error message forvalidation_menu
,show_navigation
: (Public) set to false to hide navigation menu,next
: (Public) navigate's the next menu chunk, default%{name: "MORE", input_match: "98", display_style: ":"}
,previous
: (Public) navigate's the previous menu chunk or the previous menu struct default%{name: "BACK", input_match: "0", display_style: ":"}
,split
: (Public) This is used to set the chunk size value when rendering menu_list. default value size7
,should_close
: (Public) This triggers ExUssd to end the current registry session,display_style
: (Public) This is used to change default's display style, default ":"parent
: (Internal) saves the previous menu struct to the current menu in order to facilitate navigation,validation_menu
: (Public) Its a special Menu struct that enables the developer to validate the client input,data
: (Public) takes data as Props that will be attached to the children menu struct,default_error_message
:(Public) This the default error message shown on invalid input. default"Invalid Choice\n"
ussd title only
defmodule MyHomeHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu |> Map.put(:title, "Welcome")
end
end
menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
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"}
ussd menu_list
defmodule ProductAHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu |> Map.put(:title, "selected product a")
end
end
defmodule ProductBHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu |> Map.put(:title, "selected product b")
end
end
defmodule ProductCHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu |> Map.put(:title, "selected product c")
end
end
defmodule MyHomeHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu
|> Map.put(:title, "Welcome")
|> Map.put(
:menu_list,
[
ExUssd.Menu.render(name: "Product A", handler: ProductAHandler),
ExUssd.Menu.render(name: "Product B", handler: ProductBHandler),
ExUssd.Menu.render(name: "Product C", handler: ProductCHandler)
]
)
end
end
menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
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\n1:Product A\n2:Product B"}
# simulate 1
menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
ExUssd.goto(
internal_routing: %{text: "1", session_id: "session_01", service_code: "*544#"},
menu: menu,
api_parameters: %{"sessionId" => "session_01", "phoneNumber" => "254722000000", "networkCode" => "Safaricom", "serviceCode" => "*544#", "text" => "1" }
)
{:ok, "CON selected product a\n0:BACK"}
ussd validation menu
defmodule PinValidateHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, api_parameters) do
case api_parameters.text == "5555" do
true ->
menu
|> Map.put(:title, "success, thank you.")
|> Map.put(:should_close, true)
_ ->
menu |> Map.put(:error, "Wrong pin number\n")
end
end
end
defmodule MyHomeHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu
|> Map.put(:title, "Enter your pin number")
|> Map.put(:validation_menu, ExUssd.Menu.render(name: "", handler: PinValidateHandler))
end
end
menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
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 Enter your pin number"}
# simulate wrong pin
menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
ExUssd.goto(
internal_routing: %{text: "3339", session_id: "session_01", service_code: "*544#"},
menu: menu,
api_parameters: %{"sessionId" => "session_01", "phoneNumber" => "254722000000", "networkCode" => "Safaricom", "serviceCode" => "*544#", "text" => "3339" }
)
{:ok, "CON Wrong pin number\nEnter your pin number"}
# simulate correct pin
menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
ExUssd.goto(
internal_routing: %{text: "5555", session_id: "session_01", service_code: "*544#"},
menu: menu,
api_parameters: %{"sessionId" => "session_01", "phoneNumber" => "254722000000", "networkCode" => "Safaricom", "serviceCode" => "*544#", "text" => "5555" }
)
{:ok, "END success, thank you."}
receive data as props
defmodule MyHomeHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, api_parameters) do
%{language: language} = menu.data
case language do
"Swahili" -> menu |> Map.put(:title, "Karibu")
_-> menu |> Map.put(:title, "Welcome")
end
end
end
data = %{language: "Swahili"}
ExUssd.Menu.render(name: "Home", data: data, handler: MyHomeHandler)
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> defmodule MyHomeHandler do
@behaviour ExUssd.Handler
def handle_menu(menu, _api_parameters) do
menu |> Map.put(:title, "Welcome")
end
end
iex> menu = ExUssd.Menu.render(name: "Home", handler: MyHomeHandler)
iex> ExUssd.simulate(menu: menu, text: "")
{:ok, %{menu_string: "Welcome", should_close: false}}
Examples
ussd examples using ExUssd can be found here https://github.com/lenileiro/ussd_examples
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