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('preferred_language', sa.String(), nullable=True),
|
||||||
sa.Column('password_hash', sa.String(), nullable=True),
|
sa.Column('password_hash', sa.String(), nullable=True),
|
||||||
sa.Column('failed_pin_attempts', sa.Integer(), nullable=False),
|
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('status', sa.Integer(), nullable=False),
|
||||||
sa.Column('created', sa.DateTime(), nullable=False),
|
sa.Column('created', sa.DateTime(), nullable=False),
|
||||||
sa.Column('updated', sa.DateTime(), nullable=False),
|
sa.Column('updated', sa.DateTime(), nullable=False),
|
||||||
|
@ -4,6 +4,8 @@ import json
|
|||||||
# external imports
|
# external imports
|
||||||
from cic_eth.api import Api
|
from cic_eth.api import Api
|
||||||
from cic_types.condiments import MetadataPointer
|
from cic_types.condiments import MetadataPointer
|
||||||
|
from sqlalchemy import Column, Integer, String
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.account.metadata import get_cached_preferred_language, parse_account_metadata
|
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.base import SessionBase
|
||||||
from cic_ussd.db.models.task_tracker import TaskTracker
|
from cic_ussd.db.models.task_tracker import TaskTracker
|
||||||
from cic_ussd.encoder import check_password_hash, create_password_hash
|
from cic_ussd.encoder import check_password_hash, create_password_hash
|
||||||
from sqlalchemy import Column, Integer, String
|
from cic_ussd.phone_number import Support
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
support_phone = Support.phone_number
|
||||||
|
|
||||||
|
|
||||||
class Account(SessionBase):
|
class Account(SessionBase):
|
||||||
@ -29,12 +32,16 @@ class Account(SessionBase):
|
|||||||
failed_pin_attempts = Column(Integer)
|
failed_pin_attempts = Column(Integer)
|
||||||
status = Column(Integer)
|
status = Column(Integer)
|
||||||
preferred_language = Column(String)
|
preferred_language = Column(String)
|
||||||
|
guardians = Column(String)
|
||||||
|
guardian_quora = Column(Integer)
|
||||||
|
|
||||||
def __init__(self, blockchain_address, phone_number):
|
def __init__(self, blockchain_address, phone_number):
|
||||||
self.blockchain_address = blockchain_address
|
self.blockchain_address = blockchain_address
|
||||||
self.phone_number = phone_number
|
self.phone_number = phone_number
|
||||||
self.password_hash = None
|
self.password_hash = None
|
||||||
self.failed_pin_attempts = 0
|
self.failed_pin_attempts = 0
|
||||||
|
# self.guardians = f'{support_phone}' if support_phone else None
|
||||||
|
self.guardian_quora = 1
|
||||||
self.status = AccountStatus.PENDING.value
|
self.status = AccountStatus.PENDING.value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -45,6 +52,28 @@ class Account(SessionBase):
|
|||||||
self.failed_pin_attempts = 0
|
self.failed_pin_attempts = 0
|
||||||
self.status = AccountStatus.ACTIVE.value
|
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):
|
def create_password(self, password):
|
||||||
"""This method takes a password value and hashes the value before assigning it to the corresponding
|
"""This method takes a password value and hashes the value before assigning it to the corresponding
|
||||||
`hashed_password` attribute in the user record.
|
`hashed_password` attribute in the user record.
|
||||||
|
@ -317,6 +317,102 @@
|
|||||||
"display_key": "ussd.kenya.exit_successful_token_selection",
|
"display_key": "ussd.kenya.exit_successful_token_selection",
|
||||||
"name": "exit_successful_token_selection",
|
"name": "exit_successful_token_selection",
|
||||||
"parent": null
|
"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
|
import phonenumbers
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.db.models.account import Account
|
|
||||||
|
|
||||||
|
|
||||||
class E164Format:
|
class E164Format:
|
||||||
|
@ -121,6 +121,27 @@ class MenuProcessor:
|
|||||||
self.display_key, preferred_language, last_transaction_set=last_transaction_set
|
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:
|
def account_tokens(self) -> str:
|
||||||
cached_token_data_list = get_cached_token_data_list(self.account.blockchain_address)
|
cached_token_data_list = get_cached_token_data_list(self.account.blockchain_address)
|
||||||
token_data_list = parse_token_list(cached_token_data_list)
|
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
|
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):
|
def start_menu(self):
|
||||||
"""
|
"""
|
||||||
:return:
|
:return:
|
||||||
@ -277,6 +312,47 @@ class MenuProcessor:
|
|||||||
sender_information=tx_sender_information
|
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):
|
def exit_insufficient_balance(self):
|
||||||
"""
|
"""
|
||||||
:return:
|
:return:
|
||||||
@ -379,18 +455,41 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio
|
|||||||
return menu_processor.transaction_pin_authorization()
|
return menu_processor.transaction_pin_authorization()
|
||||||
|
|
||||||
if menu_name == 'token_selection_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()
|
return menu_processor.token_selection_pin_authorization()
|
||||||
|
|
||||||
if menu_name == 'exit_insufficient_balance':
|
if menu_name == 'exit_insufficient_balance':
|
||||||
return menu_processor.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':
|
if menu_name == 'exit_successful_transaction':
|
||||||
return menu_processor.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':
|
if menu_name == 'account_balances':
|
||||||
return menu_processor.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:
|
if 'pin_authorization' in menu_name:
|
||||||
return menu_processor.pin_authorization()
|
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'
|
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:
|
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'
|
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",
|
"account_creation_prompt",
|
||||||
"exit_successful_transaction",
|
"exit_successful_transaction",
|
||||||
"exit_insufficient_balance",
|
"exit_insufficient_balance",
|
||||||
|
"exit_invalid_guardian_addition",
|
||||||
|
"exit_invalid_guardian_removal",
|
||||||
"complete"
|
"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",
|
"trigger": "scan_data",
|
||||||
"source": "account_management",
|
"source": "account_management",
|
||||||
"dest": "enter_current_pin",
|
"dest": "pin_management",
|
||||||
"conditions": "cic_ussd.state_machine.logic.menu.menu_five_selected"
|
"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: |-
|
||||||
Sent
|
Sent
|
||||||
to: |-
|
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: |-
|
sent: |-
|
||||||
Ulituma
|
Ulituma
|
||||||
to: |-
|
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
|
2. Change language
|
||||||
3. Check balance
|
3. Check balance
|
||||||
4. Check statement
|
4. Check statement
|
||||||
5. Change PIN
|
5. PIN options
|
||||||
0. Back
|
0. Back
|
||||||
metadata_management: |-
|
metadata_management: |-
|
||||||
CON My profile
|
CON My profile
|
||||||
@ -96,6 +96,13 @@ en:
|
|||||||
0. Back
|
0. Back
|
||||||
retry_pin_entry: |-
|
retry_pin_entry: |-
|
||||||
CON Incorrect PIN entered, please try again. You have %{remaining_attempts} attempts remaining.
|
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:
|
enter_current_pin:
|
||||||
first: |-
|
first: |-
|
||||||
CON Enter current PIN.
|
CON Enter current PIN.
|
||||||
@ -108,6 +115,73 @@ en:
|
|||||||
new_pin_confirmation: |-
|
new_pin_confirmation: |-
|
||||||
CON Enter your new four number PIN again
|
CON Enter your new four number PIN again
|
||||||
0. Back
|
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:
|
transaction_pin_authorization:
|
||||||
first: |-
|
first: |-
|
||||||
CON %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}.
|
CON %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}.
|
||||||
|
@ -69,7 +69,7 @@ sw:
|
|||||||
2. Chagua lugha utakayotumia
|
2. Chagua lugha utakayotumia
|
||||||
3. Angalia salio
|
3. Angalia salio
|
||||||
4. Angalia taarifa ya matumizi
|
4. Angalia taarifa ya matumizi
|
||||||
5. Badilisha nambari ya siri
|
5. Mipangilio ya nambari ya siri
|
||||||
0. Nyuma
|
0. Nyuma
|
||||||
metadata_management: |-
|
metadata_management: |-
|
||||||
CON Wasifu wangu
|
CON Wasifu wangu
|
||||||
@ -95,6 +95,13 @@ sw:
|
|||||||
0. Nyuma
|
0. Nyuma
|
||||||
retry_pin_entry: |-
|
retry_pin_entry: |-
|
||||||
CON Nambari uliyoweka si sahihi, jaribu tena. Una majaribio %{remaining_attempts} yaliyobaki.
|
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:
|
enter_current_pin:
|
||||||
first: |-
|
first: |-
|
||||||
CON Weka nambari ya siri.
|
CON Weka nambari ya siri.
|
||||||
@ -107,6 +114,73 @@ sw:
|
|||||||
new_pin_confirmation: |-
|
new_pin_confirmation: |-
|
||||||
CON Weka nambari yako ya siri tena
|
CON Weka nambari yako ya siri tena
|
||||||
0. Nyuma
|
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:
|
transaction_pin_authorization:
|
||||||
first: |-
|
first: |-
|
||||||
CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
||||||
|
Loading…
Reference in New Issue
Block a user