def push_admin_credit_transfer(transfers): # If we only get one transfer, make it a list if not isinstance(transfers, list): transfers = [transfers] # Build prepared list of transfers we want to send pusher_batch_payload = [] for transfer in transfers: for org in transfer.organisations: pusher_transfer_payload = {} pusher_transfer_payload['data'] = {} pusher_transfer_payload['data'][ 'credit_transfer'] = credit_transfer_schema.dump(transfer).data pusher_transfer_payload['name'] = 'credit_transfer' pusher_transfer_payload['channel'] = current_app.config[ 'PUSHER_ENV_CHANNEL'] + '-' + str(org.id) pusher_batch_payload.append(pusher_transfer_payload) # Break the list of prepared transfers into MAX_BATCH_SIZE chunks and send each batch to the API for pusher_payload_chunk in misc.chunk_list(pusher_batch_payload, PUSHER_MAX_BATCH_SIZE): try: async_pusher_trigger_batch.submit(pusher_payload_chunk) except Exception as e: print(e) sentry_sdk.capture_exception(e)
def put(self, credit_transfer_id): put_data = request.get_json() action = put_data.get('action', '').upper() credit_transfer = CreditTransfer.query.get(credit_transfer_id) if credit_transfer is None: response_object = { 'message': 'Credit transfer not found for id {}'.format( credit_transfer_id), } return make_response(jsonify(response_object)), 404 if credit_transfer.transfer_status.value not in ['PENDING', 'PARTIAL']: response_object = { 'message': 'Transfer status is {}. Must be PENDING or PARTIAL to modify'. format(credit_transfer.transfer_status.value) } return make_response(jsonify(response_object)), 400 ALLOWED_ACTIONS = ['COMPLETE', 'REJECT'] if action not in ALLOWED_ACTIONS: response_object = { 'message': 'Action is {} not one of {}'.format(action, ALLOWED_ACTIONS) } return make_response(jsonify(response_object)), 400 try: if action == 'COMPLETE': credit_transfer.add_approver_and_resolve_as_completed() elif action == 'REJECT': credit_transfer.resolve_as_rejected() except Exception as e: db.session.commit() response_object = { 'message': str(e), } return make_response(jsonify(response_object)), 400 db.session.flush() response_object = { 'message': 'Modification successful', 'data': { 'credit_transfer': credit_transfer_schema.dump(credit_transfer).data, } } return make_response(jsonify(response_object)), 201
def push_admin_credit_transfer(transfer): new_transfer = credit_transfer_schema.dump(transfer).data try: pusher_client.trigger(current_app.config['PUSHER_ENV_CHANNEL'], 'credit_transfer', {'credit_transfer': new_transfer}) except Exception as e: print(e) sentry.captureException()
def push_admin_credit_transfer(transfer): new_transfer = credit_transfer_schema.dump(transfer).data for org in transfer.organisations: pusher_channel = current_app.config['PUSHER_ENV_CHANNEL'] + '-' + str( org.id) try: async_pusher_trigger.submit(pusher_channel, 'credit_transfer', {'credit_transfer': new_transfer}) except Exception as e: print(e) sentry_sdk.capture_exception(e)
def post(self): post_data = request.get_json() transfer_amount = abs( round(float(post_data.get('transfer_amount') or 0), 6)) sender_blockchain_address = post_data.get('sender_blockchain_address') recipient_blockchain_address = post_data.get( 'recipient_blockchain_address') blockchain_transaction_hash = post_data.get( 'blockchain_transaction_hash') if BlockchainTransaction.query.filter_by( hash=blockchain_transaction_hash).first(): response_object = { 'message': 'Transaction hash already used', } return make_response(jsonify(response_object)), 400 send_address_obj = (BlockchainAddress.query.filter_by( address=sender_blockchain_address).first()) receive_address_obj = (BlockchainAddress.query.filter_by( address=recipient_blockchain_address).first()) if not send_address_obj and not receive_address_obj: response_object = { 'message': 'Neither sender nor receiver found for {} and {}'.format( sender_blockchain_address, recipient_blockchain_address), } return make_response(jsonify(response_object)), 404 # TODO: Handle inbounds to master wallet transfer = make_blockchain_transfer( transfer_amount, sender_blockchain_address, recipient_blockchain_address, existing_blockchain_txn=blockchain_transaction_hash, require_sufficient_balance=False) db.session.flush() credit_transfer = credit_transfer_schema.dump(transfer).data response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } return make_response(jsonify(response_object)), 201
def post(self): # Handle Parameters post_data = request.get_json() recipient_blockchain_address = post_data.get( 'recipient_blockchain_address', None) transfer_amount = post_data.get('transfer_amount', None) master_wallet = g.active_organisation.org_level_transfer_account receive_transfer_account = create_transfer_account_if_required( recipient_blockchain_address, g.active_organisation.token, TransferAccountType.EXTERNAL) error_message = None if not recipient_blockchain_address: error_message = '"recipient_blockchain_address" parameter required' if not transfer_amount: error_message = '"transfer_amount" parameter required' if not error_message and not isinstance(transfer_amount, int): try: transfer_amount = int(transfer_amount) except: error_message = '"transfer_amount must be an integer' if not master_wallet: error_message = f'Organisation {g.active_organisation.id} has no master wallet' if error_message: return {'message': error_message}, 400 transfer = CreditTransfer( transfer_amount, g.active_organisation.token, sender_transfer_account=master_wallet, recipient_transfer_account=receive_transfer_account, transfer_type=TransferTypeEnum. PAYMENT, # PAYMENT, since WITHDRAWAL is to the float account by definition require_sufficient_balance=True) # Make the transfer! transfer.resolve_as_complete_and_trigger_blockchain() credit_transfer = credit_transfer_schema.dump(transfer) response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } return response_object, 201
def post(self): post_data = request.get_json() transfer_amount = post_data.get('transfer_amount') sender_blockchain_address = post_data.get('sender_blockchain_address') recipient_blockchain_address = post_data.get( 'recipient_blockchain_address') blockchain_transaction_hash = post_data.get( 'blockchain_transaction_hash') contract_address = post_data.get('contract_address') transfer = CreditTransfer.query.execution_options( show_all=True).filter_by( blockchain_hash=blockchain_transaction_hash).first() # Case 1: Transfer exists in the database already. Mark received_third_party_sync as true, fetch it, and return it in that case if transfer: transfer.received_third_party_sync = True credit_transfer = credit_transfer_schema.dump(transfer).data response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } else: token = Token.query.filter_by(address=contract_address).first() maybe_sender_transfer_account = TransferAccount.query.execution_options( show_all=True).filter_by( blockchain_address=sender_blockchain_address).first() maybe_sender_user = maybe_sender_transfer_account.users[ 0] if maybe_sender_transfer_account and len( maybe_sender_transfer_account.users) == 1 else None maybe_recipient_transfer_account = TransferAccount.query.execution_options( show_all=True).filter_by( blockchain_address=recipient_blockchain_address).first() maybe_recipient_user = maybe_recipient_transfer_account.users[ 0] if maybe_recipient_transfer_account and len( maybe_recipient_transfer_account.users) == 1 else None # Case 2: Two non-sempo users making a trade on our token. We don't have to track this! if not maybe_recipient_transfer_account and not maybe_sender_transfer_account: response_object = { 'message': 'No existing users involved in this transfer', 'data': {} } # Case 3: Two non-Sempo users, at least one of whom has interacted with Sempo users before transacting with one another # We don't have to track this either! elif ( # The recipient is either an external transfer account we've seen before # OR we haven't seen them before and so can infer they're external ((maybe_recipient_transfer_account and maybe_recipient_transfer_account.account_type == TransferAccountType.EXTERNAL) or not maybe_recipient_transfer_account) and # # And the sender is either an external transfer account we've seen before # OR we haven't seen them before and so can infer they're external ((maybe_sender_transfer_account and maybe_sender_transfer_account.account_type == TransferAccountType.EXTERNAL) or not maybe_sender_transfer_account)): response_object = { 'message': 'Only external users involved in this transfer', 'data': {} } # Case 4: One or both of the transfer accounts are affiliated with Sempo accounts. # This is the only case where we want to generate a new CreditTransfer object. else: send_transfer_account = create_transfer_account_if_required( sender_blockchain_address, token, TransferAccountType.EXTERNAL) receive_transfer_account = create_transfer_account_if_required( recipient_blockchain_address, token, TransferAccountType.EXTERNAL) transfer = CreditTransfer( transfer_amount, token=token, sender_transfer_account=send_transfer_account, recipient_transfer_account=receive_transfer_account, transfer_type=TransferTypeEnum.PAYMENT, sender_user=maybe_sender_user, recipient_user=maybe_recipient_user, require_sufficient_balance=False, received_third_party_sync=True) transfer.resolve_as_complete_with_existing_blockchain_transaction( blockchain_transaction_hash) db.session.flush() credit_transfer = credit_transfer_schema.dump(transfer).data response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } return make_response(jsonify(response_object)), 201
def get(self, credit_transfer_id): transfer_account_ids = request.args.get('transfer_account_ids') # Handle search parameters # HANDLE PARAM : search_string - Any search string. An empty string (or None) will just return everything! search_string = request.args.get('search_string') or '' # HANDLE PARAM : params - Standard filter object. Exact same as the ones Metrics uses! encoded_filters = request.args.get('params') filters = process_transfer_filters(encoded_filters) # HANDLE PARAM : order # Valid orders types are: `ASC` and `DESC` # Default: DESC order_arg = request.args.get('order') or 'DESC' if order_arg.upper() not in ['ASC', 'DESC']: return { 'message': 'Invalid order value \'{}\'. Please use \'ASC\' or \'DESC\''. format(order_arg) } order = asc if order_arg.upper() == 'ASC' else desc # HANDLE PARAM: sort_by # Valid orders types are: first_name, last_name, email, date_account_created, rank, balance, status # Default: rank sort_by_arg = request.args.get('sort_by') or 'rank' if credit_transfer_id: credit_transfer = CreditTransfer.query.get(credit_transfer_id) if credit_transfer is None: return make_response( jsonify({'message': 'Credit transfer not found'})), 404 if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'admin'): result = credit_transfer_schema.dump(credit_transfer).data elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'): result = view_credit_transfer_schema.dump(credit_transfer).data transfer_stats = [] response_object = { 'status': 'success', 'message': 'Successfully Loaded.', 'data': { 'credit_transfer': result, 'transfer_stats': transfer_stats } } return make_response(jsonify(response_object)), 200 else: if transfer_account_ids: # We're getting a list of transfer accounts - parse try: parsed_transfer_account_ids = list( map(lambda x: int(x), filter(None, transfer_account_ids.split(',')))) except Exception as e: response_object = {'status': 'fail', 'message': str(e)} return make_response(jsonify(response_object)), 400 if parsed_transfer_account_ids: try: query = generate_search_query( search_string, filters, order, sort_by_arg, search_type=CREDIT_TRANSFER) except Exception as e: response_object = {'status': 'fail', 'message': str(e)} return make_response(jsonify(response_object)), 200 final_query = query.filter( or_( CreditTransfer.recipient_transfer_account_id.in_( parsed_transfer_account_ids), CreditTransfer.sender_transfer_account_id.in_( parsed_transfer_account_ids))) credit_transfers, total_items, total_pages, new_last_fetched = paginate_query( final_query, ignore_last_fetched=True) if AccessControl.has_sufficient_tier( g.user.roles, 'ADMIN', 'admin'): result = credit_transfers_schema.dump(credit_transfers) elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'): result = view_credit_transfers_schema.dump( credit_transfers) return { 'status': 'success', 'message': 'Successfully Loaded.', 'items': total_items, 'pages': total_pages, 'last_fetched': new_last_fetched, 'query_time': datetime.datetime.utcnow(), 'data': { 'credit_transfers': result.data } } else: try: final_query = generate_search_query( search_string, filters, order, sort_by_arg, search_type=CREDIT_TRANSFER) except Exception as e: response_object = {'status': 'fail', 'message': str(e)} return make_response(jsonify(response_object)), 200 credit_transfers, total_items, total_pages, new_last_fetched = paginate_query( final_query, ignore_last_fetched=True) if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'admin'): result = credit_transfers_schema.dump(credit_transfers) elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'): result = view_credit_transfers_schema.dump(credit_transfers) return { 'status': 'success', 'message': 'Successfully Loaded.', 'items': total_items, 'pages': total_pages, 'last_fetched': new_last_fetched, 'query_time': datetime.datetime.utcnow(), 'data': { 'credit_transfers': result.data } }
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 post(self, credit_transfer_id): 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)) 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 (NoTransferAccountError, UserNotFoundError) as e: response_object = { 'message': str(e), } return make_response(jsonify(response_object)), 400 if not CreditTransfer.check_has_correct_users_for_transfer_type( transfer_type, individual_sender_user, individual_recipient_user): response_object = { 'message': 'For transfer type {}, wrong users of {} and {}'.format( transfer_type, individual_sender_user, individual_recipient_user) } return make_response(jsonify(response_object)), 400 for sender_user, recipient_user in transfer_user_list: try: if transfer_type == 'PAYMENT': transfer = make_payment_transfer(transfer_amount, sender_user, recipient_user, transfer_use, uuid=uuid) elif transfer_type == 'WITHDRAWAL': transfer = make_withdrawal_transfer(transfer_amount, sender_user, uuid=uuid) elif transfer_type == 'DISBURSEMENT': transfer = make_disbursement_transfer(transfer_amount, recipient_user, uuid=uuid) elif transfer_type == 'BALANCE': transfer = make_target_balance_transfer(target_balance, recipient_user, uuid=uuid) 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: if is_bulk: credit_transfers.append(transfer) db.session.commit() response_list.append({ 'status': 200, 'message': 'Transfer Successful' }) else: db.session.commit() credit_transfer = credit_transfer_schema.dump( transfer).data response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } return make_response(jsonify(response_object)), 201 db.session.commit() response_object = { 'message': 'Bulk Transfer Creation Successful', 'bulk_responses': response_list, 'data': { 'credit_transfers': credit_transfers_schema.dump(credit_transfers).data } } return make_response(jsonify(response_object)), 201
def get(self, credit_transfer_id): transfer_account_ids = request.args.get('transfer_account_ids') transfer_type = request.args.get('transfer_type', 'ALL') result = None if transfer_type: transfer_type = transfer_type.upper() if credit_transfer_id: credit_transfer = CreditTransfer.query.get(credit_transfer_id) if credit_transfer is None: return make_response( jsonify({'message': 'Credit transfer not found'})), 404 if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'admin'): result = credit_transfer_schema.dump(credit_transfer).data elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'): result = view_credit_transfer_schema.dump(credit_transfer).data transfer_stats = [] response_object = { 'status': 'success', 'message': 'Successfully Loaded.', 'data': { 'credit_transfer': result, 'transfer_stats': transfer_stats } } return make_response(jsonify(response_object)), 200 else: query = CreditTransfer.query transfer_list = None if transfer_type != 'ALL': try: transfer_type_enum = TransferTypeEnum[transfer_type] query = query.filter( CreditTransfer.transfer_type == transfer_type_enum) except KeyError: response_object = { 'message': 'Invalid Filter: Transfer Type ', } return make_response(jsonify(response_object)), 400 if transfer_account_ids: # We're getting a list of transfer accounts - parse try: parsed_transfer_account_ids = list( map(lambda x: int(x), filter(None, transfer_account_ids.split(',')))) except ValueError: response_object = { 'message': 'Invalid Filter: Transfer Account IDs ', } return make_response(jsonify(response_object)), 400 if parsed_transfer_account_ids: query = query.filter( or_( CreditTransfer.recipient_transfer_account_id.in_( parsed_transfer_account_ids), CreditTransfer.sender_transfer_account_id.in_( parsed_transfer_account_ids))) transfers, total_items, total_pages, new_last_fetched = paginate_query( query) if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'admin'): transfer_list = credit_transfers_schema.dump(transfers).data elif AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'view'): transfer_list = view_credit_transfers_schema.dump( transfers).data response_object = { 'status': 'success', 'message': 'Successfully Loaded.', 'items': total_items, 'pages': total_pages, 'last_fetched': new_last_fetched, 'data': { 'credit_transfers': transfer_list, } } return make_response(jsonify(response_object)), 200