def post(self):

        post_data = request.get_json()

        withdrawal_id_list = post_data.get('withdrawal_id_list')
        credit_transfers = []

        for withdrawal_id_string in withdrawal_id_list:

            withdrawal_id = int(withdrawal_id_string)

            withdrawal = CreditTransfer.query.get(withdrawal_id)

            withdrawal.resolve_as_completed()

            credit_transfers.append(
                CreditTransfer.query.get(withdrawal_id_string))

        db.session.flush()

        response_object = {
            'message': 'Withdrawal Confirmed',
            'data': {
                'credit_transfers':
                credit_transfers_schema.dump(credit_transfers).data,
            }
        }

        return make_response(jsonify(response_object)), 201
Пример #2
0
    def get(self, disbursement_id):
        transfers = db.session.query(CreditTransfer)\
            .filter(CreditTransfer.disbursement.has(id=disbursement_id))\
            .options(joinedload(CreditTransfer.disbursement))

        transfers, total_items, total_pages, new_last_fetched = paginate_query(
            transfers)

        if transfers is None:
            response_object = {
                'message': 'No credit transfers',
            }

            return make_response(jsonify(response_object)), 400

        transfer_list = credit_transfers_schema.dump(transfers).data

        d = db.session.query(Disbursement).filter_by(
            id=disbursement_id).first()

        disbursement = disbursement_schema.dump(d).data

        response_object = {
            'status': 'success',
            'message': 'Successfully Loaded.',
            'items': total_items,
            'pages': total_pages,
            'last_fetched': new_last_fetched,
            'data': {
                'credit_transfers': transfer_list,
                'disbursement': disbursement
            }
        }
        return make_response(jsonify(response_object)), 200
