cic-internal-integration/apps/cic-ussd/cic_ussd/state_machine/logic/transaction.py

135 lines
6.1 KiB
Python
Raw Normal View History

2021-02-06 16:13:47 +01:00
# standard imports
import logging
from typing import Tuple
# third party imports
import celery
2021-08-25 12:33:35 +02:00
from phonenumbers.phonenumberutil import NumberParseException
2021-11-29 16:04:50 +01:00
from sqlalchemy.orm.session import Session
2021-02-06 16:13:47 +01:00
# local imports
2021-08-06 18:29:01 +02:00
from cic_ussd.account.balance import get_cached_available_balance
from cic_ussd.account.chain import Chain
2021-11-29 16:04:50 +01:00
from cic_ussd.account.tokens import get_active_token_symbol, get_cached_token_data
2021-08-06 18:29:01 +02:00
from cic_ussd.account.transaction import OutgoingTransaction
2021-06-29 12:49:25 +02:00
from cic_ussd.db.models.account import Account
from cic_ussd.phone_number import process_phone_number, E164Format
2021-08-06 18:29:01 +02:00
from cic_ussd.session.ussd_session import save_session_data
2021-11-29 16:04:50 +01:00
2021-02-06 16:13:47 +01:00
logg = logging.getLogger(__file__)
2021-06-29 12:49:25 +02:00
def is_valid_recipient(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-08-25 12:33:35 +02:00
"""This function checks that a phone number provided as the recipient of a transaction does not match the sending
party's own phone number.
2021-02-06 16:13:47 +01:00
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
2021-08-25 12:33:35 +02:00
:return: A recipient account's validity for a transaction
2021-02-06 16:13:47 +01:00
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-08-25 12:33:35 +02:00
try:
phone_number = process_phone_number(user_input, E164Format.region)
except NumberParseException:
phone_number = None
2021-08-06 18:29:01 +02:00
is_not_initiator = phone_number != account.phone_number
2021-08-25 12:33:35 +02:00
is_present = Account.get_by_phone_number(phone_number, session) is not None
return phone_number is not None and phone_number.startswith('+') and is_present and is_not_initiator
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
being attempted.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A transaction amount's validity
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-02-06 16:13:47 +01:00
try:
return int(user_input) > 0
except ValueError:
return False
2021-06-29 12:49:25 +02:00
def has_sufficient_balance(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
being attempted.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: An account balance's validity
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-11-29 16:04:50 +01:00
identifier = bytes.fromhex(account.blockchain_address)
token_symbol = get_active_token_symbol(account.blockchain_address)
token_data = get_cached_token_data(account.blockchain_address, token_symbol)
decimals = token_data.get('decimals')
return int(user_input) <= get_cached_available_balance(decimals, [identifier, token_symbol.encode('utf-8')])
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, Account, Session]):
2021-02-06 16:13:47 +01:00
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-04-06 19:53:38 +02:00
2021-08-06 18:29:01 +02:00
session_data = ussd_session.get('data') or {}
2021-06-29 12:49:25 +02:00
recipient_phone_number = process_phone_number(phone_number=user_input, region=E164Format.region)
session_data['recipient_phone_number'] = recipient_phone_number
2021-04-06 19:53:38 +02:00
2021-08-06 18:29:01 +02:00
save_session_data('cic-ussd', session, session_data, ussd_session)
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, Account, Session]):
"""
:param state_machine_data:
:type state_machine_data:
:return:
:rtype:
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
recipient_phone_number = process_phone_number(user_input, E164Format.region)
recipient = Account.get_by_phone_number(recipient_phone_number, session)
blockchain_address = recipient.blockchain_address
2021-04-14 11:00:10 +02:00
s_query_person_metadata = celery.signature(
2021-08-06 18:29:01 +02:00
'cic_ussd.tasks.metadata.query_person_metadata', [blockchain_address], queue='cic-ussd')
s_query_person_metadata.apply_async()
2021-06-29 12:49:25 +02:00
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, Account, Session]):
2021-02-06 16:13:47 +01:00
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-04-06 19:53:38 +02:00
2021-08-06 18:29:01 +02:00
session_data = ussd_session.get('data') or {}
2021-04-06 19:53:38 +02:00
session_data['transaction_amount'] = user_input
2021-08-06 18:29:01 +02:00
save_session_data('cic-ussd', session, session_data, ussd_session)
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def process_transaction_request(state_machine_data: Tuple[str, dict, Account, Session]):
2021-02-06 16:13:47 +01:00
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-02-06 16:13:47 +01:00
chain_str = Chain.spec.__str__()
2021-08-06 18:29:01 +02:00
recipient_phone_number = ussd_session.get('data').get('recipient_phone_number')
2021-06-29 12:49:25 +02:00
recipient = Account.get_by_phone_number(phone_number=recipient_phone_number, session=session)
2021-02-06 16:13:47 +01:00
to_address = recipient.blockchain_address
2021-08-06 18:29:01 +02:00
from_address = account.blockchain_address
amount = int(ussd_session.get('data').get('transaction_amount'))
2021-11-29 16:04:50 +01:00
token_symbol = get_active_token_symbol(account.blockchain_address)
token_data = get_cached_token_data(account.blockchain_address, token_symbol)
decimals = token_data.get('decimals')
2021-08-06 18:29:01 +02:00
outgoing_tx_processor = OutgoingTransaction(chain_str=chain_str,
from_address=from_address,
to_address=to_address)
2021-11-29 16:04:50 +01:00
outgoing_tx_processor.transfer(amount=amount, decimals=decimals, token_symbol=token_symbol)