Remove submodule cic ussd
This commit is contained in:
36
apps/cic-ussd/cic_ussd/db/__init__.py
Normal file
36
apps/cic-ussd/cic_ussd/db/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# third party imports
|
||||
from confini import Config
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def dsn_from_config(config):
|
||||
"""
|
||||
This function builds a data source name mapping to a database from values defined in the config object.
|
||||
:param config: A config object.
|
||||
:type config: Config
|
||||
:return: A database URI.
|
||||
:rtype: str
|
||||
"""
|
||||
scheme = config.get('DATABASE_ENGINE')
|
||||
if config.get('DATABASE_DRIVER') is not None:
|
||||
scheme += '+{}'.format(config.get('DATABASE_DRIVER'))
|
||||
|
||||
dsn = ''
|
||||
if config.get('DATABASE_ENGINE') == 'sqlite':
|
||||
dsn = f'{scheme}:///{config.get("DATABASE_NAME")}'
|
||||
|
||||
else:
|
||||
dsn = '{}://{}:{}@{}:{}/{}'.format(
|
||||
scheme,
|
||||
config.get('DATABASE_USER'),
|
||||
config.get('DATABASE_PASSWORD'),
|
||||
config.get('DATABASE_HOST'),
|
||||
config.get('DATABASE_PORT'),
|
||||
config.get('DATABASE_NAME'),
|
||||
)
|
||||
logg.debug('parsed dsn from config: {}'.format(dsn))
|
||||
return dsn
|
||||
1
apps/cic-ussd/cic_ussd/db/migrations/default/README
Normal file
1
apps/cic-ussd/cic_ussd/db/migrations/default/README
Normal file
@@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
||||
74
apps/cic-ussd/cic_ussd/db/migrations/default/alembic.ini
Normal file
74
apps/cic-ussd/cic_ussd/db/migrations/default/alembic.ini
Normal file
@@ -0,0 +1,74 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = .
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# timezone to use when rendering the date
|
||||
# within the migration file as well as the filename.
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; this defaults
|
||||
# to ./versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path
|
||||
# version_locations = %(here)s/bar %(here)s/bat ./versions
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
80
apps/cic-ussd/cic_ussd/db/migrations/default/env.py
Normal file
80
apps/cic-ussd/cic_ussd/db/migrations/default/env.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name, disable_existing_loggers=True)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = None
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = context.config.attributes.get("connection", None)
|
||||
|
||||
if connectable is None:
|
||||
connectable = engine_from_config(
|
||||
context.config.get_section(context.config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
24
apps/cic-ussd/cic_ussd/db/migrations/default/script.py.mako
Normal file
24
apps/cic-ussd/cic_ussd/db/migrations/default/script.py.mako
Normal file
@@ -0,0 +1,24 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Create ussd session table
|
||||
|
||||
Revision ID: 2a329190a9af
|
||||
Revises: b5ab9371c0b8
|
||||
Create Date: 2020-10-06 00:06:54.354168
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2a329190a9af'
|
||||
down_revision = 'f289e8510444'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('ussd_session',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated', sa.DateTime(), nullable=True),
|
||||
sa.Column('external_session_id', sa.String(), nullable=False),
|
||||
sa.Column('service_code', sa.String(), nullable=False),
|
||||
sa.Column('msisdn', sa.String(), nullable=False),
|
||||
sa.Column('user_input', sa.String(), nullable=True),
|
||||
sa.Column('state', sa.String(), nullable=False),
|
||||
sa.Column('session_data', postgresql.JSON(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('version', sa.Integer(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_ussd_session_external_session_id'), 'ussd_session', ['external_session_id'], unique=True)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index(op.f('ix_ussd_session_external_session_id'), table_name='ussd_session')
|
||||
op.drop_table('ussd_session')
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Create task tracker table
|
||||
|
||||
Revision ID: a571d0aee6f8
|
||||
Revises: 2a329190a9af
|
||||
Create Date: 2021-01-04 18:28:00.462228
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a571d0aee6f8'
|
||||
down_revision = '2a329190a9af'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('task_tracker',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated', sa.DateTime(), nullable=True),
|
||||
sa.Column('task_uuid', sa.String(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('task_tracker')
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Create user table
|
||||
|
||||
Revision ID: f289e8510444
|
||||
Revises:
|
||||
Create Date: 2020-07-14 21:37:13.014200
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f289e8510444'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('blockchain_address', sa.String(), nullable=False),
|
||||
sa.Column('phone_number', sa.String(), nullable=False),
|
||||
sa.Column('preferred_language', sa.String(), nullable=True),
|
||||
sa.Column('password_hash', sa.String(), nullable=True),
|
||||
sa.Column('failed_pin_attempts', sa.Integer(), nullable=False),
|
||||
sa.Column('account_status', sa.Integer(), nullable=False),
|
||||
sa.Column('created', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated', sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_user_phone_number'), 'user', ['phone_number'], unique=True)
|
||||
op.create_index(op.f('ix_user_blockchain_address'), 'user', ['blockchain_address'], unique=True)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index(op.f('ix_user_blockchain_address'), table_name='user')
|
||||
op.drop_index(op.f('ix_user_phone_number'), table_name='user')
|
||||
op.drop_table('user')
|
||||
47
apps/cic-ussd/cic_ussd/db/models/base.py
Normal file
47
apps/cic-ussd/cic_ussd/db/models/base.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# standard imports
|
||||
import datetime
|
||||
|
||||
# third-party imports
|
||||
from sqlalchemy import Column, Integer, DateTime
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
Model = declarative_base(name='Model')
|
||||
|
||||
|
||||
class SessionBase(Model):
|
||||
__abstract__ = True
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
updated = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
||||
|
||||
engine = None
|
||||
session = None
|
||||
query = None
|
||||
|
||||
@staticmethod
|
||||
def create_session():
|
||||
session = sessionmaker(bind=SessionBase.engine)
|
||||
return session()
|
||||
|
||||
@staticmethod
|
||||
def _set_engine(engine):
|
||||
SessionBase.engine = engine
|
||||
|
||||
@staticmethod
|
||||
def build():
|
||||
Model.metadata.create_all(bind=SessionBase.engine)
|
||||
|
||||
@staticmethod
|
||||
# https://docs.sqlalchemy.org/en/13/core/pooling.html#pool-disconnects
|
||||
def connect(data_source_name):
|
||||
engine = create_engine(data_source_name, pool_pre_ping=True)
|
||||
SessionBase._set_engine(engine)
|
||||
|
||||
@staticmethod
|
||||
def disconnect():
|
||||
SessionBase.engine.dispose()
|
||||
SessionBase.engine = None
|
||||
|
||||
19
apps/cic-ussd/cic_ussd/db/models/task_tracker.py
Normal file
19
apps/cic-ussd/cic_ussd/db/models/task_tracker.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
from sqlalchemy import Column, String
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaskTracker(SessionBase):
|
||||
__tablename__ = 'task_tracker'
|
||||
|
||||
def __init__(self, task_uuid):
|
||||
self.task_uuid = task_uuid
|
||||
|
||||
task_uuid = Column(String, nullable=False)
|
||||
90
apps/cic-ussd/cic_ussd/db/models/user.py
Normal file
90
apps/cic-ussd/cic_ussd/db/models/user.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# standard imports
|
||||
from enum import IntEnum
|
||||
|
||||
# third party imports
|
||||
from sqlalchemy import Column, Integer, String
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.encoder import check_password_hash, create_password_hash
|
||||
|
||||
|
||||
class AccountStatus(IntEnum):
|
||||
PENDING = 1
|
||||
ACTIVE = 2
|
||||
LOCKED = 3
|
||||
RESET = 4
|
||||
|
||||
|
||||
class User(SessionBase):
|
||||
"""
|
||||
This class defines a user record along with functions responsible for hashing the user's corresponding password and
|
||||
subsequently verifying a password's validity given an input to compare against the persisted hash.
|
||||
"""
|
||||
__tablename__ = 'user'
|
||||
|
||||
blockchain_address = Column(String)
|
||||
phone_number = Column(String)
|
||||
password_hash = Column(String)
|
||||
failed_pin_attempts = Column(Integer)
|
||||
account_status = Column(Integer)
|
||||
preferred_language = Column(String)
|
||||
|
||||
def __init__(self, blockchain_address, phone_number):
|
||||
self.blockchain_address = blockchain_address
|
||||
self.phone_number = phone_number
|
||||
self.password_hash = None
|
||||
self.failed_pin_attempts = 0
|
||||
self.account_status = AccountStatus.PENDING.value
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User: {self.blockchain_address}>'
|
||||
|
||||
def create_password(self, password):
|
||||
"""This method takes a password value and hashes the value before assigning it to the corresponding
|
||||
`hashed_password` attribute in the user record.
|
||||
:param password: A password value
|
||||
:type password: str
|
||||
"""
|
||||
self.password_hash = create_password_hash(password)
|
||||
|
||||
def verify_password(self, password):
|
||||
"""This method takes a password value and compares it to the user's corresponding `hashed_password` value to
|
||||
establish password validity.
|
||||
:param password: A password value
|
||||
:type password: str
|
||||
:return: Pin validity
|
||||
:rtype: boolean
|
||||
"""
|
||||
return check_password_hash(password, self.password_hash)
|
||||
|
||||
def reset_account_pin(self):
|
||||
"""This method is used to unlock a user's account."""
|
||||
self.failed_pin_attempts = 0
|
||||
self.account_status = AccountStatus.RESET.value
|
||||
|
||||
def get_account_status(self):
|
||||
"""This method checks whether the account is past the allowed number of failed pin attempts.
|
||||
If so, it changes the accounts status to Locked.
|
||||
:return: The account status for a user object
|
||||
:rtype: str
|
||||
"""
|
||||
if self.failed_pin_attempts > 2:
|
||||
self.account_status = AccountStatus.LOCKED.value
|
||||
return AccountStatus(self.account_status).name
|
||||
|
||||
def activate_account(self):
|
||||
"""This method is used to reset failed pin attempts and change account status to Active."""
|
||||
self.failed_pin_attempts = 0
|
||||
self.account_status = AccountStatus.ACTIVE.value
|
||||
|
||||
def has_valid_pin(self):
|
||||
"""This method checks whether the user's account status and if a pin hash is present which implies
|
||||
pin validity.
|
||||
:return: The presence of a valid pin and status of the account being active.
|
||||
:rtype: bool
|
||||
"""
|
||||
valid_pin = None
|
||||
if self.get_account_status() == 'ACTIVE' and self.password_hash is not None:
|
||||
valid_pin = True
|
||||
return valid_pin
|
||||
71
apps/cic-ussd/cic_ussd/db/models/ussd_session.py
Normal file
71
apps/cic-ussd/cic_ussd/db/models/ussd_session.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
from sqlalchemy import Column, String, Integer
|
||||
from sqlalchemy.dialects.postgresql import JSON
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.error import VersionTooLowError
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UssdSession(SessionBase):
|
||||
__tablename__ = 'ussd_session'
|
||||
|
||||
external_session_id = Column(String, nullable=False, index=True, unique=True)
|
||||
service_code = Column(String, nullable=False)
|
||||
msisdn = Column(String, nullable=False)
|
||||
user_input = Column(String)
|
||||
state = Column(String, nullable=False)
|
||||
session_data = Column(JSON)
|
||||
version = Column(Integer, nullable=False)
|
||||
|
||||
def set_data(self, key, session, value):
|
||||
if self.session_data is None:
|
||||
self.session_data = {}
|
||||
self.session_data[key] = value
|
||||
|
||||
# https://stackoverflow.com/questions/42559434/updates-to-json-field-dont-persist-to-db
|
||||
flag_modified(self, "session_data")
|
||||
session.add(self)
|
||||
|
||||
def get_data(self, key):
|
||||
if self.session_data is not None:
|
||||
return self.session_data.get(key)
|
||||
else:
|
||||
return None
|
||||
|
||||
def check_version(self, new_version):
|
||||
if new_version <= self.version:
|
||||
raise VersionTooLowError('New session version number is not greater than last saved version!')
|
||||
|
||||
def update(self, user_input, state, version, session):
|
||||
self.check_version(version)
|
||||
self.user_input = user_input
|
||||
self.state = state
|
||||
self.version = version
|
||||
session.add(self)
|
||||
|
||||
@staticmethod
|
||||
def have_session_for_phone(phone):
|
||||
r = UssdSession.session.query(UssdSession).filter_by(msisdn=phone).first()
|
||||
return r is not None
|
||||
|
||||
def to_json(self):
|
||||
""" This function serializes the in db ussd session object to a JSON object
|
||||
:return: A JSON object of a ussd session in db
|
||||
:rtype: dict
|
||||
"""
|
||||
return {
|
||||
"external_session_id": self.external_session_id,
|
||||
"service_code": self.service_code,
|
||||
"msisdn": self.msisdn,
|
||||
"user_input": self.user_input,
|
||||
"state": self.state,
|
||||
"session_data": self.session_data,
|
||||
"version": self.version
|
||||
}
|
||||
214
apps/cic-ussd/cic_ussd/db/ussd_menu.json
Normal file
214
apps/cic-ussd/cic_ussd/db/ussd_menu.json
Normal file
@@ -0,0 +1,214 @@
|
||||
{
|
||||
"ussd_menu": {
|
||||
"1": {
|
||||
"description": "The self signup process has been initiated and the account is being created",
|
||||
"display_key": "ussd.kenya.account_creation_prompt",
|
||||
"name": "account_creation_prompt",
|
||||
"parent": null
|
||||
},
|
||||
"2": {
|
||||
"description": "Start menu. This is the entry point for users to select their preferred language",
|
||||
"display_key": "ussd.kenya.initial_language_selection",
|
||||
"name": "initial_language_selection",
|
||||
"parent": null
|
||||
},
|
||||
"3": {
|
||||
"description": "PIN setup entry menu",
|
||||
"display_key": "ussd.kenya.initial_pin_entry",
|
||||
"name": "initial_pin_entry",
|
||||
"parent": "initial_language_selection"
|
||||
},
|
||||
"4": {
|
||||
"description": "Confirm new PIN menu",
|
||||
"display_key": "ussd.kenya.initial_pin_confirmation",
|
||||
"name": "initial_pin_confirmation",
|
||||
"parent": "initial_pin_entry"
|
||||
},
|
||||
"5": {
|
||||
"description": "Start menu. This is the entry point for activated users",
|
||||
"display_key": "ussd.kenya.start",
|
||||
"name": "start",
|
||||
"parent": null
|
||||
},
|
||||
"6": {
|
||||
"description": "Send Token recipient entry",
|
||||
"display_key": "ussd.kenya.enter_transaction_recipient",
|
||||
"name": "enter_transaction_recipient",
|
||||
"parent": "start"
|
||||
},
|
||||
"7": {
|
||||
"description": "Send Token amount prompt menu",
|
||||
"display_key": "ussd.kenya.enter_transaction_amount",
|
||||
"name": "enter_transaction_amount",
|
||||
"parent": "start"
|
||||
},
|
||||
"8": {
|
||||
"description": "PIN entry for authorization to send token",
|
||||
"display_key": "ussd.kenya.transaction_pin_authorization",
|
||||
"name": "transaction_pin_authorization",
|
||||
"parent": "start"
|
||||
},
|
||||
"9": {
|
||||
"description": "Terminal of a menu flow where an SMS is expected after.",
|
||||
"display_key": "ussd.kenya.complete",
|
||||
"name": "complete",
|
||||
"parent": null
|
||||
},
|
||||
"10": {
|
||||
"description": "Help menu",
|
||||
"display_key": "ussd.kenya.help",
|
||||
"name": "help",
|
||||
"parent": "start"
|
||||
},
|
||||
"11": {
|
||||
"description": "Manage account menu",
|
||||
"display_key": "ussd.kenya.profile_management",
|
||||
"name": "profile_management",
|
||||
"parent": "start"
|
||||
},
|
||||
"12": {
|
||||
"description": "Manage business directory info",
|
||||
"display_key": "ussd.kenya.select_preferred_language",
|
||||
"name": "select_preferred_language",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"13": {
|
||||
"description": "About business directory info",
|
||||
"display_key": "ussd.kenya.mini_statement_pin_authorization",
|
||||
"name": "mini_statement_pin_authorization",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"14": {
|
||||
"description": "Change business directory info",
|
||||
"display_key": "ussd.kenya.enter_current_pin",
|
||||
"name": "enter_current_pin",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"15": {
|
||||
"description": "New PIN entry menu",
|
||||
"display_key": "ussd.kenya.enter_new_pin",
|
||||
"name": "enter_new_pin",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"16": {
|
||||
"description": "First name entry menu",
|
||||
"display_key": "ussd.kenya.enter_first_name",
|
||||
"name": "enter_first_name",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"17": {
|
||||
"description": "Last name entry menu",
|
||||
"display_key": "ussd.kenya.enter_last_name",
|
||||
"name": "enter_last_name",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"18": {
|
||||
"description": "Gender entry menu",
|
||||
"display_key": "ussd.kenya.enter_gender",
|
||||
"name": "enter_gender",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"19": {
|
||||
"description": "Location entry menu",
|
||||
"display_key": "ussd.kenya.enter_location",
|
||||
"name": "enter_location",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"20": {
|
||||
"description": "Business profile entry menu",
|
||||
"display_key": "ussd.kenya.enter_business_profile",
|
||||
"name": "enter_business_profile",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"21": {
|
||||
"description": "Menu to display a user's entire profile",
|
||||
"display_key": "ussd.kenya.display_user_profile_data",
|
||||
"name": "display_user_profile_data",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"22": {
|
||||
"description": "Pin authorization to change name",
|
||||
"display_key": "ussd.kenya.name_management_pin_authorization",
|
||||
"name": "name_management_pin_authorization",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"23": {
|
||||
"description": "Pin authorization to change gender",
|
||||
"display_key": "ussd.kenya.gender_management_pin_authorization",
|
||||
"name": "gender_management_pin_authorization",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"24": {
|
||||
"description": "Pin authorization to change location",
|
||||
"display_key": "ussd.kenya.location_management_pin_authorization",
|
||||
"name": "location_management_pin_authorization",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"26": {
|
||||
"description": "Pin authorization to display user's profile",
|
||||
"display_key": "ussd.kenya.view_profile_pin_authorization",
|
||||
"name": "view_profile_pin_authorization",
|
||||
"parent": "profile_management"
|
||||
},
|
||||
"27": {
|
||||
"description": "Exit menu",
|
||||
"display_key": "ussd.kenya.exit",
|
||||
"name": "exit",
|
||||
"parent": null
|
||||
},
|
||||
"28": {
|
||||
"description": "Invalid menu option",
|
||||
"display_key": "ussd.kenya.exit_invalid_menu_option",
|
||||
"name": "exit_invalid_menu_option",
|
||||
"parent": null
|
||||
},
|
||||
"29": {
|
||||
"description": "PIN policy violation",
|
||||
"display_key": "ussd.kenya.exit_invalid_pin",
|
||||
"name": "exit_invalid_pin",
|
||||
"parent": null
|
||||
},
|
||||
"30": {
|
||||
"description": "PIN mismatch. New PIN and the new PIN confirmation do not match",
|
||||
"display_key": "ussd.kenya.exit_pin_mismatch",
|
||||
"name": "exit_pin_mismatch",
|
||||
"parent": null
|
||||
},
|
||||
"31": {
|
||||
"description": "Ussd PIN Blocked Menu",
|
||||
"display_key": "ussd.kenya.exit_pin_blocked",
|
||||
"name": "exit_pin_blocked",
|
||||
"parent": null
|
||||
},
|
||||
"32": {
|
||||
"description": "Key params missing in request",
|
||||
"display_key": "ussd.kenya.exit_invalid_request",
|
||||
"name": "exit_invalid_request",
|
||||
"parent": null
|
||||
},
|
||||
"33": {
|
||||
"description": "The user did not select a choice",
|
||||
"display_key": "ussd.kenya.exit_invalid_input",
|
||||
"name": "exit_invalid_input",
|
||||
"parent": null
|
||||
},
|
||||
"34": {
|
||||
"description": "Exit following a successful transaction.",
|
||||
"display_key": "ussd.kenya.exit_successful_transaction",
|
||||
"name": "exit_successful_transaction",
|
||||
"parent": null
|
||||
},
|
||||
"35": {
|
||||
"description": "Manage account menu",
|
||||
"display_key": "ussd.kenya.account_management",
|
||||
"name": "account_management",
|
||||
"parent": "start"
|
||||
},
|
||||
"36": {
|
||||
"description": "Exit following insufficient balance to perform a transaction.",
|
||||
"display_key": "ussd.kenya.exit_insufficient_balance",
|
||||
"name": "exit_insufficient_balance",
|
||||
"parent": null
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user