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:
Philip Wafula 2021-11-29 21:24:38 +00:00
commit cd102807a6
17 changed files with 824 additions and 9 deletions

View File

@ -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),

View File

@ -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.

View File

@ -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"
} }
} }
} }

View File

@ -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:

View File

@ -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()

View File

@ -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'

View 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)

View File

@ -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"
] ]

View 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"
]

View File

@ -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"
}, },
{ {

View 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"
}
]

View 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"
}
]

View 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"
}
]

View File

@ -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.

View File

@ -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.

View File

@ -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}.

View File

@ -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}.