Пример #3
0
    def get(self):
        """
        This endpoint searches transfer accounts and credit transfers. It will check first name/last name/phone number/email address
        Parameters:
            - search_string: Any string you want to search. When empty or not provided, all results will be returned. 
            - search_type: Valid inputs are transfer_accounts, and credit_transfers.
            - order: Which order to return results in (ASC or DESC)
            - sort_by: What to sort by. `rank` works for both, and sorts by search relevance.
                - Transfer Accounts can be sorted by: 'first_name', 'last_name', 'email', 'date_account_created', 'rank', 'balance', 'status'
                - Credit Transfers can be sorted by: 'sender_first_name', 'sender_last_name', 'sender_email', 'sender_date_account_created', 
                  'recipient_first_name', 'recipient_last_name', 'recipient_email', 'recipient_date_account_created', 'rank', 'amount', 
                  'transfer_type', 'approval', 'date_transaction_created'
        Return Value:
            Results object, similar to the existing transfer_accounts and credit_transfers API return values
        """
        # HANDLE PARAM : search_stirng
        search_string = request.args.get('search_string') or ''

        # HANDLE PARAM : search_type
        # Valid search types are: `transfer_accounts` and `credit_transfers`
        # Default: transfer_accounts
        search_type = request.args.get('search_type') or 'transfer_accounts'
        if search_type not in ['transfer_accounts', 'credit_transfers']:
            response_object = {
                'message':
                'Invalid search_type \'{}\'. Please use type \'transfer_accounts\' or \'credit_transfers\''
                .format(search_type),
            }
            return make_response(jsonify(response_object)), 400

        # HANDLE PARAM : sort_by
        # Valid params differ depending on sort_by. See: sort_by
        # Default: rank

        # Aliases used for joining the separate sender and recipient objects to transfers
        sender = aliased(User)
        recipient = aliased(User)
        # Build order by object
        sort_types_to_database_types = {
            'first_name': User.first_name,
            'last_name': User.last_name,
            'email': User.email,
            'date_account_created': User.created,
            'rank': 'rank',
            'balance': TransferAccount._balance_wei,
            'status': TransferAccount.is_approved,
            'amount': CreditTransfer.transfer_amount,
            'transfer_type': CreditTransfer.transfer_type,
            'approval': CreditTransfer.transfer_status,
            'date_transaction_created': CreditTransfer.resolved_date,
            'sender_first_name': sender.first_name,
            'sender_last_name': sender.last_name,
            'sender_email': sender.email,
            'sender_date_account_created': recipient.created,
            'recipient_first_name': recipient.first_name,
            'recipient_last_name': recipient.last_name,
            'recipient_email': recipient.email,
            'recipient_date_account_created': recipient.created
        }

        # These lists are to validate the user input-- not using sort_types_to_database_types since credit_transfers and transfer_accounts have unique options
        user_sorting_options = [
            'first_name', 'last_name', 'email', 'date_account_created'
        ]
        sender_sorting_options = list(
            map(lambda s: 'sender_' + s, user_sorting_options)
        )  # sender_first_name, sender_last_name, etc...
        recipient_sorting_options = list(
            map(lambda s: 'recipient_' + s, user_sorting_options)
        )  # recipient_first_name, recipient_last_name, etc...
        sorting_options = {
            'transfer_accounts':
            [*user_sorting_options, 'rank', 'balance', 'status'],
            'credit_transfers': [
                *sender_sorting_options, *recipient_sorting_options, 'rank',
                'amount', 'transfer_type', 'approval',
                'date_transaction_created'
            ]
        }
        sort_by_arg = request.args.get('sort_by') or 'rank'
        if sort_by_arg not in sorting_options[search_type]:
            response_object = {
                # Example output:
                # "Invalid sort_by value 'pizza'. Please use one of the following: 'first_name', 'last_name', 'email', 'rank', 'balance', 'status', 'date_account_created'"
                'message': 'Invalid sort_by value \'{}\'. Please use one of the following: {}'\
                    .format(sort_by_arg, ', '.join('\'{}\''.format(a) for a in sorting_options[search_type])),
            }
            return make_response(jsonify(response_object)), 400
        sort_by = sort_types_to_database_types[sort_by_arg]

        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 not in ['ASC', 'DESC']:
            response_object = {
                'message':
                'Invalid order value \'{}\'. Please use \'ASC\' or \'DESC\''.
                format(order_arg),
            }
            return make_response(jsonify(response_object)), 400
        order = desc
        if order_arg == 'ASC':
            order = asc

        # Note: Using tsquery wildcards here. Good docs of them here:
        # https://www.postgresql.org/docs/current/datatype-textsearch.html#DATATYPE-TSQUERY
        # 'Fran deRoo' -> 'Fran:* | deRoo:*'
        # Matches strings like "Francine deRoos"
        # Will also match "Michiel deRoos" because of the or clause, but this will be ranked lower
        search_string = re.sub('\s+', ' ', search_string)
        search_terms = search_string.strip().split(' ')
        tsquery = ':* | '.join(search_terms) + ':*'

        # Return everything if the search string is empty
        if search_string == '':
            if search_type == 'transfer_accounts':
                final_query = TransferAccount.query.filter(TransferAccount.is_ghost != True)\
                    .join(User, User.default_transfer_account_id == TransferAccount.id)
                final_query = apply_filters(final_query, filters, User)
                if sort_by_arg == 'rank':
                    # There's no search rank when there's no query string, so do chrono instead
                    final_query = final_query.order_by(order(User.created))
                else:
                    final_query = final_query.order_by(order(sort_by))

                transfer_accounts, total_items, total_pages = paginate_query(
                    final_query, TransferAccount)
                result = transfer_accounts_schema.dump(transfer_accounts)
                data = {'transfer_accounts': result.data}
            else:
                final_query = CreditTransfer.query.filter()\
                    .outerjoin(sender, sender.default_transfer_account_id == CreditTransfer.sender_transfer_account_id)\
                    .outerjoin(recipient, recipient.default_transfer_account_id == CreditTransfer.recipient_transfer_account_id)
                if sort_by_arg == 'rank':
                    # There's no search rank when there's no query string, so do chrono instead
                    final_query = final_query.order_by(
                        order(CreditTransfer.created))
                else:
                    final_query = final_query.order_by(order(sort_by))

                credit_transfers, total_items, total_pages = paginate_query(
                    final_query, CreditTransfer)
                result = credit_transfers_schema.dump(credit_transfers)
                data = {'credit_transfers': result.data}

        else:
            # First get users who match search string
            user_search_result = db.session.query(
                db.distinct(SearchView.id),
                SearchView,
                # This ugly (but functional) multi-tscolumn ranking is a modified from Ben Smithgall's blog post
                # https://www.codeforamerica.org/blog/2015/07/02/multi-table-full-text-search-with-postgres-flask-and-sqlalchemy/
                db.func.max(db.func.full_text.ts_rank(
                    db.func.setweight(db.func.coalesce(SearchView.tsv_email, ''), 'D')\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_phone, ''), 'A'))\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_first_name, ''), 'B'))\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_last_name, ''), 'B'))\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_public_serial_number, ''), 'A'))\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_primary_blockchain_address, ''), 'A'))\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_location, ''), 'C'))\
                        .concat(db.func.setweight(db.func.coalesce(SearchView.tsv_default_transfer_account_id, ''), 'A')),
                        db.func.to_tsquery(tsquery, postgresql_regconfig='english')))\
                .label('rank'))\
                .group_by(SearchView)\
                .subquery()

            # Then use those results to join aginst TransferAccount or CreditTransfer
            if search_type == 'transfer_accounts':
                # TransferAccount Search Logic
                final_query = db.session.query(TransferAccount)\
                    .join(user_search_result, user_search_result.c.default_transfer_account_id == TransferAccount.id)\
                    .join(User, user_search_result.c.default_transfer_account_id == User.default_transfer_account_id)\
                    .filter(user_search_result.c.rank > 0.0)\
                    .filter(TransferAccount.is_ghost != True)
                final_query = apply_filters(final_query, filters, User)

                if sort_by_arg == 'rank':
                    final_query = final_query.order_by(
                        order(user_search_result.c.rank))
                else:
                    final_query = final_query.order_by(order(sort_by))

                transfer_accounts, total_items, total_pages = paginate_query(
                    final_query, TransferAccount)
                result = transfer_accounts_schema.dump(transfer_accounts)
                data = {'transfer_accounts': result.data}
            # CreditTransfer Search Logic
            else:
                sender_search_result = aliased(user_search_result)
                recipient_search_result = aliased(user_search_result)
                # Join the search results objects to sort by rank, as well as aliased user objects (sender/recipient) for other sorting options
                final_query = db.session.query(CreditTransfer)\
                    .outerjoin(sender_search_result,
                        sender_search_result.c.default_transfer_account_id == CreditTransfer.sender_transfer_account_id
                    )\
                    .outerjoin(recipient_search_result,
                        recipient_search_result.c.default_transfer_account_id == CreditTransfer.recipient_transfer_account_id
                    )\
                    .outerjoin(sender,
                        sender_search_result.c.default_transfer_account_id == sender.default_transfer_account_id
                    )\
                    .outerjoin(recipient,
                        recipient_search_result.c.default_transfer_account_id == recipient.default_transfer_account_id
                    )\
                    .filter(or_(recipient_search_result.c.rank > 0.0, sender_search_result.c.rank > 0.0))

                if sort_by_arg == 'rank':
                    final_query = final_query.order_by(
                        order(recipient_search_result.c.rank +
                              sender_search_result.c.rank))
                else:
                    final_query = final_query.order_by(order(sort_by))
                credit_transfers, total_items, total_pages = paginate_query(
                    final_query, CreditTransfer)
                result = credit_transfers_schema.dump(credit_transfers)
                data = {'credit_transfers': result.data}

        response_object = {
            'message': 'Successfully Loaded.',
            'items': total_items,
            'pages': total_pages,
            'query_time': datetime.datetime.utcnow(),
            'data': data
        }

        bytes_data = orjson.dumps(response_object)
        resp = make_response(bytes_data, 200)
        resp.mimetype = 'application/json'
        return resp
