def _make_initial_disbursement(self, initial_disbursement=None, auto_resolve=None): from server.utils.credit_transfer import make_payment_transfer if not initial_disbursement: # if initial_disbursement is still none, then we don't want to create a transfer. return None user_id = get_authorising_user_id() if user_id is not None: sender = User.query.execution_options(show_all=True).get(user_id) else: sender = self.primary_user disbursement = make_payment_transfer( initial_disbursement, token=self.token, send_user=sender, receive_user=self.primary_user, transfer_subtype=TransferSubTypeEnum.DISBURSEMENT, transfer_mode=TransferModeEnum.WEB, is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False, automatically_resolve_complete=auto_resolve) disbursement.is_initial_disbursement = True return disbursement
def new_disbursement(create_transfer_account_user): from server.utils.credit_transfer import make_payment_transfer disbursement = make_payment_transfer( 100, receive_transfer_account=create_transfer_account_user, transfer_subtype=TransferSubTypeEnum.DISBURSEMENT) return disbursement
def transfer_token(sender: User, recipient: User, amount: float, reason_id: Optional[int] = None, transfer_subtype: Optional[TransferSubTypeEnum]=TransferSubTypeEnum.STANDARD): sent_token = default_token(sender) received_token = default_token(recipient) transfer_use = None if reason_id is not None: transfer_use = str(int(reason_id)) transfer = make_payment_transfer(amount, token=sent_token, send_user=sender, receive_user=recipient, transfer_use=transfer_use, is_ghost_transfer=True, require_sender_approved=False, require_recipient_approved=False, transfer_subtype=transfer_subtype, transfer_mode=TransferModeEnum.USSD) exchanged_amount = None if sent_token.id != received_token.id: exchange = Exchange() exchange.exchange_from_amount(user=recipient, from_token=sent_token, to_token=received_token, from_amount=amount, prior_task_uuids=[transfer.blockchain_task_uuid], transfer_mode=TransferModeEnum.USSD) exchanged_amount = exchange.to_transfer.transfer_amount return exchanged_amount
def _make_initial_disbursement(self, initial_disbursement, auto_resolve=False): from server.utils.credit_transfer import make_payment_transfer active_org = getattr(g, 'active_organisation', Organisation.master_organisation()) initial_disbursement = initial_disbursement or active_org.default_disbursement if not initial_disbursement: return None user_id = get_authorising_user_id() if user_id is not None: sender = User.query.execution_options(show_all=True).get(user_id) else: sender = self.primary_user disbursement = make_payment_transfer( initial_disbursement, token=self.token, send_user=sender, receive_user=self.primary_user, transfer_subtype=TransferSubTypeEnum.DISBURSEMENT, is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False, automatically_resolve_complete=auto_resolve) return disbursement
def test_get_directory_listing_users(test_client, init_database, dl_processor): token1 = Token.query.filter_by(symbol="SM1").first() category = dl_processor.selected_business_category user = dl_processor.recipient def create_user(cat, token, is_market_enabled): _user = UserFactory(phone=phone(), business_usage_id=cat.id, is_market_enabled=is_market_enabled) create_transfer_account_for_user(_user, token, 200) return _user market_disabled_user = create_user(category, token1, False) # different token user token2 = Token.query.filter_by(symbol="SM2").first() create_user(category, token2, True) # different category user category2 = TransferUsage(name='Test', icon='message') different_business_category_user = create_user(category2, token1, True) shown_user = create_user(category, token1, True) # user did more transactions more_transactions_user = create_user(category, token1, True) make_payment_transfer(100, token=token1, send_user=different_business_category_user, receive_user=more_transactions_user, transfer_use=str(int(category.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) make_payment_transfer(100, token=token1, send_user=different_business_category_user, receive_user=more_transactions_user, transfer_use=str(int(category.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) users = dl_processor.get_directory_listing_users() assert len(users) == 2 assert users[0].id == more_transactions_user.id assert users[1].id == shown_user.id
def test_make_payment_transfer_error( test_client, init_database, create_transfer_account_user, create_user_with_existing_transfer_account, transfer_amount, require_sender_approved, require_recipient_approved, require_sufficient_balance, error): send_user = create_transfer_account_user if require_sender_approved: send_user.transfer_account.is_approved = False receive_user = create_user_with_existing_transfer_account if require_recipient_approved: receive_user.transfer_account.is_approved = False with pytest.raises(error): CreditTransferUtils.make_payment_transfer( transfer_amount=transfer_amount, token=send_user.transfer_account.token, send_user=send_user, receive_user=receive_user, require_sender_approved=require_sender_approved, require_recipient_approved=require_recipient_approved, require_sufficient_balance=require_sufficient_balance, )
def test_golden_path_send_token(mocker, test_client, init_database, initialised_blockchain_network, init_seed): token = Token.query.filter_by(symbol="SM1").first() org = OrganisationFactory(country_code=config.DEFAULT_COUNTRY) sender = UserFactory(preferred_language="en", phone='+6110011223344', first_name="Bob", last_name="Foo", pin_hash=User.salt_hash_secret('0000'), default_organisation=org) create_transfer_account_for_user(sender, token, 4220) recipient = UserFactory(preferred_language="sw", phone='+6114433221100', first_name="Joe", last_name="Bar", default_organisation=org) create_transfer_account_for_user(recipient, token, 1980) usages = TransferUsage.query.filter_by(default=True).order_by( TransferUsage.priority).all() top_priority = usages[0] # Take the last to ensure that we're not going to simply reinforce the existing order usage = usages[-1] # do two of these transfers to ensure last is is the first shown make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) def mock_send_message(phone, message): messages.append({'phone': phone, 'message': message}) mocker.patch(f'server.utils.phone._send_twilio_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_messagebird_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_at_message.submit', mock_send_message) assert get_session() is None resp = req("", test_client, sender.phone) assert get_session() is not None assert "CON Welcome" in resp resp = req("1", test_client, sender.phone) assert "CON Enter Phone" in resp resp = req(recipient.phone, test_client, sender.phone) assert "CON Enter Amount" in resp resp = req("12.5", test_client, sender.phone) assert f"{recipient.user_details()} will receive 12.5 {token.symbol} from {sender.user_details()}" in resp resp = req("0000", test_client, sender.phone) assert "END Your request has been sent." in resp assert default_transfer_account(sender).balance == (4220 - 100 - 100 - 1250) assert default_transfer_account(recipient).balance == (1980 + 100 + 100 + 1250) assert len(messages) == 2 sent_message = messages[0] assert sent_message['phone'] == sender.phone assert f"sent 12 SM1 to {recipient.first_name}" in sent_message['message'] received_message = messages[1] assert received_message['phone'] == recipient.phone assert f"Umepokea 12 SM1 kutoka kwa {sender.first_name}" in received_message[ 'message']
def post(self): post_data = request.get_json() uuid = post_data.get('uuid') created = post_data.get('created') transfer_use = post_data.get('transfer_use') try: use_ids = transfer_use.split(',') # passed as '3,4' etc. except AttributeError: use_ids = transfer_use transfer_mode = post_data.get('transfer_mode') transfer_amount = round(Decimal(post_data.get('transfer_amount', 0)), 6) transfer_random_key = post_data.get('transfer_random_key') pin = post_data.get('pin') nfc_serial_number = post_data.get('nfc_id') user_id = post_data.get('user_id') public_identifier = post_data.get('public_identifier') transfer_account_id = post_data.get('transfer_account_id') qr_data = post_data.get('qr_data') if qr_data is not None: qr_data = str(qr_data).strip(" ").strip("\t") my_transfer_account_id = post_data.get("my_transfer_account_id") is_sending = post_data.get('is_sending', False) transfer_card = None my_transfer_account = None authorised = False if transfer_account_id: counterparty_transfer_account = TransferAccount.query.get(transfer_account_id) else: counterparty_transfer_account = None if uuid: existing_transfer = CreditTransfer.query.filter_by(uuid=uuid).first() if existing_transfer: # We return a 201 here so that the client removes the uuid from the cache response_object = { 'message': 'Transfer already in cache', 'data': { 'credit_transfer': me_credit_transfer_schema.dump(existing_transfer).data, } } return make_response(jsonify(response_object)), 201 if qr_data: split_qr_data = qr_data.split('-') transfer_amount = int(split_qr_data[0]) transfer_account_id = int(split_qr_data[1]) user_id = int(split_qr_data[2]) qr_hash = split_qr_data[3] counterparty_user = User.query.get(user_id) if not counterparty_user: response_object = { 'message': 'No such user for ID {}'.format(user_id), 'feedback': True, } return make_response(jsonify(response_object)), 404 counterparty_transfer_account = TransferAccount.query.get(transfer_account_id) if not counterparty_transfer_account: response_object = { 'message': 'No such Transfer Account for ID {}'.format(transfer_account_id), 'feedback': True, } return make_response(jsonify(response_object)), 404 if counterparty_transfer_account not in counterparty_user.transfer_accounts: if not counterparty_transfer_account: response_object = { 'message': 'User {} not authorised for Transfer Account {}.' .format(user_id, transfer_account_id), 'feedback': True, } return make_response(jsonify(response_object)), 401 my_transfer_account = find_transfer_accounts_with_matching_token( g.user, counterparty_transfer_account.token ) user_secret = counterparty_user.secret if not check_for_any_valid_hash(transfer_amount, transfer_account_id, user_secret, qr_hash): response_object = { 'message': 'Invalid QR Code', 'feedback': True, } return make_response(jsonify(response_object)), 401 authorised = True elif nfc_serial_number: # We treat NFC serials differently because they're automatically authorised under the current version transfer_card = TransferCard.query.filter_by(nfc_serial_number=nfc_serial_number).first() if transfer_card: counterparty_user = transfer_card.user counterparty_transfer_account = transfer_card.transfer_account if not transfer_card or not counterparty_user or not counterparty_transfer_account: response_object = { 'message': 'Card not found', 'feedback': True } return make_response(jsonify(response_object)), 404 authorised = True else: try: counterparty_user, _ = find_user_with_transfer_account_from_identifiers( user_id, public_identifier, transfer_account_id) except (NoTransferAccountError, UserNotFoundError) as e: if not Web3.isAddress(public_identifier.strip('ethereum:')) or not is_sending: response_object = { 'message': str(e), 'feedback': True } return make_response(jsonify(response_object)), 400 my_transfer_account = TransferAccount.query.get(my_transfer_account_id) if not my_transfer_account: response_object = { 'message': 'Transfer Account not found for my_transfer_account_id {}'.format( my_transfer_account_id) } return make_response(jsonify(response_object)), 400 #We're sending directly to a blockchain address return handle_transfer_to_blockchain_address(transfer_amount, my_transfer_account, public_identifier.strip('ethereum:'), transfer_use, transfer_mode=TransferModeEnum.EXTERNAL, uuid=uuid) if not counterparty_user: response_object = { 'message': 'User not found', 'feedback': True } return make_response(jsonify(response_object)), 400 authorised = counterparty_user.verify_password(str(pin)) if is_sending: authorised = True if not authorised: response_object = { 'message': 'Not Authorised', 'feedback': True, } return make_response(jsonify(response_object)), 401 if not my_transfer_account: if not my_transfer_account_id: response_object = { 'message': 'You must provide your Transfer Account ID', } return make_response(jsonify(response_object)), 400 my_transfer_account = TransferAccount.query.get(my_transfer_account_id) if not my_transfer_account: response_object = { 'message': 'Transfer Account not found for my_transfer_account_id {}'.format(my_transfer_account_id) } return make_response(jsonify(response_object)), 400 if my_transfer_account not in g.user.transfer_accounts: response_object = { 'message': 'Transfer account provided does not belong to user', } return make_response(jsonify(response_object)), 401 if is_sending: send_user = g.user send_transfer_account = my_transfer_account receive_user = counterparty_user receive_transfer_account = counterparty_transfer_account else: if counterparty_transfer_account is None: response_object = { 'message': 'Counterparty Transfer Account not specified' } return make_response(jsonify(response_object)), 400 send_user = counterparty_user send_transfer_account = counterparty_transfer_account receive_user = g.user receive_transfer_account = my_transfer_account if transfer_amount == 0 or transfer_amount > send_transfer_account.balance: db.session.commit() response_object = { 'message': 'Insufficient funds', 'feedback': True, } return make_response(jsonify(response_object)), 400 try: transfer = make_payment_transfer(transfer_amount=transfer_amount, send_user=send_user, send_transfer_account=send_transfer_account, receive_user=receive_user, receive_transfer_account=receive_transfer_account, transfer_use=transfer_use, transfer_mode=transfer_mode, uuid=uuid, transfer_card=transfer_card) except AccountNotApprovedError as e: db.session.commit() if e.is_sender is True: response_object = { 'message': "Sender is not approved", 'feedback': True, } return make_response(jsonify(response_object)), 400 elif e.is_sender is False: response_object = { 'message': "Recipient is not approved", 'feedback': True, } return make_response(jsonify(response_object)), 400 else: response_object = { 'message': "Account is not approved", 'feedback': True, } return make_response(jsonify(response_object)), 400 except InsufficientBalanceError as e: db.session.commit() response_object = { 'message': "Insufficient balance", 'feedback': True, } return make_response(jsonify(response_object)), 400 if created: try: transfer.created = datetime.datetime.strptime(created, "%Y-%m-%dT%H:%M:%S.%fz") except ValueError as e: pass if is_sending: push_user_transfer_confirmation(receive_user, transfer_random_key) db.session.commit() response_object = { 'message': 'Payment Successful', 'first_name': counterparty_user.first_name, 'last_name': counterparty_user.last_name, 'feedback': True, 'data': { 'credit_transfer': me_credit_transfer_schema.dump(transfer).data } } return make_response(jsonify(response_object)), 201
def post(self, credit_transfer_id): auto_resolve = AccessControl.has_sufficient_tier( g.user.roles, 'ADMIN', 'superadmin') post_data = request.get_json() uuid = post_data.get('uuid') queue = 'low-priority' transfer_type = post_data.get('transfer_type') transfer_amount = abs( round(Decimal(post_data.get('transfer_amount') or 0), 6)) token_id = post_data.get('token_id') target_balance = post_data.get('target_balance') transfer_use = post_data.get('transfer_use') try: use_ids = transfer_use.split(',') # passed as '3,4' etc. except AttributeError: use_ids = transfer_use sender_user_id = post_data.get('sender_user_id') recipient_user_id = post_data.get('recipient_user_id') # These could be phone numbers, email, nfc serial numbers, card numbers etc sender_public_identifier = post_data.get('sender_public_identifier') recipient_public_identifier = post_data.get( 'recipient_public_identifier') sender_transfer_account_id = post_data.get( 'sender_transfer_account_id') recipient_transfer_account_id = post_data.get( 'recipient_transfer_account_id') recipient_transfer_accounts_ids = post_data.get( 'recipient_transfer_accounts_ids') # invert_recipient_list will send to everyone _except_ for the users in recipient_transfer_accounts_ids invert_recipient_list = post_data.get('invert_recipient_list', False) invert_recipient_list = False if invert_recipient_list == False else True credit_transfers = [] response_list = [] is_bulk = False transfer_card = None if uuid: existing_transfer = CreditTransfer.query.filter_by( uuid=uuid).first() # We return a 201 here so that the client removes the uuid from the cache response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer_schema.dump(existing_transfer).data, } } return make_response(jsonify(response_object)), 201 if transfer_amount <= 0 and not target_balance and not ( transfer_amount == 0 and transfer_type == "BALANCE"): response_object = { 'message': 'Transfer amount must be positive', } return make_response(jsonify(response_object)), 400 if recipient_transfer_accounts_ids: is_bulk = True batch_uuid = str(uuid4()) if transfer_type not in ["DISBURSEMENT", "BALANCE"]: response_object = { 'message': 'Bulk transfer must be either disbursement or balance', } return make_response(jsonify(response_object)), 400 transfer_user_list = [] individual_sender_user = None if invert_recipient_list: all_accounts_query = TransferAccount.query.filter( TransferAccount.is_ghost != True).filter_by( organisation_id=g.active_organisation.id) all_user_accounts_query = (all_accounts_query.filter( TransferAccount.account_type == TransferAccountType.USER)) all_accounts_except_selected_query = all_user_accounts_query.filter( not_( TransferAccount.id.in_( recipient_transfer_accounts_ids))) for individual_recipient_user in all_accounts_except_selected_query.all( ): transfer_user_list.append( (individual_sender_user, individual_recipient_user.primary_user, None)) else: for transfer_account_id in recipient_transfer_accounts_ids: try: individual_recipient_user, transfer_card = find_user_with_transfer_account_from_identifiers( None, None, transfer_account_id) transfer_user_list.append( (individual_sender_user, individual_recipient_user, transfer_card)) except (NoTransferAccountError, UserNotFoundError) as e: response_list.append({ 'status': 400, 'message': str(e) }) else: batch_uuid = None try: individual_sender_user, transfer_card = find_user_with_transfer_account_from_identifiers( sender_user_id, sender_public_identifier, sender_transfer_account_id) individual_recipient_user, _ = find_user_with_transfer_account_from_identifiers( recipient_user_id, recipient_public_identifier, recipient_transfer_account_id) transfer_user_list = [ (individual_sender_user, individual_recipient_user, transfer_card) ] except Exception as e: response_object = { 'message': str(e), } return make_response(jsonify(response_object)), 400 if token_id: token = Token.query.get(token_id) if not token: response_object = {'message': 'Token not found'} return make_response(jsonify(response_object)), 404 else: active_organisation = g.active_organisation if active_organisation is None: response_object = {'message': 'Must provide token_id'} return make_response(jsonify(response_object)), 400 else: token = active_organisation.token for sender_user, recipient_user, transfer_card in transfer_user_list: try: if transfer_type == 'PAYMENT': transfer = make_payment_transfer( transfer_amount, token=token, send_user=sender_user, receive_user=recipient_user, transfer_use=transfer_use, transfer_mode=TransferModeEnum.WEB, uuid=uuid, automatically_resolve_complete=False, queue=queue, batch_uuid=batch_uuid, transfer_card=transfer_card) elif transfer_type == 'RECLAMATION': transfer = make_payment_transfer( transfer_amount, token=token, send_user=sender_user, uuid=uuid, transfer_subtype=TransferSubTypeEnum.RECLAMATION, transfer_mode=TransferModeEnum.WEB, require_recipient_approved=False, automatically_resolve_complete=False, queue=queue, batch_uuid=batch_uuid) elif transfer_type == 'DISBURSEMENT': transfer = make_payment_transfer( transfer_amount, token=token, send_user=g.user, receive_user=recipient_user, uuid=uuid, transfer_subtype=TransferSubTypeEnum.DISBURSEMENT, transfer_mode=TransferModeEnum.WEB, automatically_resolve_complete=False, queue=queue, batch_uuid=batch_uuid) elif transfer_type == 'BALANCE': transfer = make_target_balance_transfer( target_balance, recipient_user, uuid=uuid, automatically_resolve_complete=False, transfer_mode=TransferModeEnum.WEB, queue=queue, ) if auto_resolve: transfer.add_approver_and_resolve_as_completed() except (InsufficientBalanceError, AccountNotApprovedError, InvalidTargetBalanceError, BlockchainError, Exception) as e: if is_bulk: response_list.append({'status': 400, 'message': str(e)}) else: db.session.commit() response_object = {'message': str(e)} return make_response(jsonify(response_object)), 400 else: message = 'Transfer Successful' if auto_resolve else 'Transfer Pending. Must be approved.' if is_bulk: credit_transfers.append(transfer) response_list.append({'status': 201, 'message': message}) else: db.session.flush() credit_transfer = credit_transfer_schema.dump( transfer).data response_object = { 'message': message, 'is_create': True, 'data': { 'credit_transfer': credit_transfer, } } return make_response(jsonify(response_object)), 201 db.session.flush() message = 'Bulk Transfer Creation Successful' if auto_resolve else 'Bulk Transfer Pending. Must be approved.' response_object = { 'message': message, 'bulk_responses': response_list, 'data': { 'credit_transfers': credit_transfers_schema.dump(credit_transfers).data } } return make_response(jsonify(response_object)), 201
def post(self, credit_transfer_id): auto_resolve = AccessControl.has_sufficient_tier( g.user.roles, 'ADMIN', 'superadmin') post_data = request.get_json() uuid = post_data.get('uuid') transfer_type = post_data.get('transfer_type') transfer_amount = abs( round(float(post_data.get('transfer_amount') or 0), 6)) token_id = post_data.get('token_id') target_balance = post_data.get('target_balance') transfer_use = post_data.get('transfer_use') sender_user_id = post_data.get('sender_user_id') recipient_user_id = post_data.get('recipient_user_id') # These could be phone numbers, email, nfc serial numbers, card numbers etc sender_public_identifier = post_data.get('sender_public_identifier') recipient_public_identifier = post_data.get( 'recipient_public_identifier') sender_transfer_account_id = post_data.get( 'sender_transfer_account_id') recipient_transfer_account_id = post_data.get( 'recipient_transfer_account_id') recipient_transfer_accounts_ids = post_data.get( 'recipient_transfer_accounts_ids') credit_transfers = [] response_list = [] is_bulk = False if uuid: existing_transfer = CreditTransfer.query.filter_by( uuid=uuid).first() # We return a 201 here so that the client removes the uuid from the cache response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer_schema.dump(existing_transfer).data, } } return make_response(jsonify(response_object)), 201 if transfer_amount <= 0 and not target_balance: response_object = { 'message': 'Transfer amount must be positive', } return make_response(jsonify(response_object)), 400 if recipient_transfer_accounts_ids: is_bulk = True if transfer_type not in ["DISBURSEMENT", "BALANCE"]: response_object = { 'message': 'Bulk transfer must be either disbursement or balance', } return make_response(jsonify(response_object)), 400 transfer_user_list = [] for transfer_account_id in recipient_transfer_accounts_ids: try: individual_sender_user = None individual_recipient_user = find_user_with_transfer_account_from_identifiers( None, None, transfer_account_id) transfer_user_list.append( (individual_sender_user, individual_recipient_user)) except (NoTransferAccountError, UserNotFoundError) as e: response_list.append({'status': 400, 'message': str(e)}) else: try: individual_sender_user = find_user_with_transfer_account_from_identifiers( sender_user_id, sender_public_identifier, sender_transfer_account_id) individual_recipient_user = find_user_with_transfer_account_from_identifiers( recipient_user_id, recipient_public_identifier, recipient_transfer_account_id) transfer_user_list = [(individual_sender_user, individual_recipient_user)] except Exception as e: response_object = { 'message': str(e), } return make_response(jsonify(response_object)), 400 if token_id: token = Token.query.get(token_id) if not token: response_object = {'message': 'Token not found'} return make_response(jsonify(response_object)), 404 else: active_organisation = g.active_organisation if active_organisation is None: response_object = {'message': 'Must provide token_id'} return make_response(jsonify(response_object)), 400 else: token = active_organisation.token for sender_user, recipient_user in transfer_user_list: try: if transfer_type == 'PAYMENT': transfer = make_payment_transfer( transfer_amount, token=token, send_user=sender_user, receive_user=recipient_user, transfer_use=transfer_use, uuid=uuid, automatically_resolve_complete=auto_resolve) elif transfer_type == 'RECLAMATION': transfer = make_payment_transfer( transfer_amount, token=token, send_user=sender_user, uuid=uuid, transfer_subtype=TransferSubTypeEnum.RECLAMATION, require_recipient_approved=False, automatically_resolve_complete=auto_resolve) elif transfer_type == 'DISBURSEMENT': transfer = make_payment_transfer( transfer_amount, token=token, send_user=g.user, receive_user=recipient_user, uuid=uuid, transfer_subtype=TransferSubTypeEnum.DISBURSEMENT, automatically_resolve_complete=auto_resolve) elif transfer_type == 'BALANCE': transfer = make_target_balance_transfer( target_balance, recipient_user, uuid=uuid, automatically_resolve_complete=auto_resolve) except (InsufficientBalanceError, AccountNotApprovedError, InvalidTargetBalanceError, BlockchainError, Exception) as e: if is_bulk: response_list.append({'status': 400, 'message': str(e)}) else: db.session.commit() response_object = {'message': str(e)} return make_response(jsonify(response_object)), 400 else: message = 'Transfer Successful' if auto_resolve else 'Transfer Pending. Must be approved.' if is_bulk: credit_transfers.append(transfer) response_list.append({'status': 201, 'message': message}) else: db.session.flush() credit_transfer = credit_transfer_schema.dump( transfer).data response_object = { 'message': message, 'is_create': True, 'data': { 'credit_transfer': credit_transfer, } } return make_response(jsonify(response_object)), 201 db.session.flush() message = 'Bulk Transfer Creation Successful' if auto_resolve else 'Bulk Transfer Pending. Must be approved.' response_object = { 'message': message, 'bulk_responses': response_list, 'data': { 'credit_transfers': credit_transfers_schema.dump(credit_transfers).data } } return make_response(jsonify(response_object)), 201
def generate_timeseries_metrics(create_organisation): # Generates metrics over timeline # User1 and User2 made today user1 = create_transfer_account_user(first_name='Ricky', phone="+19025551234", organisation=create_organisation, roles=[('BENEFICIARY', 'beneficiary') ]) user1.created = zero_time user1.default_transfer_account.is_approved = True user1.default_transfer_account._make_initial_disbursement(100, True) user1._location = 'Sunnyvale' attribute_dict = {'custom_attributes': {}} attribute_dict['custom_attributes']['colour'] = 'red' attribute_dict['custom_attributes']['chips'] = 'Dressed All Over' set_custom_attributes(attribute_dict, user1) user1.lat = 44.675447 user1.lng = -63.594995 user2 = create_transfer_account_user(first_name='Bubbles', phone="+19025551235", organisation=create_organisation) user2.created = zero_time user2.default_transfer_account.is_approved = True user2.default_transfer_account._make_initial_disbursement(200, True) user2._location = 'Sunnyvale' attribute_dict = {'custom_attributes': {}} attribute_dict['custom_attributes']['colour'] = 'red' attribute_dict['custom_attributes']['chips'] = 'Chicken' user2.lat = 44.675447 user2.lng = -63.594995 set_custom_attributes(attribute_dict, user2) # user3 made yesterday user3 = create_transfer_account_user(first_name='Julian', phone="+19025531230", organisation=create_organisation) user3.default_transfer_account.is_approved = True disburse = user3.default_transfer_account._make_initial_disbursement( 210, True) user3.created = zero_time user3.created = user3.created - timedelta(days=1) + timedelta(hours=6) disburse.created = user3.created - timedelta(days=1) + timedelta(hours=3) user3._location = 'Dartmouth' attribute_dict = {'custom_attributes': {}} attribute_dict['custom_attributes']['colour'] = 'blue' attribute_dict['custom_attributes']['chips'] = 'Barbecue' set_custom_attributes(attribute_dict, user3) user3.lat = 44.668055 user3.lng = -63.580829 # user4 made 4 days ago user4 = create_transfer_account_user(first_name='Randy', phone="+19025511230", organisation=create_organisation) user4.created = zero_time user4.default_transfer_account.is_approved = True disburse = user4.default_transfer_account._make_initial_disbursement( 201, True) user4.created = user4.created - timedelta(days=4) + timedelta(hours=23) disburse.created = user4.created - timedelta(days=4) + timedelta(hours=1) user4._location = 'Lower Sackville' attribute_dict = {'custom_attributes': {}} attribute_dict['custom_attributes']['colour'] = 'blue' attribute_dict['custom_attributes']['chips'] = 'Barbecue' set_custom_attributes(attribute_dict, user4) user4.lat = 44.770061 user4.lng = -63.692723 # user5/user6 made 10 days ago user5 = create_transfer_account_user(first_name='Cory', phone="+19011531230", organisation=create_organisation) user5.created = zero_time user5.default_transfer_account.is_approved = True disburse = user5.default_transfer_account._make_initial_disbursement( 202, True) user5.created = user5.created - timedelta(days=10) + timedelta(hours=2) disburse.created = user5.created - timedelta(days=10) + timedelta(hours=5) user5._location = 'Truro' attribute_dict = {'custom_attributes': {}} attribute_dict['custom_attributes']['colour'] = 'green' attribute_dict['custom_attributes']['chips'] = 'Dressed All Over' set_custom_attributes(attribute_dict, user5) user5.lat = 45.368075 user5.lng = -63.256207 user6 = create_transfer_account_user(first_name='Trevor', phone="+19025111230", organisation=create_organisation) user6.created = zero_time user6.default_transfer_account.is_approved = True disburse = user6.default_transfer_account._make_initial_disbursement( 204, True) attribute_dict = {'custom_attributes': {}} attribute_dict['custom_attributes']['colour'] = 'red' attribute_dict['custom_attributes']['chips'] = 'Jalapeno' set_custom_attributes(attribute_dict, user6) user6.lat = 44.368363 user6.lng = -64.526330 db.session.commit() tu1 = TransferUsage.find_or_create("Pepperoni") tu2 = TransferUsage.find_or_create("Jalepeno Chips") tu3 = TransferUsage.find_or_create("Shopping Carts") tu1.created = zero_time - timedelta(days=15) + timedelta(hours=22) tu2.created = zero_time - timedelta(days=15) + timedelta(hours=2) tu3.created = zero_time - timedelta(days=15) + timedelta(hours=1) p1 = make_payment_transfer( 100, create_organisation.token, send_user=user1, send_transfer_account=user1.default_transfer_account, receive_user=user2, receive_transfer_account=user2.default_transfer_account, transfer_use=str(int(tu1.id))) p1.created = zero_time + timedelta(hours=3) p2 = make_payment_transfer( 25, create_organisation.token, send_user=user3, send_transfer_account=user3.default_transfer_account, receive_user=user4, receive_transfer_account=user4.default_transfer_account, transfer_use=str(int(tu1.id))) p2.created = zero_time p2.created = p2.created - timedelta(days=1) + timedelta(hours=7) p3 = make_payment_transfer( 5, create_organisation.token, send_user=user4, send_transfer_account=user4.default_transfer_account, receive_user=user2, receive_transfer_account=user2.default_transfer_account, transfer_use=str(int(tu2.id))) p3.created = zero_time p3.created = p3.created - timedelta(days=1) + timedelta(hours=22) p4 = make_payment_transfer( 20, create_organisation.token, send_user=user5, send_transfer_account=user5.default_transfer_account, receive_user=user6, receive_transfer_account=user6.default_transfer_account, transfer_use=str(int(tu3.id))) p4.created = zero_time p4.created = p4.created - timedelta(days=4) + timedelta(hours=1) p5 = make_payment_transfer( 20, create_organisation.token, send_user=user6, send_transfer_account=user6.default_transfer_account, receive_user=user5, receive_transfer_account=user5.default_transfer_account, transfer_use=str(int(tu2.id))) p5.created = zero_time p5.created = p5.created - timedelta(days=6) + timedelta(hours=23) db.session.commit()
def test_golden_path_send_token(mocker, test_client, init_database, initialised_blockchain_network, init_seed): token = Token.query.filter_by(symbol="SM1").first() org = OrganisationFactory(country_code='KE') sender = UserFactory(preferred_language="en", phone=make_kenyan_phone(phone()), first_name="Bob", last_name="Foo", pin_hash=User.salt_hash_secret('0000'), default_organisation=org) create_transfer_account_for_user(sender, token, 4220) recipient = UserFactory(preferred_language="sw", phone=make_kenyan_phone(phone()), first_name="Joe", last_name="Bar", default_organisation=org) create_transfer_account_for_user(recipient, token, 1980) messages = [] session_id = 'ATUid_05af06225e6163ec2dc9dc9cf8bc97aa' usages = TransferUsage.query.filter_by(default=True).order_by( TransferUsage.priority).all() top_priority = usages[0] # Take the last to ensure that we're not going to simply reinforce the existing order usage = usages[-1] # do two of these transfers to ensure last is is the first shown make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) def mock_send_message(phone, message): messages.append({'phone': phone, 'message': message}) mocker.patch(f'server.utils.phone._send_twilio_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_messagebird_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_at_message.submit', mock_send_message) def req(text): response = test_client.post( f'/api/v1/ussd/kenya?username={config.EXTERNAL_AUTH_USERNAME}&password={config.EXTERNAL_AUTH_PASSWORD}', headers=dict(Accept='application/json'), json={ 'sessionId': session_id, 'phoneNumber': sender.phone, 'text': text, 'serviceCode': '*384*23216#' }) assert response.status_code == 200 return response.data.decode("utf-8") def get_session(): return UssdSession.query.filter_by(session_id=session_id).first() assert get_session() is None resp = req("") assert get_session() is not None assert "CON Welcome" in resp resp = req("1") assert "CON Enter Phone" in resp resp = req(recipient.phone) assert "CON Enter Amount" in resp resp = req("12.5") assert "CON Transfer Reason" in resp assert f"1. {top_priority.translations['en']}" in resp assert "9." in resp resp = req("9") assert "CON Please specify" in resp assert "10. Show previous" in resp assert "9." not in resp resp = req("10") resp = req("4") assert "CON Please enter your PIN" in resp resp = req("0000") assert "CON Send 12.5 SM1" in resp # went to second page, should not be the first assert f"for {top_priority.translations['en']}" not in resp resp = req("1") assert "END Your request has been sent." in resp assert default_transfer_account(sender).balance == (4220 - 100 - 100 - 1250) assert default_transfer_account(recipient).balance == (1980 + 100 + 100 + 1250) assert len(messages) == 3 sent_message = messages[1] assert sent_message['phone'] == sender.phone assert f"sent a payment of 12.50 SM1 to {recipient.first_name}" in sent_message[ 'message'] received_message = messages[2] assert received_message['phone'] == recipient.phone assert f"Umepokea 12.50 SM1 kutoka kwa {sender.first_name}" in received_message[ 'message']
def make_transfers(disbursement_id, auto_resolve=False): send_transfer_account = g.user.default_organisation.queried_org_level_transfer_account from server.models.user import User from server.models.transfer_account import TransferAccount from server.models.disbursement import Disbursement disbursement = db.session.query(Disbursement).filter( Disbursement.id == disbursement_id).first() for idx, ta in enumerate(disbursement.transfer_accounts): try: user = ta.primary_user if disbursement.transfer_type == 'DISBURSEMENT': transfer = make_payment_transfer( disbursement.disbursement_amount, send_user=g.user, receive_user=db.session.query(User).filter( User.id == user.id).first(), send_transfer_account=send_transfer_account, receive_transfer_account=db.session.query(TransferAccount). filter(TransferAccount.id == ta.id).first(), transfer_subtype=TransferSubTypeEnum.DISBURSEMENT, transfer_mode=TransferModeEnum.WEB, automatically_resolve_complete=False, ) if disbursement.transfer_type == 'RECLAMATION': transfer = make_payment_transfer( disbursement.disbursement_amount, send_user=db.session.query(User).filter( User.id == user.id).first(), send_transfer_account=db.session.query(TransferAccount). filter(TransferAccount.id == ta.id).first(), transfer_subtype=TransferSubTypeEnum.RECLAMATION, transfer_mode=TransferModeEnum.WEB, require_recipient_approved=False, automatically_resolve_complete=False, ) if disbursement.transfer_type == 'BALANCE': transfer = make_target_balance_transfer( disbursement.disbursement_amount, db.session.query(User).filter(User.id == user.id).first(), automatically_resolve_complete=False, transfer_mode=TransferModeEnum.WEB, ) disbursement.credit_transfers.append(transfer) if auto_resolve and disbursement.state == 'APPROVED': transfer.approvers = disbursement.approvers transfer.add_approver_and_resolve_as_completed() except Exception as e: disbursement.errors = disbursement.errors + [ str(ta) + ': ' + str(e) ] db.session.commit() percent_complete = ( (idx + 1) / len(disbursement.transfer_accounts)) * 100 yield { 'message': 'success' if percent_complete == 100 else 'pending', 'percent_complete': math.floor(percent_complete), 'data': { 'credit_transfers': credit_transfers_schema.dump( disbursement.credit_transfers).data } } clear_metrics_cache() rebuild_metrics_cache()
def generate_timeseries_metrics(create_organisation): # Generates metrics over timeline # User1 and User2 made today user1 = create_transfer_account_user(first_name='Ricky', phone="+19025551234", organisation=create_organisation, roles=[('BENEFICIARY', 'beneficiary') ]) user1.default_transfer_account.is_approved = True user1.default_transfer_account._make_initial_disbursement(100, True) user1._location = 'Sunnyvale' user2 = create_transfer_account_user(first_name='Bubbles', phone="+19025551235", organisation=create_organisation) user2.default_transfer_account.is_approved = True user2.default_transfer_account._make_initial_disbursement(200, True) user2._location = 'Sunnyvale' # user3 made yesterday user3 = create_transfer_account_user(first_name='Julian', phone="+19025531230", organisation=create_organisation) user3.default_transfer_account.is_approved = True disburse = user3.default_transfer_account._make_initial_disbursement( 210, True) user3.created = user3.created - timedelta(days=1) disburse.created = user3.created - timedelta(days=1) user3._location = 'Dartmouth' # user4 made 4 days ago user4 = create_transfer_account_user(first_name='Randy', phone="+19025511230", organisation=create_organisation) user4.default_transfer_account.is_approved = True disburse = user4.default_transfer_account._make_initial_disbursement( 201, True) user4.created = user4.created - timedelta(days=4) disburse.created = user4.created - timedelta(days=4) user4._location = 'Lower Sackville' # user5/user6 made 10 days ago user5 = create_transfer_account_user(first_name='Cory', phone="+19011531230", organisation=create_organisation) user5.default_transfer_account.is_approved = True disburse = user5.default_transfer_account._make_initial_disbursement( 202, True) user5.created = user5.created - timedelta(days=10) disburse.created = user5.created - timedelta(days=10) user5._location = 'Truro' user6 = create_transfer_account_user(first_name='Trevor', phone="+19025111230", organisation=create_organisation) user6.default_transfer_account.is_approved = True disburse = user6.default_transfer_account._make_initial_disbursement( 204, True) user6.created = user6.created - timedelta(days=10) disburse.created = user6.created - timedelta(days=10) db.session.commit() tu1 = TransferUsage.find_or_create("Pepperoni") tu2 = TransferUsage.find_or_create("Jalepeno Chips") tu3 = TransferUsage.find_or_create("Shopping Carts") tu1.created = tu1.created - timedelta(days=15) tu2.created = tu1.created - timedelta(days=15) tu3.created = tu1.created - timedelta(days=15) p1 = make_payment_transfer( 100, create_organisation.token, send_user=user1, send_transfer_account=user1.default_transfer_account, receive_user=user2, receive_transfer_account=user2.default_transfer_account, transfer_use=str(int(tu1.id))) p2 = make_payment_transfer( 25, create_organisation.token, send_user=user3, send_transfer_account=user3.default_transfer_account, receive_user=user4, receive_transfer_account=user4.default_transfer_account, transfer_use=str(int(tu1.id))) p2.created = p2.created - timedelta(days=1) p3 = make_payment_transfer( 5, create_organisation.token, send_user=user4, send_transfer_account=user4.default_transfer_account, receive_user=user2, receive_transfer_account=user2.default_transfer_account, transfer_use=str(int(tu2.id))) p3.created = p3.created - timedelta(days=1) p4 = make_payment_transfer( 20, create_organisation.token, send_user=user5, send_transfer_account=user5.default_transfer_account, receive_user=user6, receive_transfer_account=user6.default_transfer_account, transfer_use=str(int(tu3.id))) p4.created = p4.created - timedelta(days=4) p4 = make_payment_transfer( 20, create_organisation.token, send_user=user6, send_transfer_account=user6.default_transfer_account, receive_user=user5, receive_transfer_account=user5.default_transfer_account, transfer_use=str(int(tu2.id))) p4.created = p4.created - timedelta(days=6) db.session.commit()
def make_transfers(disbursement_id, auto_resolve=False): send_transfer_account = g.user.default_organisation.queried_org_level_transfer_account from server.models.user import User from server.models.transfer_account import TransferAccount from server.models.disbursement import Disbursement disbursement = db.session.query(Disbursement).filter( Disbursement.id == disbursement_id).first() for idx, ta in enumerate(disbursement.transfer_accounts): user = ta.primary_user if disbursement.transfer_type == 'DISBURSEMENT': transfer = make_payment_transfer( disbursement.disbursement_amount, send_user=g.user, receive_user=db.session.query(User).filter( User.id == user.id).first(), send_transfer_account=send_transfer_account, receive_transfer_account=db.session.query(TransferAccount). filter(TransferAccount.id == ta.id).first(), transfer_subtype=TransferSubTypeEnum.DISBURSEMENT, transfer_mode=TransferModeEnum.WEB, automatically_resolve_complete=False, ) if disbursement.transfer_type == 'RECLAMATION': transfer = make_payment_transfer( disbursement.disbursement_amount, send_user=db.session.query(User).filter( User.id == user.id).first(), send_transfer_account=db.session.query(TransferAccount).filter( TransferAccount.id == ta.id).first(), transfer_subtype=TransferSubTypeEnum.RECLAMATION, transfer_mode=TransferModeEnum.WEB, require_recipient_approved=False, automatically_resolve_complete=False, ) if disbursement.transfer_type == 'BALANCE': transfer = make_target_balance_transfer( disbursement.disbursement_amount, db.session.query(User).filter(User.id == user.id).first(), automatically_resolve_complete=False, transfer_mode=TransferModeEnum.WEB, ) disbursement.credit_transfers.append(transfer) if auto_resolve: # See below comment on batching issues transfer.resolve_as_complete_and_trigger_blockchain( batch_uuid=None) db.session.commit() percent_complete = ( (idx + 1) / len(disbursement.transfer_accounts)) * 100 yield { 'message': 'success' if percent_complete == 100 else 'pending', 'percent_complete': math.floor(percent_complete), 'data': { 'credit_transfers': credit_transfers_schema.dump( disbursement.credit_transfers).data } }