How To Set Up Ussd Gateway
Setting Up a USSD Service for MicroFinance Institutions
A pace-by-step guide
- Setting up the logic for USSD is easy with the Africa'southward Talking API. This is a guide to how to use the code provided on this repository to create a USSD that allows users to become registered then access a carte of the post-obit services:
USSD APP Features |
---|
Request to become a call from back up |
Deposit Money to user's account |
Withdraw money from users business relationship |
Ship money from users account to some other |
Repay loan |
Buy Airtime |
INSTALLATION AND GUIDE
-
clone/download the projection into the directory of your choice
-
Create a .env file on your root directory
Be sure to substitute the example variables with your credentials
Docker
-
To install using docker, run
$ docker-compose up -b 8080:8000
This will get-go your awarding on port 8080
Using a virtual environs
-
Create a virtual surround
$ python3 -m venv venv $ . venv/bin/activate
-
Install the project'south dependancies
$ pip install requirements.txt
-
Configure your flask path
$ consign FLASK_APP=manage.py
-
Initialise your database
-
Launch application
-
Head to https://localhost:5000
-
You need to gear up up on the sandbox and create a USSD channel that you will utilise to test past dialing into it via our simulator.
-
Assuming that you are doing your development on a localhost, yous take to expose your application living in the webroot of your localhost to the internet via a tunneling application like Ngrok. Otherwise, if your server has a public IP, you are good to go! Your URL callback for this demo volition get: http:///MfUSSD/microfinanceUSSD.php
-
This application has been adult on an Ubuntu xvi.04LTS and lives in the web root at /var/www/html/MfUSSD. Courtesy of Ngrok, the publicly accessible url is: https://49af2317.ngrok.io (instead of http://localhost) which is referenced in the lawmaking as well. (Create your own which volition be unlike.)
-
The webhook or callback to this application therefore becomes: https://49af2317.ngrok.io/api/v1.i/ussd/callback. To allow the application to talk to the Africa's Talking USSD gateway, this callback URL is placed in the dashboard, under ussd callbacks here.
-
Finally, this application works with a connexion to an sqlite database. This is the default database shipped with python, however its recomended switching to a proper database when deploying the application. As well create a session_levels tabular array and a users tabular array. These details are configured in the models.py and this is required in the master awarding script app/apiv2/views.py
Field | Blazon | Aught | Primal | Default | Extra |
---|---|---|---|---|---|
id | int(6) | Aye | NULL | ||
name | varchar(thirty) | Yes | NULL | ||
phonenumber | varchar(xx) | YES | NULL | ||
city | varchar(30) | Yep | NULL | ||
validation | varchar(thirty) | YES | Nix | ||
reg_date | timestamp | NO | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
- The awarding uses redis for session management. User sessions are stored equally fundamental value pairs in redis.
Features on the Services Listing
This USSD awarding has the post-obit user journey.
-
The user dials the ussd lawmaking - something similar
*384*303#
-
The application checks if the user is registered or not. If the user is registered, the services menu is served which allows the user to: receive SMS, receive a call with an IVR menu.
-
In example the user is not registered, the awarding prompts the user for their proper noun and city (with validations), earlier successfully serving the services menu.
Code walkthrough
This documentation is for the USSD application that lives in https://49af2317.ngrok.io/api/v1.ane/ussd/callback.
- The applications entrypoint is at
app/ussd/views.py
#one. This code but runs after a post request from AT @ussd.road('/ussd/callback', methods =['POST']) def ussd_callback(): """ Handles postal service telephone call back from AT :return: """
Import all the necessary scripts to run this application
# ii. Import all neccesary modules from flask import 1000, make_response from app.models import AnonymousUser from . import ussd from .airtime import Airtime from .deposit import Deposit from .dwelling house import LowerLevelMenu from .register import RegistrationMenu from .withdraw import WithDrawal
Receive the HTTP POST from AT. app/ussd/decorators.py
Nosotros will utilize a decorator that hooks on to the awarding request, to query and initialize session metadata stored in redis.
# 3. go data from ATs post payload session_id = asking.values.get("sessionId", None) phone_number = request.values.get("phoneNumber", None) text = request.values.get("text", "default")
The AT USSD gateway keeps chaining the user response. We desire to catch the latest input from a string like onei2
text_array = text.split("*") user_response = text_array[len(text_array) - 1]
Interactions with the user can be managed using the received sessionId and a level management procedure that your application implements every bit follows.
- The USSD session has a set time limit(xx-180 secs based on provider) under which the sessionId does not change. Using this sessionId, it is easy to navigate your user across the USSD menus by graduating their level(menu step) so that y'all dont serve them the aforementioned bill of fare or lose track of where the user is.
- Query redis for the user's session level using the sessionID as the key. If this exists, the user is returning and they therefore have a stored level. Take hold of that level and serve that user the correct menu. Otherwise, serve the user the home menu.
- The session metadata is stored in flask'southward
thou
global variable to allow for access within the current request context.
# 4. Query session metadata from redis or initialize a new session for this user if the session does non exist # get session session = redis.become(session_id) if session is None: session = {"level": 0, "session_id": session_id} redis.gear up(session_id, json.dumps(session)) else: session = json.loads(session.decode()) # add user, response and session to the request variable g g.user_response = text_array[len(text_array) - 1] g.session = session g.current_user = user thou.phone_number = phone_number chiliad.session_id = session_id return func(* args, ** kwargs)
Before serving the menu, check if the incoming phone number request belongs to a registered user(sort of a login). If they are registered, they can access the bill of fare, otherwise, they should first register.
app/ussd/views.py
# 5. Check if the user is in the db session_id = g.session_id user = g.current_user session = g.session user_response = grand.user_response if isinstance(user, AnonymousUser): # register user menu = RegistrationMenu(session_id = session_id, session = session, phone_number = 1000.phone_number, user_response = user_response, user = user) return card.execute()
If the user is available and all their mandatory fields are complete, and so the awarding switches between their responses to figure out which bill of fare to serve. The get-go menu is normally a effect of receiving a blank text -- the user just dialed in.
# 7. Serve the Services Menu if level < 2: menu = LowerLevelMenu(session_id = session_id, session = session, phone_number = m.phone_number, user_response = user_response, user = user) render menu.execute() if level >= 50: menu = Deposit(session_id = session_id, session = session, phone_number = g.phone_number, user_response = user_response, user = user, level = level) render menu.execute() if level >= 40: menu = WithDrawal(session_id = session_id, session = session, phone_number = g.phone_number, user_response = user_response, user = user, level = level) return menu.execute() if level >= 10: menu = Airtime(session_id = session_id, session = session, phone_number = g.phone_number, user_response = user_response, user = user, level = level) return card.execute()
If the user is not registered, we employ the users level - purely to take the user through the registration process. We also enclose the logic in a condition that prevents the user from sending empty responses.
if isinstance(user, AnonymousUser): # register user menu = RegistrationMenu(session_id = session_id, session = session, phone_number = g.phone_number, user_response = user_response, user = user) return menu.execute()
Complexities of Voice.
- The phonation service included in this script requires a few juggling acts and probably requires a brusque review of its ain. When the user requests a to go a phone call, the following happens. a) The script at https://49af2317.ngrok.io/api/v1.i/ussd/callback requests the telephone call() method through the Africa'southward Talking Vox Gateway, passing the number to be called and the caller/dialer Id. The call is made and it comes into the users phone. When they respond isActive becomes ane.
def please_call(self): # call the user and bridge to a sales person menu_text = "END Please wait while we place your telephone call.\due north" # make a telephone call caller = current_app.config["AT_NUMBER"] to = self.user.phone_number # create a new instance of our awesome gateway gateway = AfricasTalkingGateway( current_app.config["AT_USERNAME"], current_app.config["AT_APIKEY"]) try: gateway.telephone call(caller, to) except AfricasTalkingGateway as e: print "Encountered an error when calling: {}".format(str(east)) # impress the response on to the page so that our gateway can read it return answer(menu_text) instance "ii":
b) Every bit a outcome, Africa'southward Talking gateway check the callback for the voice number in this example +254703554404. c) The callback is a route on our views.py file whose URL is: https://49af2317.ngrok.io/api/v1.i/vocalization/callback d) The instructions are to reply with a text to speech message for the user to enter dtmf digits.
@ussd.route('/phonation/callback', methods =['POST']) def voice_callback(): """ voice_callback from AT'southward gateway is handled here """ sessionId = request.get('sessionId') isActive = request.get('isActive') if isActive == "ane": callerNumber = asking.get('callerNumber') # Become values from the AT'due south Mail request session_id = asking.values.get("sessionId", None) isActive = request.values.get('isActive') serviceCode = request.values.get("serviceCode", None) text = request.values.get("text", "default") text_array = text.carve up("*") user_response = text_array[len(text_array) - 1] # Etch the response menu_text = '<?xml version="1.0" encoding="UTF-8"?>' menu_text += '<Response>' menu_text += '<GetDigits timeout="30" finishOnKey="#" callbackUrl="https://49af2317.ngrok.io/api/v1.ane/phonation/callback">' menu_text += '<Say>"Thank you for calling. Printing 0 to talk to sales, 1 to talk to support or 2 to hear this message again."</Say>' menu_text += '</GetDigits>' menu_text += '<Say>"Thank you for calling. Good farewell!"</Say>' menu_text += '</Response>' # Impress the response onto the folio so that our gateway can read it return answer(menu_text) else: # Read in call details (duration, toll). This flag is set once the call is completed. # Note that the gateway does not expect a response in thie example duration = asking.get('durationInSeconds') currencyCode = request.go('currencyCode') amount = request.go('amount') # Yous can so store this information in the database for your records
eastward) When the user enters the digit - in this case 0, one or 2, this digit is submitted to another route also in our views.py file which lives at https://49af2317.ngrok.io/api/v1.1/voice/menu and which switches betwixt the various dtmf digits to make an outgoing call to the right recipient, who will exist bridged to speak to the person currently listening to music on concord. We specify this music with the ringtone flag as follows: ringbackTone="url_to/static/media/SautiFinaleMoney.mp3"
@ussd.route('/voice/bill of fare') def voice_menu(): """ When the user enters the digit - in this case 0, 1 or 2, this route switches between the various dtmf digits to make an approachable call to the correct recipient, who volition be bridged to speak to the person currently listening to music on concur. We specify this music with the ringtone flag equally follows: ringbackTone="url_to/static/media/SautiFinaleMoney.mp3" """ # one. Receive Postal service from AT isActive = asking.get('isActive') callerNumber = request.get('callerNumber') dtmfDigits = request.get('dtmfDigits') sessionId = request.become('sessionId') # Bank check if isActive=1 to act on the call or isActive=='0' to store the # result if (isActive == '1'): # 2a. Switch through the DTMFDigits if (dtmfDigits == "0"): # Etch response - talk to sales- response = '<?xml version="1.0" encoding="UTF-8"?>' response += '<Response>' response += '<Say>Please hold while we connect you to Sales.</Say>' response += '<Dial phoneNumbers="880.welovenerds@ke.sip.africastalking.com" ringbackTone="{}"/>'.format(url_for('media', path = 'SautiFinaleMoney.mp3')) response += '</Response>' # Print the response onto the folio so that our gateway can read it render answer(response) elif (dtmfDigits == "one"): # 2c. Compose response - talk to support- response = '<?xml version="one.0" encoding="UTF-8"?>' response += '<Response>' response += '<Say>Please hold while we connect you to Support.</Say>' response += '<Dial phoneNumbers="880.welovenerds@ke.sip.africastalking.com" ringbackTone="{}"/>'.format(url_for('media', path = 'SautiFinaleMoney.mp3')) response += '</Response>' # Impress the response onto the page so that our gateway can read information technology render reply(response) elif (dtmfDigits == "2"): # 2nd. Redirect to the main IVR- response = '<?xml version="1.0" encoding="UTF-8"?>' response += '<Response>' response += '<Redirect>{}</Redirect>'.format(url_for('voice_callback')) response += '</Response>' # Print the response onto the page so that our gateway can read information technology return respond(response) else: # 2e. By default talk to back up response = '<?xml version="1.0" encoding="UTF-eight"?>' response += '<Response>' response += '<Say>Please agree while we connect you lot to Support.</Say>' response += '<Dial phoneNumbers="880.welovenerds@ke.sip.africastalking.com" ringbackTone="{}"/>'.format(url_for('media', path = 'SautiFinaleMoney.mp3')) response += '</Response>' # Print the response onto the page and then that our gateway can read it return respond(response) else: # three. Store the data from the Mail durationInSeconds = request.get('durationInSeconds') direction = request.get('management') amount = request.get('amount') callerNumber = request.get('callerNumber') destinationNumber = asking.get('destinationNumber') sessionId = asking.get('sessionId') callStartTime = request.become('callStartTime') isActive = request.get('isActive') currencyCode = request.get('currencyCode') status = request.get('status') # 3a. Store the data, write your SQL statements here-
When the agent/person picks upward, the conversation can go along.
- That is basically our awarding! Happy coding!
How To Set Up Ussd Gateway,
Source: https://github.com/Piusdan/USSD-Python-Demo
Posted by: carneswournig.blogspot.com
0 Response to "How To Set Up Ussd Gateway"
Post a Comment