├── README.md ├── __pycache__ └── main.cpython-310.pyc └── main.py /README.md: -------------------------------------------------------------------------------- 1 | # fastapi-ussd-sample 2 | 3 | To run this application, you must have: 4 | - Python installed on your system 5 | 6 | Clone the repository 7 | 8 | Open the terminal in the application directory 9 | 10 | - run `pip install fastapi` 11 | - run `pip install "uvicorn[standard]"` 12 | - run `pip install cachetools` 13 | - then finally run `uvicorn main:app --reload` 14 | -------------------------------------------------------------------------------- /__pycache__/main.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArkeselDev/fastapi-ussd-sample/a0c0851d4018c88748743046eb452f6aae1730be/__pycache__/main.cpython-310.pyc -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime 2 | from cachetools import Cache 3 | 4 | from typing import Optional 5 | 6 | from fastapi import FastAPI 7 | 8 | from pydantic import BaseModel 9 | 10 | 11 | # maxsize is the size of data the Cache can hold 12 | cache_data = Cache(maxsize=50000) 13 | 14 | 15 | class UssdRequest(BaseModel): 16 | sessionID: str 17 | userID: str 18 | newSession: bool 19 | msisdn: str 20 | userData: str | None = None 21 | network: str 22 | 23 | 24 | class UssdResponse(BaseModel): 25 | sessionID: str | None = None 26 | userID: str | None = None 27 | continueSession: bool | None = None 28 | msisdn: str | None = None 29 | message: str | None = None 30 | 31 | 32 | class UssdState(BaseModel): 33 | sessionID: str 34 | message: str 35 | newSession: bool 36 | msisdn: str 37 | userData: str | None = None 38 | network: str 39 | message: str 40 | level: int 41 | part: int 42 | 43 | 44 | app = FastAPI() 45 | 46 | 47 | @app.get("/") 48 | def read_root(): 49 | return {"Hello": "World"} 50 | 51 | 52 | @app.post("/ussd") 53 | async def handle_ussd(ussd_request: UssdRequest): 54 | response = UssdResponse( 55 | sessionID=ussd_request.sessionID, 56 | userID=ussd_request.userID, 57 | msisdn=ussd_request.msisdn, 58 | ) 59 | 60 | if ussd_request.newSession: 61 | response.message = ( 62 | "Welcome to Arkesel Voting Portal. Please vote for your favourite service from Arkesel" 63 | + "\n1. SMS" 64 | + "\n2. Voice" 65 | + "\n3. Email" 66 | + "\n4. USSD" 67 | + "\n5. Payments" 68 | ) 69 | response.continueSession = True 70 | 71 | # Keep track of the USSD state of the user and their session 72 | 73 | current_state = UssdState( 74 | sessionID=ussd_request.sessionID, 75 | msisdn=ussd_request.msisdn, 76 | userData=ussd_request.userData, 77 | network=ussd_request.network, 78 | message=response.message, 79 | level=1, 80 | part=1, 81 | newSession=True, 82 | ) 83 | 84 | user_response_tracker = cache_data.get(hash(ussd_request.sessionID), []) 85 | 86 | user_response_tracker.append(current_state) 87 | 88 | cache_data[hash(ussd_request.sessionID)] = user_response_tracker 89 | else: 90 | last_response = cache_data.get(hash(ussd_request.sessionID), [])[-1] 91 | 92 | if last_response.level == 1: 93 | user_data = ussd_request.userData 94 | 95 | if user_data == "1": 96 | response.message = ( 97 | "For SMS which of the features do you like best?" 98 | + "\n1. From File" 99 | + "\n2. Quick SMS" 100 | + "\n\n #. Next Page" 101 | ) 102 | response.continueSession = True 103 | 104 | # Keep track of the USSD state of the user and their session 105 | 106 | current_state = UssdState( 107 | sessionID=ussd_request.sessionID, 108 | msisdn=ussd_request.msisdn, 109 | userData=ussd_request.userData, 110 | network=ussd_request.network, 111 | message=response.message, 112 | level=2, 113 | part=1, 114 | newSession=ussd_request.newSession, 115 | ) 116 | 117 | user_response_tracker = cache_data.get(hash(ussd_request.sessionID), []) 118 | 119 | user_response_tracker.append(current_state) 120 | 121 | cache_data[hash(ussd_request.sessionID)] = user_response_tracker 122 | elif ( 123 | user_data == "2" 124 | or user_data == "3" 125 | or user_data == "4" 126 | or user_data == "5" 127 | ): 128 | response.message = "Thank you for voting!" 129 | response.continueSession = False 130 | else: 131 | response.message = "Bad choice!" 132 | response.continueSession = False 133 | elif last_response.level == 2: 134 | possible_choices = ["1", "2", "3", "4"] 135 | 136 | if last_response.part == 1 and ussd_request.userData == "#": 137 | response.message = ( 138 | "For SMS which of the features do you like best?" 139 | + "\n3. Bulk SMS" 140 | + "\n\n*. Go Back" 141 | + "\n#. Next Page" 142 | ) 143 | response.continueSession = True 144 | 145 | current_state = UssdState( 146 | sessionID=ussd_request.sessionID, 147 | msisdn=ussd_request.msisdn, 148 | userData=ussd_request.userData, 149 | network=ussd_request.network, 150 | message=response.message, 151 | level=2, 152 | part=2, 153 | newSession=ussd_request.newSession, 154 | ) 155 | 156 | user_response_tracker = cache_data.get(hash(ussd_request.sessionID), []) 157 | 158 | user_response_tracker.append(current_state) 159 | 160 | cache_data[hash(ussd_request.sessionID)] = user_response_tracker 161 | elif last_response.part == 2 and ussd_request.userData == "#": 162 | response.message = ( 163 | "For SMS which of the features do you like best?" 164 | + "\n4. SMS To Contacts" 165 | + "\n\n*. Go Back" 166 | ) 167 | response.continueSession = True 168 | 169 | current_state = UssdState( 170 | sessionID=ussd_request.sessionID, 171 | msisdn=ussd_request.msisdn, 172 | userData=ussd_request.userData, 173 | network=ussd_request.network, 174 | message=response.message, 175 | level=2, 176 | part=3, 177 | newSession=ussd_request.newSession, 178 | ) 179 | 180 | user_response_tracker = cache_data.get(hash(ussd_request.sessionID), []) 181 | 182 | user_response_tracker.append(current_state) 183 | 184 | cache_data[hash(ussd_request.sessionID)] = user_response_tracker 185 | elif last_response.part == 3 and ussd_request.userData == "*": 186 | response.message = ( 187 | "For SMS which of the features do you like best?" 188 | + "\n3. Bulk SMS" 189 | + "\n\n*. Go Back" 190 | + "\n#. Next Page" 191 | ) 192 | response.continueSession = True 193 | 194 | current_state = UssdState( 195 | sessionID=ussd_request.sessionID, 196 | msisdn=ussd_request.msisdn, 197 | userData=ussd_request.userData, 198 | network=ussd_request.network, 199 | message=response.message, 200 | level=2, 201 | part=2, 202 | newSession=ussd_request.newSession, 203 | ) 204 | 205 | user_response_tracker = cache_data.get(hash(ussd_request.sessionID), []) 206 | 207 | user_response_tracker.append(current_state) 208 | 209 | cache_data[hash(ussd_request.sessionID)] = user_response_tracker 210 | elif last_response.part == 2 and ussd_request.userData == "*": 211 | response.message = ( 212 | "For SMS which of the features do you like best?" 213 | + "\n1. From File" 214 | + "\n2. Quick SMS" 215 | + "\n\n #. Next Page" 216 | ) 217 | response.continueSession = True 218 | 219 | # Keep track of the USSD state of the user and their session 220 | 221 | current_state = UssdState( 222 | sessionID=ussd_request.sessionID, 223 | msisdn=ussd_request.msisdn, 224 | userData=ussd_request.userData, 225 | network=ussd_request.network, 226 | message=response.message, 227 | level=2, 228 | part=1, 229 | newSession=ussd_request.newSession, 230 | ) 231 | 232 | user_response_tracker = cache_data.get(hash(ussd_request.sessionID), []) 233 | 234 | user_response_tracker.append(current_state) 235 | 236 | cache_data[hash(ussd_request.sessionID)] = user_response_tracker 237 | elif ussd_request.userData in possible_choices: 238 | response.message = "Thank you for voting!" 239 | response.continueSession = False 240 | else: 241 | response.message = "Bad choice!" 242 | response.continueSession = False 243 | 244 | return response 245 | --------------------------------------------------------------------------------