Merge branch 'philip/social-pin-recovery' into 'master'
Philip/social pin recovery See merge request grassrootseconomics/cic-internal-integration!311
This commit is contained in:
		
						commit
						cd102807a6
					
				@ -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),
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -5,7 +5,6 @@ from typing import Optional
 | 
			
		||||
import phonenumbers
 | 
			
		||||
 | 
			
		||||
# local imports
 | 
			
		||||
from cic_ussd.db.models.account import Account
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class E164Format:
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										200
									
								
								apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
@ -11,5 +11,7 @@
 | 
			
		||||
  "account_creation_prompt",
 | 
			
		||||
  "exit_successful_transaction",
 | 
			
		||||
  "exit_insufficient_balance",
 | 
			
		||||
  "exit_invalid_guardian_addition",
 | 
			
		||||
  "exit_invalid_guardian_removal",
 | 
			
		||||
  "complete"
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										16
									
								
								apps/cic-ussd/states/pin_management_states.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/cic-ussd/states/pin_management_states.json
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
]
 | 
			
		||||
@ -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"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								apps/cic-ussd/transitions/pin_guard_transitions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								apps/cic-ussd/transitions/pin_guard_transitions.json
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										20
									
								
								apps/cic-ussd/transitions/pin_management_transitions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								apps/cic-ussd/transitions/pin_management_transitions.json
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										43
									
								
								apps/cic-ussd/transitions/pin_reset_transitions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								apps/cic-ussd/transitions/pin_reset_transitions.json
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@ -18,4 +18,19 @@ en:
 | 
			
		||||
  sent: |-
 | 
			
		||||
    Sent
 | 
			
		||||
  to: |-
 | 
			
		||||
    To
 | 
			
		||||
    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.
 | 
			
		||||
@ -18,4 +18,19 @@ sw:
 | 
			
		||||
  sent: |-
 | 
			
		||||
    Ulituma
 | 
			
		||||
  to: |-
 | 
			
		||||
    Kwa
 | 
			
		||||
    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.
 | 
			
		||||
@ -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}.
 | 
			
		||||
 | 
			
		||||
@ -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}.
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user