README.md

# ExUssd

[![Actions Status](https://github.com/beamkenya/ex_ussd/workflows/Elixir%20CI/badge.svg)](https://github.com/beamkenya/ex_ussd/actions) ![Hex.pm](https://img.shields.io/hexpm/v/ex_ussd) ![Hex.pm](https://img.shields.io/hexpm/dt/ex_ussd)
[![Coverage Status](https://coveralls.io/repos/github/beamkenya/ex_ussd/badge.svg?branch=develop)](https://coveralls.io/github/beamkenya/ex_ussd?branch=develop)

## 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](#Installation)
- [Gateway Providers](#providers)
- [Configuration](#Configuration)
- [Documentation](#Documentation)
- [Contribution](#contribution)
- [Contributors](#contributors)
- [Licence](#licence)

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `ex_ussd` to your list of dependencies in `mix.exs`:

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

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/ex_ussd](https://hexdocs.pm/ex_ussd).

## Providers

ExUssd currently supports

[Africastalking API](https://africastalking.com)

[Infobip API](https://www.infobip.com/)

## 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

```elixir
config :ex_ussd, :gateway, AfricasTalking
```

### Infobip

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

```elixir
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,

```elixir
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

```elixir
  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

```elixir
  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"`

```elixir
  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 ":"

```elixir
  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`

```elixir
  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.

```elixir
  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

```elixir
  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.

```elixir
  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](https://github.com/beamkenya/ex_ussd/issues) and [pull requests](https://github.com/beamkenya/ex_ussd/pulls) to see whether someone else has raised a similar idea or question.
If you don't see your idea listed, [Open an issue](https://github.com/beamkenya/ex_ussd/issues).

Check the [Contribution guide](contributing.md) on how to contribute.

## Contributors

Auto-populated from:
[contributors-img](https://contributors-img.firebaseapp.com/image?repo=beamkenya/ex_ussd)

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

## Licence

ExPesa is released under [MIT License](https://github.com/appcues/exsentry/blob/master/LICENSE.txt)

[![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=for-the-badge)](#)