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') 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 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 test_find_user_with_transfer_account_from_identifiers_error( test_client, init_database, create_master_organisation, new_sempo_admin_user): with pytest.raises(NoTransferAccountError): CreditTransferUtils.find_user_with_transfer_account_from_identifiers( new_sempo_admin_user.id, None, None)