From c3c43c28a5b84d2e0d89644ab86d65215f5f4c62 Mon Sep 17 00:00:00 2001 From: Philip Wafula Date: Mon, 29 Nov 2021 21:24:37 +0000 Subject: [PATCH] Philip/social pin recovery --- .../default/versions/f289e8510444_.py | 2 + apps/cic-ussd/cic_ussd/db/models/account.py | 33 ++- apps/cic-ussd/cic_ussd/db/ussd_menu.json | 96 +++++++++ apps/cic-ussd/cic_ussd/phone_number.py | 1 - apps/cic-ussd/cic_ussd/processor/menu.py | 101 ++++++++- .../cic_ussd/state_machine/logic/menu.py | 12 ++ .../cic_ussd/state_machine/logic/pin_guard.py | 200 ++++++++++++++++++ apps/cic-ussd/states/exit_states.json | 2 + .../states/pin_management_states.json | 16 ++ .../account_management_transitions.json | 2 +- .../transitions/pin_guard_transitions.json | 119 +++++++++++ .../pin_management_transitions.json | 20 ++ .../transitions/pin_reset_transitions.json | 43 ++++ apps/cic-ussd/var/lib/locale/helpers.en.yml | 17 +- apps/cic-ussd/var/lib/locale/helpers.sw.yml | 17 +- apps/cic-ussd/var/lib/locale/ussd.en.yml | 76 ++++++- apps/cic-ussd/var/lib/locale/ussd.sw.yml | 76 ++++++- 17 files changed, 824 insertions(+), 9 deletions(-) create mode 100644 apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py create mode 100644 apps/cic-ussd/states/pin_management_states.json create mode 100644 apps/cic-ussd/transitions/pin_guard_transitions.json create mode 100644 apps/cic-ussd/transitions/pin_management_transitions.json create mode 100644 apps/cic-ussd/transitions/pin_reset_transitions.json diff --git a/apps/cic-ussd/cic_ussd/db/migrations/default/versions/f289e8510444_.py b/apps/cic-ussd/cic_ussd/db/migrations/default/versions/f289e8510444_.py index b871cc9c..0bec677a 100644 --- a/apps/cic-ussd/cic_ussd/db/migrations/default/versions/f289e8510444_.py +++ b/apps/cic-ussd/cic_ussd/db/migrations/default/versions/f289e8510444_.py @@ -24,6 +24,8 @@ def upgrade(): 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('guardians', sa.String(), nullable=True), + sa.Column('guardian_quora', sa.Integer(), nullable=False), sa.Column('status', sa.Integer(), nullable=False), sa.Column('created', sa.DateTime(), nullable=False), sa.Column('updated', sa.DateTime(), nullable=False), diff --git a/apps/cic-ussd/cic_ussd/db/models/account.py b/apps/cic-ussd/cic_ussd/db/models/account.py index fe0fe21c..505f822b 100644 --- a/apps/cic-ussd/cic_ussd/db/models/account.py +++ b/apps/cic-ussd/cic_ussd/db/models/account.py @@ -4,6 +4,8 @@ import json # external imports from cic_eth.api import Api from cic_types.condiments import MetadataPointer +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm.session import Session # local imports from cic_ussd.account.metadata import get_cached_preferred_language, parse_account_metadata @@ -12,8 +14,9 @@ from cic_ussd.db.enum import AccountStatus from cic_ussd.db.models.base import SessionBase from cic_ussd.db.models.task_tracker import TaskTracker from cic_ussd.encoder import check_password_hash, create_password_hash -from sqlalchemy import Column, Integer, String -from sqlalchemy.orm.session import Session +from cic_ussd.phone_number import Support + +support_phone = Support.phone_number class Account(SessionBase): @@ -29,12 +32,16 @@ class Account(SessionBase): failed_pin_attempts = Column(Integer) status = Column(Integer) preferred_language = Column(String) + guardians = Column(String) + guardian_quora = Column(Integer) 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.guardians = f'{support_phone}' if support_phone else None + self.guardian_quora = 1 self.status = AccountStatus.PENDING.value def __repr__(self): @@ -45,6 +52,28 @@ class Account(SessionBase): self.failed_pin_attempts = 0 self.status = AccountStatus.ACTIVE.value + def add_guardian(self, phone_number: str): + set_guardians = phone_number + if self.guardians: + set_guardians = self.guardians.split(',') + set_guardians.append(phone_number) + ','.join(set_guardians) + self.guardians = set_guardians + + def remove_guardian(self, phone_number: str): + set_guardians = self.guardians.split(',') + set_guardians.remove(phone_number) + if len(set_guardians) > 1: + self.guardians = ','.join(set_guardians) + else: + self.guardians = set_guardians[0] + + def get_guardians(self) -> list: + return self.guardians.split(',') if self.guardians else [] + + def set_guardian_quora(self, quora: int): + self.guardian_quora = quora + 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. diff --git a/apps/cic-ussd/cic_ussd/db/ussd_menu.json b/apps/cic-ussd/cic_ussd/db/ussd_menu.json index 616021ea..a176f952 100644 --- a/apps/cic-ussd/cic_ussd/db/ussd_menu.json +++ b/apps/cic-ussd/cic_ussd/db/ussd_menu.json @@ -317,6 +317,102 @@ "display_key": "ussd.kenya.exit_successful_token_selection", "name": "exit_successful_token_selection", "parent": null + }, + "54": { + "description": "Pin management menu for operations related to an account's pin.", + "display_key": "ussd.kenya.pin_management", + "name": "pin_management", + "parent": "start" + }, + "55": { + "description": "Phone number entry for account whose pin is being reset.", + "display_key": "ussd.kenya.reset_guarded_pin", + "name": "reset_guarded_pin", + "parent": "pin_management" + }, + "56": { + "description": "Pin entry for initiating request to reset an account's pin.", + "display_key": "ussd.kenya.reset_guarded_pin_authorization", + "name": "reset_guarded_pin_authorization", + "parent": "pin_management" + }, + "57": { + "description": "Exit menu following successful pin reset initiation.", + "display_key": "ussd.kenya.exit_pin_reset_initiated_success", + "name": "exit_pin_reset_initiated_success", + "parent": "pin_management" + }, + "58": { + "description": "Exit menu in the event that an account is not a set guardian.", + "display_key": "ussd.kenya.exit_not_authorized_for_pin_reset", + "name": "exit_not_authorized_for_pin_reset", + "parent": "pin_management" + }, + "59": { + "description": "Pin guard menu for handling guardianship operations.", + "display_key": "ussd.kenya.guard_pin", + "name": "guard_pin", + "parent": "pin_management" + }, + "60": { + "description": "Pin entry to display a list of set guardians.", + "display_key": "ussd.kenya.guardian_list_pin_authorization", + "name": "guardian_list_pin_authorization", + "parent": "guard_pin" + }, + "61": { + "description": "Menu to display list of set guardians.", + "display_key": "ussd.kenya.guardian_list", + "name": "guardian_list", + "parent": "guard_pin" + }, + "62": { + "description": "Phone number entry to add an account as a guardian to reset pin.", + "display_key": "ussd.kenya.add_guardian", + "name": "add_guardian", + "parent": "guard_pin" + }, + "63": { + "description": "Pin entry to confirm addition of an account as a guardian.", + "display_key": "ussd.kenya.add_guardian_pin_authorization", + "name": "add_guardian_pin_authorization", + "parent": "guard_pin" + }, + "64": { + "description": "Exit menu when an account is successfully added as pin reset guardian.", + "display_key": "ussd.kenya.exit_guardian_addition_success", + "name": "exit_guardian_addition_success", + "parent": "guard_pin" + }, + "65": { + "description": "Phone number entry to remove an account as a guardian to reset pin.", + "display_key": "ussd.kenya.remove_guardian", + "name": "remove_guardian", + "parent": "guard_pin" + }, + "66": { + "description": "Pin entry to confirm removal of an account as a guardian.", + "display_key": "ussd.kenya.remove_guardian_pin_authorization", + "name": "remove_guardian_pin_authorization", + "parent": "guard_pin" + }, + "67": { + "description": "Exit menu when an account is successfully removed as pin reset guardian.", + "display_key": "ussd.kenya.exit_guardian_removal_success", + "name": "exit_guardian_removal_success", + "parent": "guard_pin" + }, + "68": { + "description": "Exit menu when invalid phone number entry for guardian addition. ", + "display_key": "ussd.kenya.exit_invalid_guardian_addition", + "name": "exit_invalid_guardian_addition", + "parent": "guard_pin" + }, + "69": { + "description": "Exit menu when invalid phone number entry for guardian removal. ", + "display_key": "ussd.kenya.exit_invalid_guardian_removal", + "name": "exit_invalid_guardian_removal", + "parent": "guard_pin" } } } \ No newline at end of file diff --git a/apps/cic-ussd/cic_ussd/phone_number.py b/apps/cic-ussd/cic_ussd/phone_number.py index 0bb0a9cb..4541b69b 100644 --- a/apps/cic-ussd/cic_ussd/phone_number.py +++ b/apps/cic-ussd/cic_ussd/phone_number.py @@ -5,7 +5,6 @@ from typing import Optional import phonenumbers # local imports -from cic_ussd.db.models.account import Account class E164Format: diff --git a/apps/cic-ussd/cic_ussd/processor/menu.py b/apps/cic-ussd/cic_ussd/processor/menu.py index 30f09800..5346a35d 100644 --- a/apps/cic-ussd/cic_ussd/processor/menu.py +++ b/apps/cic-ussd/cic_ussd/processor/menu.py @@ -121,6 +121,27 @@ class MenuProcessor: self.display_key, preferred_language, last_transaction_set=last_transaction_set ) + def add_guardian_pin_authorization(self): + guardian_information = self.guardian_metadata() + return self.pin_authorization(guardian_information=guardian_information) + + def guardian_list(self): + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + set_guardians = self.account.get_guardians() + if set_guardians: + guardians_list = '' + guardians_list_header = translation_for('helpers.guardians_list_header', preferred_language) + for phone_number in set_guardians: + guardian = Account.get_by_phone_number(phone_number, self.session) + guardian_information = guardian.standard_metadata_id() + guardians_list += f'{guardian_information}\n' + guardians_list = guardians_list_header + '\n' + guardians_list + else: + guardians_list = translation_for('helpers.no_guardians_list', preferred_language) + return translation_for(self.display_key, preferred_language, guardians_list=guardians_list) + def account_tokens(self) -> str: cached_token_data_list = get_cached_token_data_list(self.account.blockchain_address) token_data_list = parse_token_list(cached_token_data_list) @@ -207,6 +228,20 @@ class MenuProcessor: f'{self.display_key}.retry', preferred_language, retry_pin_entry=retry_pin_entry ) + def guarded_account_metadata(self): + guarded_account_phone_number = self.ussd_session.get('data').get('guarded_account_phone_number') + guarded_account = Account.get_by_phone_number(guarded_account_phone_number, self.session) + return guarded_account.standard_metadata_id() + + def guardian_metadata(self): + guardian_phone_number = self.ussd_session.get('data').get('guardian_phone_number') + guardian = Account.get_by_phone_number(guardian_phone_number, self.session) + return guardian.standard_metadata_id() + + def reset_guarded_pin_authorization(self): + guarded_account_information = self.guarded_account_metadata() + return self.pin_authorization(guarded_account_information=guarded_account_information) + def start_menu(self): """ :return: @@ -277,6 +312,47 @@ class MenuProcessor: sender_information=tx_sender_information ) + def exit_guardian_addition_success(self) -> str: + guardian_information = self.guardian_metadata() + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return translation_for(self.display_key, + preferred_language, + guardian_information=guardian_information) + + def exit_guardian_removal_success(self): + guardian_information = self.guardian_metadata() + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return translation_for(self.display_key, + preferred_language, + guardian_information=guardian_information) + + def exit_invalid_guardian_addition(self): + failure_reason = self.ussd_session.get('data').get('failure_reason') + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return translation_for(self.display_key, preferred_language, error_exit=failure_reason) + + def exit_invalid_guardian_removal(self): + failure_reason = self.ussd_session.get('data').get('failure_reason') + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return translation_for(self.display_key, preferred_language, error_exit=failure_reason) + + def exit_pin_reset_initiated_success(self): + guarded_account_information = self.guarded_account_metadata() + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return translation_for(self.display_key, + preferred_language, + guarded_account_information=guarded_account_information) + def exit_insufficient_balance(self): """ :return: @@ -379,18 +455,41 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio return menu_processor.transaction_pin_authorization() if menu_name == 'token_selection_pin_authorization': - logg.debug(f'RESPONSE IS: {menu_processor.token_selection_pin_authorization()}') return menu_processor.token_selection_pin_authorization() if menu_name == 'exit_insufficient_balance': return menu_processor.exit_insufficient_balance() + if menu_name == 'exit_invalid_guardian_addition': + return menu_processor.exit_invalid_guardian_addition() + + if menu_name == 'exit_invalid_guardian_removal': + return menu_processor.exit_invalid_guardian_removal() + if menu_name == 'exit_successful_transaction': return menu_processor.exit_successful_transaction() + if menu_name == 'exit_guardian_addition_success': + return menu_processor.exit_guardian_addition_success() + + if menu_name == 'exit_guardian_removal_success': + return menu_processor.exit_guardian_removal_success() + + if menu_name == 'exit_pin_reset_initiated_success': + return menu_processor.exit_pin_reset_initiated_success() + if menu_name == 'account_balances': return menu_processor.account_balances() + if menu_name == 'guardian_list': + return menu_processor.guardian_list() + + if menu_name == 'add_guardian_pin_authorization': + return menu_processor.add_guardian_pin_authorization() + + if menu_name == 'reset_guarded_pin_authorization': + return menu_processor.reset_guarded_pin_authorization() + if 'pin_authorization' in menu_name: return menu_processor.pin_authorization() diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/menu.py b/apps/cic-ussd/cic_ussd/state_machine/logic/menu.py index 931a2a06..cf2b127b 100644 --- a/apps/cic-ussd/cic_ussd/state_machine/logic/menu.py +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/menu.py @@ -81,6 +81,18 @@ def menu_six_selected(state_machine_data: Tuple[str, dict, Account, Session]) -> return user_input == '6' +def menu_nine_selected(state_machine_data: Tuple[str, dict, Account, Session]) -> bool: + """ + This function checks that user input matches a string with value '6' + :param state_machine_data: A tuple containing user input, a ussd session and user object. + :type state_machine_data: tuple + :return: A user input's match with '6' + :rtype: bool + """ + user_input, ussd_session, account, session = state_machine_data + return user_input == '9' + + def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, Account, Session]) -> bool: """ This function checks that user input matches a string with value '00' diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py b/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py new file mode 100644 index 00000000..4e87e473 --- /dev/null +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py @@ -0,0 +1,200 @@ +# standard imports +import logging +from typing import Tuple + +# external imports +import celery +import i18n +from phonenumbers.phonenumberutil import NumberParseException +from sqlalchemy.orm.session import Session + +# local imports +from cic_ussd.account.metadata import get_cached_preferred_language +from cic_ussd.db.models.account import Account +from cic_ussd.db.models.base import SessionBase +from cic_ussd.phone_number import process_phone_number, E164Format +from cic_ussd.session.ussd_session import save_session_data +from cic_ussd.translation import translation_for + + +logg = logging.getLogger(__file__) + + +def save_guardian_to_session_data(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + session_data = ussd_session.get('data') or {} + guardian_phone_number = process_phone_number(phone_number=user_input, region=E164Format.region) + session_data['guardian_phone_number'] = guardian_phone_number + save_session_data('cic-ussd', session, session_data, ussd_session) + + +def save_guarded_account_session_data(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + session_data = ussd_session.get('data') or {} + guarded_account_phone_number = process_phone_number(phone_number=user_input, region=E164Format.region) + session_data['guarded_account_phone_number'] = guarded_account_phone_number + save_session_data('cic-ussd', session, session_data, ussd_session) + + +def retrieve_person_metadata(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + guardian_phone_number = process_phone_number(user_input, E164Format.region) + guardian = Account.get_by_phone_number(guardian_phone_number, session) + blockchain_address = guardian.blockchain_address + s_query_person_metadata = celery.signature( + 'cic_ussd.tasks.metadata.query_person_metadata', [blockchain_address], queue='cic-ussd') + s_query_person_metadata.apply_async() + + +def is_valid_guardian_addition(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + try: + phone_number = process_phone_number(user_input, E164Format.region) + except NumberParseException: + phone_number = None + + preferred_language = get_cached_preferred_language(account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + + is_valid_account = Account.get_by_phone_number(phone_number, session) is not None + is_initiator = phone_number == account.phone_number + is_existent_guardian = phone_number in account.get_guardians() + + failure_reason = '' + if not is_valid_account: + failure_reason = translation_for('helpers.error.no_matching_account', preferred_language) + + if is_initiator: + failure_reason = translation_for('helpers.error.is_initiator', preferred_language) + + if is_existent_guardian: + failure_reason = translation_for('helpers.error.is_existent_guardian', preferred_language) + + if failure_reason: + session_data = ussd_session.get('data') or {} + session_data['failure_reason'] = failure_reason + save_session_data('cic-ussd', session, session_data, ussd_session) + + return phone_number is not None and is_valid_account and not is_existent_guardian and not is_initiator + + +def add_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + guardian_phone_number = ussd_session.get('data').get('guardian_phone_number') + account.add_guardian(guardian_phone_number) + session.add(account) + session.flush() + SessionBase.release_session(session=session) + + +def is_set_pin_guardian(account: Account, checked_number: str, preferred_language: str, session: Session, ussd_session: dict): + """""" + failure_reason = '' + set_guardians = [] + if account: + set_guardians = account.get_guardians() + else: + failure_reason = translation_for('helpers.error.no_matching_account', preferred_language) + + is_set_guardian = checked_number in set_guardians + is_initiator = checked_number == account.phone_number + + if not is_set_guardian: + failure_reason = translation_for('helpers.error.is_not_existent_guardian', preferred_language) + + if is_initiator: + failure_reason = translation_for('helpers.error.is_initiator', preferred_language) + + if failure_reason: + session_data = ussd_session.get('data') or {} + session_data['failure_reason'] = failure_reason + save_session_data('cic-ussd', session, session_data, ussd_session) + + return is_set_guardian and not is_initiator + + +def is_dialers_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]): + user_input, ussd_session, account, session = state_machine_data + phone_number = process_phone_number(phone_number=user_input, region=E164Format.region) + preferred_language = get_cached_preferred_language(account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return is_set_pin_guardian(account, phone_number, preferred_language, session, ussd_session) + + +def is_others_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]): + user_input, ussd_session, account, session = state_machine_data + preferred_language = get_cached_preferred_language(account.blockchain_address) + phone_number = process_phone_number(phone_number=user_input, region=E164Format.region) + guarded_account = Account.get_by_phone_number(phone_number, session) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + return is_set_pin_guardian(guarded_account, account.phone_number, preferred_language, session, ussd_session) + + +def remove_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + guardian_phone_number = ussd_session.get('data').get('guardian_phone_number') + account.remove_guardian(guardian_phone_number) + session.add(account) + session.flush() + SessionBase.release_session(session=session) + + +def initiate_pin_reset(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + session_data = ussd_session.get('data') + quorum_count = session_data['quorum_count'] if session_data.get('quorum_count') else 0 + quorum_count += 1 + session_data['quorum_count'] = quorum_count + save_session_data('cic-ussd', session, session_data, ussd_session) + guarded_account_phone_number = session_data.get('guarded_account_phone_number') + guarded_account = Account.get_by_phone_number(guarded_account_phone_number, session) + if quorum_count >= guarded_account.guardian_quora: + guarded_account.reset_pin(session) + logg.debug(f'Reset initiated for: {guarded_account.phone_number}') + session_data['quorum_count'] = 0 + save_session_data('cic-ussd', session, session_data, ussd_session) diff --git a/apps/cic-ussd/states/exit_states.json b/apps/cic-ussd/states/exit_states.json index 982e6b3a..6060a732 100644 --- a/apps/cic-ussd/states/exit_states.json +++ b/apps/cic-ussd/states/exit_states.json @@ -11,5 +11,7 @@ "account_creation_prompt", "exit_successful_transaction", "exit_insufficient_balance", + "exit_invalid_guardian_addition", + "exit_invalid_guardian_removal", "complete" ] \ No newline at end of file diff --git a/apps/cic-ussd/states/pin_management_states.json b/apps/cic-ussd/states/pin_management_states.json new file mode 100644 index 00000000..fe4548f3 --- /dev/null +++ b/apps/cic-ussd/states/pin_management_states.json @@ -0,0 +1,16 @@ +[ + "pin_management", + "reset_guarded_pin", + "reset_guarded_pin_authorization", + "exit_pin_reset_initiated_success", + "exit_not_authorized_for_pin_reset", + "guard_pin", + "guardian_list_pin_authorization", + "guardian_list", + "add_guardian", + "add_guardian_pin_authorization", + "exit_guardian_addition_success", + "remove_guardian", + "remove_guardian_pin_authorization", + "exit_guardian_removal_success" +] \ No newline at end of file diff --git a/apps/cic-ussd/transitions/account_management_transitions.json b/apps/cic-ussd/transitions/account_management_transitions.json index 535e83dc..11691e83 100644 --- a/apps/cic-ussd/transitions/account_management_transitions.json +++ b/apps/cic-ussd/transitions/account_management_transitions.json @@ -50,7 +50,7 @@ { "trigger": "scan_data", "source": "account_management", - "dest": "enter_current_pin", + "dest": "pin_management", "conditions": "cic_ussd.state_machine.logic.menu.menu_five_selected" }, { diff --git a/apps/cic-ussd/transitions/pin_guard_transitions.json b/apps/cic-ussd/transitions/pin_guard_transitions.json new file mode 100644 index 00000000..fb77fba3 --- /dev/null +++ b/apps/cic-ussd/transitions/pin_guard_transitions.json @@ -0,0 +1,119 @@ +[ + { + "trigger": "scan_data", + "source": "guard_pin", + "dest": "guardian_list_pin_authorization", + "conditions": "cic_ussd.state_machine.logic.menu.menu_one_selected" + }, + { + "trigger": "scan_data", + "source": "guardian_list_pin_authorization", + "dest": "guardian_list", + "conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin" + }, + { + "trigger": "scan_data", + "source": "guardian_list_pin_authorization", + "dest": "exit_pin_blocked", + "conditions": "cic_ussd.state_machine.logic.pin.is_blocked_pin" + }, + { + "trigger": "scan_data", + "source": "guard_pin", + "dest": "add_guardian", + "conditions": "cic_ussd.state_machine.logic.menu.menu_two_selected" + }, + { + "trigger": "scan_data", + "source": "add_guardian", + "dest": "add_guardian_pin_authorization", + "after": [ + "cic_ussd.state_machine.logic.pin_guard.save_guardian_to_session_data", + "cic_ussd.state_machine.logic.pin_guard.retrieve_person_metadata" + ], + "conditions": "cic_ussd.state_machine.logic.pin_guard.is_valid_guardian_addition" + }, + { + "trigger": "scan_data", + "source": "add_guardian", + "dest": "exit_invalid_guardian_addition", + "unless": "cic_ussd.state_machine.logic.pin_guard.is_valid_guardian_addition" + }, + { + "trigger": "scan_data", + "source": "add_guardian_pin_authorization", + "dest": "exit_guardian_addition_success", + "after": "cic_ussd.state_machine.logic.pin_guard.add_pin_guardian", + "conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin" + }, + { + "trigger": "scan_data", + "source": "add_guardian_pin_authorization", + "dest": "exit_pin_blocked", + "conditions": "cic_ussd.state_machine.logic.pin.is_locked_account" + }, + { + "trigger": "scan_data", + "source": "guard_pin", + "dest": "remove_guardian", + "conditions": "cic_ussd.state_machine.logic.menu.menu_three_selected" + }, + { + "trigger": "scan_data", + "source": "remove_guardian", + "dest": "remove_guardian_pin_authorization", + "after": [ + "cic_ussd.state_machine.logic.pin_guard.save_guardian_to_session_data", + "cic_ussd.state_machine.logic.pin_guard.retrieve_person_metadata" + ], + "conditions": "cic_ussd.state_machine.logic.pin_guard.is_dialers_pin_guardian" + }, + { + "trigger": "scan_data", + "source": "remove_guardian", + "dest": "exit_invalid_guardian_removal", + "unless": "cic_ussd.state_machine.logic.pin_guard.is_dialers_pin_guardian" + }, + { + "trigger": "scan_data", + "source": "remove_guardian_pin_authorization", + "dest": "exit_guardian_removal_success", + "after": "cic_ussd.state_machine.logic.pin_guard.remove_pin_guardian", + "conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin" + }, + { + "trigger": "scan_data", + "source": "remove_guardian_pin_authorization", + "dest": "exit_pin_blocked", + "conditions": "cic_ussd.state_machine.logic.pin.is_locked_account" + }, + { + "trigger": "scan_data", + "source": "exit_guardian_removal_success", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_nine_selected" + }, + { + "trigger": "scan_data", + "source": "exit_invalid_guardian_addition", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_nine_selected" + }, + { + "trigger": "scan_data", + "source": "exit_invalid_guardian_removal", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_nine_selected" + }, + { + "trigger": "scan_data", + "source": "guardian_list", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_nine_selected" + }, + { + "trigger": "scan_data", + "source": "guard_pin", + "dest": "exit_invalid_menu_option" + } +] \ No newline at end of file diff --git a/apps/cic-ussd/transitions/pin_management_transitions.json b/apps/cic-ussd/transitions/pin_management_transitions.json new file mode 100644 index 00000000..45cd9986 --- /dev/null +++ b/apps/cic-ussd/transitions/pin_management_transitions.json @@ -0,0 +1,20 @@ +[ + { + "trigger": "scan_data", + "source": "pin_management", + "dest": "enter_current_pin", + "conditions": "cic_ussd.state_machine.logic.menu.menu_one_selected" + }, + { + "trigger": "scan_data", + "source": "pin_management", + "dest": "reset_guarded_pin", + "conditions": "cic_ussd.state_machine.logic.menu.menu_two_selected" + }, + { + "trigger": "scan_data", + "source": "pin_management", + "dest": "guard_pin", + "conditions": "cic_ussd.state_machine.logic.menu.menu_three_selected" + } +] \ No newline at end of file diff --git a/apps/cic-ussd/transitions/pin_reset_transitions.json b/apps/cic-ussd/transitions/pin_reset_transitions.json new file mode 100644 index 00000000..f0c2b2ce --- /dev/null +++ b/apps/cic-ussd/transitions/pin_reset_transitions.json @@ -0,0 +1,43 @@ +[ + { + "trigger": "scan_data", + "source": "reset_guarded_pin", + "dest": "reset_guarded_pin_authorization", + "after": [ + "cic_ussd.state_machine.logic.pin_guard.save_guarded_account_session_data", + "cic_ussd.state_machine.logic.pin_guard.retrieve_person_metadata" + ], + "conditions": "cic_ussd.state_machine.logic.pin_guard.is_others_pin_guardian" + }, + { + "trigger": "scan_data", + "source": "reset_guarded_pin_authorization", + "dest": "exit_pin_reset_initiated_success", + "after": "cic_ussd.state_machine.logic.pin_guard.initiate_pin_reset", + "conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin" + }, + { + "trigger": "scan_data", + "source": "exit_pin_reset_initiated_success", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_nine_selected" + }, + { + "trigger": "scan_data", + "source": "reset_guarded_pin_authorization", + "dest": "exit_pin_blocked", + "conditions": "cic_ussd.state_machine.logic.pin.is_locked_account" + }, + { + "trigger": "scan_data", + "source": "reset_guarded_pin", + "dest": "exit_not_authorized_for_pin_reset", + "unless": "cic_ussd.state_machine.logic.pin_guard.is_others_pin_guardian" + }, + { + "trigger": "scan_data", + "source": "exit_not_authorized_for_pin_reset", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_nine_selected" + } +] \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/helpers.en.yml b/apps/cic-ussd/var/lib/locale/helpers.en.yml index a38704d6..70b344df 100644 --- a/apps/cic-ussd/var/lib/locale/helpers.en.yml +++ b/apps/cic-ussd/var/lib/locale/helpers.en.yml @@ -18,4 +18,19 @@ en: sent: |- Sent to: |- - To \ No newline at end of file + To + guardians_list_header: |- + Walinzi uliowaongeza ni: + no_guardians_list: |- + No guardians set + error: + no_phone_number_provided: |- + No phone number was provided. + no_matching_account: |- + The number provided is not registered. + is_initiator: |- + Phone number cannot be your own. + is_existent_guardian: |- + This phone number is is already added as a guardian. + is_not_existent_guardian: |- + Phone number not set as PIN reset guardian. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/helpers.sw.yml b/apps/cic-ussd/var/lib/locale/helpers.sw.yml index be347285..cace30c9 100644 --- a/apps/cic-ussd/var/lib/locale/helpers.sw.yml +++ b/apps/cic-ussd/var/lib/locale/helpers.sw.yml @@ -18,4 +18,19 @@ sw: sent: |- Ulituma to: |- - Kwa \ No newline at end of file + Kwa + guardians_list_header: |- + Your set guardians are: + no_guardians_list: |- + Hamna walinzi walioongezwa + error: + no_phone_number_provided: |- + Namabari ya simu haijawekwa. + no_matching_account: |- + Nambari uliyoweka haijasajiliwa. + is_initiator: |- + Nambari yafaa kuwa tofauti na yako. + is_existent_guardian: |- + Namabari hii tayari imeongezwa kama mlinzi wa nambari ya siri. + is_not_existent_guardian: |- + Nambari hii haijaongezwa kama mlinzi wa nambari ya siri. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/ussd.en.yml b/apps/cic-ussd/var/lib/locale/ussd.en.yml index 196323a0..40985fed 100644 --- a/apps/cic-ussd/var/lib/locale/ussd.en.yml +++ b/apps/cic-ussd/var/lib/locale/ussd.en.yml @@ -70,7 +70,7 @@ en: 2. Change language 3. Check balance 4. Check statement - 5. Change PIN + 5. PIN options 0. Back metadata_management: |- CON My profile @@ -96,6 +96,13 @@ en: 0. Back retry_pin_entry: |- CON Incorrect PIN entered, please try again. You have %{remaining_attempts} attempts remaining. + 0. Back + pin_management: |- + CON Pin options + 1. Change PIN + 2. Reset PIN + 3. Guard PIN + 0. Back enter_current_pin: first: |- CON Enter current PIN. @@ -108,6 +115,73 @@ en: new_pin_confirmation: |- CON Enter your new four number PIN again 0. Back + reset_guarded_pin: |- + CON Enter phone number you are the guardian to reset their pin + 0. Back + reset_guarded_pin_authorization: + first: |- + CON Enter YOUR pin to confirm %{guarded_account_information}'s reset + 0. Back + retry: |- + %{retry_pin_entry} + exit_pin_reset_initiated_success: |- + CON Success: You have initiated a PIN reset for %{guarded_account_information} + 0. Back + 9. Exit + exit_not_authorized_for_pin_reset: |- + CON Failure: You are not authorized to reset that PIN. You must be a guardian! + 0. Back + 9. Exit + guard_pin: |- + CON Pin guard + 1. View guardians + 2. Add guardian + 3. Remove guardian + 0. Back + guardian_list_pin_authorization: + first: |- + CON Enter your pin to view set guardians + 0. Back + retry: |- + %{retry_pin_entry} + guardian_list: |- + CON %{guardians_list} + 0. Back + 9. Exit + add_guardian: |- + CON Enter phone number to add as pin reset guardian + 0. Back + add_guardian_pin_authorization: + first: |- + CON Enter your pin to add %{guardian_information} as your PIN reset guardian + 0. Back + retry: |- + %{retry_pin_entry} + exit_guardian_addition_success: |- + CON Success: %{guardian_information} can now reset your PIN + 0. Back + 9. Exit + exit_invalid_guardian_addition: |- + CON %{error_exit} + 0. Back + 9. Exit + remove_guardian: |- + CON Enter phone number to revoke guardianship: + 0. Back + remove_guardian_pin_authorization: + first: |- + CON Enter your pin to remove %{guardian_information} as your PIN reset guardian + 0. Back + retry: |- + %{retry_pin_entry} + exit_guardian_removal_success: |- + CON Success: %{guardian_information} PIN reset guardianship is revoked + 0. Back + 9. Exit + exit_invalid_guardian_removal: |- + CON %{error_exit} + 0. Back + 9. Exit transaction_pin_authorization: first: |- CON %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}. diff --git a/apps/cic-ussd/var/lib/locale/ussd.sw.yml b/apps/cic-ussd/var/lib/locale/ussd.sw.yml index 0a5bb8ec..89bf9e71 100644 --- a/apps/cic-ussd/var/lib/locale/ussd.sw.yml +++ b/apps/cic-ussd/var/lib/locale/ussd.sw.yml @@ -69,7 +69,7 @@ sw: 2. Chagua lugha utakayotumia 3. Angalia salio 4. Angalia taarifa ya matumizi - 5. Badilisha nambari ya siri + 5. Mipangilio ya nambari ya siri 0. Nyuma metadata_management: |- CON Wasifu wangu @@ -95,6 +95,13 @@ sw: 0. Nyuma retry_pin_entry: |- CON Nambari uliyoweka si sahihi, jaribu tena. Una majaribio %{remaining_attempts} yaliyobaki. + 0. Nyuma + pin_management: |- + CON Pin options + 1. Badilisha nambari yangu ya siri + 2. Tuma ombili la kubadilisha nambari ya siri + 3. Linda nambari ya siri + 0. Nyuma enter_current_pin: first: |- CON Weka nambari ya siri. @@ -107,6 +114,73 @@ sw: new_pin_confirmation: |- CON Weka nambari yako ya siri tena 0. Nyuma + reset_guarded_pin: |- + CON Weka nambari ya simu ili kutuma ombi la kubalisha nambari ya siri. + 0. Nyuma + reset_guarded_pin_authorization: + first: |- + CON Weka nambari YAKO ya siri ili kudhibitisha ombi la kubadilisha nambari ya siri ya %{guarded_account_information}. + 0. Nyuma + retry: |- + %{retry_pin_entry} + exit_pin_reset_initiated_success: |- + CON Ombi lako la kubadili nambari ya siri ya %{guarded_account_information} limetumwa. + 0. Nyuma + 9. Ondoka + exit_not_authorized_for_pin_reset: |- + CON Huruhusiwi kutuma ombi la kubadilisha nambari ya siri. + 0. Nyuma + 9. Ondoka + guard_pin: |- + CON Linda nambari ya siri + 1. Walinzi wa namabari ya siri + 2. Ongeza mlinzi + 3. Ondoa mlinzi + 0. Nyuma + guardian_list_pin_authorization: + first: |- + CON Weka nambari yako ya siri ili kuona walinzi uliowaongeza + 0. Nyuma + retry: |- + %{retry_pin_entry} + guardian_list: |- + CON %{guardians_list} + 0. Nyuma + 9. Ondoka + add_guardian: |- + CON Weka nambari ya simu ili kuongeza mlinzi + 0. Nyuma + add_guardian_pin_authorization: + first: |- + CON Weka nambari YAKO ya siri ili kumwongeza %{guardian_information} kama mlinzi + 0. Nyuma + retry: |- + %{retry_pin_entry} + exit_guardian_addition_success: |- + CON Ombi lako la kumwongeza: %{guardian_information} kama mlinzi limefanikiwa + 0. Nyuma + 9. Ondoka + exit_invalid_guardian_addition: |- + CON %{error_exit} + 0. Nyuma + 9. Ondoka + remove_guardian: |- + CON Weka nambari ya simu ili kuondoa mlinzi + 0. Nyuma + remove_guardian_pin_authorization: + first: |- + CON Weka nambari YAKO ya siri ili kumwondoa %{guardian_information} kama mlinzi + 0. Nyuma + retry: |- + %{retry_pin_entry} + exit_guardian_removal_success: |- + CON Ombi lako la kumwondoa: %{guardian_information} kama mlinzi limefanikiwa + 0. Nyuma + 9. Ondoka + exit_invalid_guardian_removal: |- + CON %{error_exit} + 0. Nyuma + 9. Ondoka transaction_pin_authorization: first: |- CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.