Пример #4
0
    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
                }
            }
Пример #5
0
    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
Пример #6
0
    def get(self, credit_transfer_id):
        transfer_account_ids = request.args.get('transfer_account_ids')
        transfer_type = request.args.get('transfer_type', 'ALL')
        get_transfer_stats = request.args.get('get_stats', False)

        transfer_list = 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'):
                transfer_list = credit_transfers_schema.dump([credit_transfer
                                                              ]).data
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                transfer_list = view_credit_transfers_schema.dump(
                    [credit_transfer]).data

            transfer_stats = []

            response_object = {
                'status': 'success',
                'message': 'Successfully Loaded.',
                'data': {
                    'credit_transfer': transfer_list,
                    '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 = paginate_query(
                query, CreditTransfer)

            if get_transfer_stats:
                transfer_stats = calculate_transfer_stats(
                    total_time_series=True)
            else:
                transfer_stats = None

            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,
                'data': {
                    'credit_transfers': transfer_list,
                    'transfer_stats': transfer_stats
                }
            }

            return make_response(jsonify(response_object)), 200
Пример #7
0
    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
Пример #8
0
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()
Пример #9
0
    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')
        get_transfer_stats = False

        transfer_list = 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'):
                transfer_list = credit_transfers_schema.dump([credit_transfer
                                                              ]).data
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                transfer_list = view_credit_transfers_schema.dump(
                    [credit_transfer]).data

            transfer_stats = []

            response_object = {
                'status': 'success',
                'message': 'Successfully Loaded.',
                'data': {
                    'credit_transfer': transfer_list,
                    '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 = paginate_query(
                query, CreditTransfer)

            #
            # if get_transfer_stats:
            #     transfer_stats = calculate_transfer_stats(total_time_series=True)
            # else:
            #     transfer_stats = None

            transfer_stats = {
                'total_distributed':
                '15300.0000000000000000',
                'total_spent':
                0,
                'total_exchanged':
                0,
                'has_transferred_count':
                0,
                'zero_balance_count':
                0,
                'total_beneficiaries':
                3,
                'total_users':
                3,
                'master_wallet_balance':
                0,
                'daily_transaction_volume': [{
                    'date': '2020-04-29T07:05:01.771621',
                    'volume': 0
                }],
                'daily_disbursement_volume': [{
                    'date':
                    '2020-04-29T00:00:00',
                    'volume': ('300.0000000000000000')
                }, {
                    'date':
                    '2020-04-28T00:00:00',
                    'volume':
                    '15000.0000000000000000'
                }],
                'transfer_use_breakdown': [],
                'last_day_volume': {
                    'date': '2020-04-29T07:05:01.771617',
                    'volume': 0
                },
                'filter_active':
                False
            }

            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,
                'data': {
                    'credit_transfers': transfer_list,
                    'transfer_stats': transfer_stats
                }
            }

            return make_response(jsonify(response_object)), 200
Пример #11
0
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
            }
        }