def _make_initial_disbursement(self,
                                   initial_disbursement=None,
                                   auto_resolve=None):
        from server.utils.credit_transfer import make_payment_transfer

        if not initial_disbursement:
            # if initial_disbursement is still none, then we don't want to create a transfer.
            return None

        user_id = get_authorising_user_id()
        if user_id is not None:
            sender = User.query.execution_options(show_all=True).get(user_id)
        else:
            sender = self.primary_user

        disbursement = make_payment_transfer(
            initial_disbursement,
            token=self.token,
            send_user=sender,
            receive_user=self.primary_user,
            transfer_subtype=TransferSubTypeEnum.DISBURSEMENT,
            transfer_mode=TransferModeEnum.WEB,
            is_ghost_transfer=False,
            require_sender_approved=False,
            require_recipient_approved=False,
            automatically_resolve_complete=auto_resolve)
        disbursement.is_initial_disbursement = True
        return disbursement
Example #2
0
def new_disbursement(create_transfer_account_user):
    from server.utils.credit_transfer import make_payment_transfer
    disbursement = make_payment_transfer(
        100,
        receive_transfer_account=create_transfer_account_user,
        transfer_subtype=TransferSubTypeEnum.DISBURSEMENT)
    return disbursement
Example #3
0
    def transfer_token(sender: User,
                       recipient: User,
                       amount: float,
                       reason_id: Optional[int] = None,
                       transfer_subtype: Optional[TransferSubTypeEnum]=TransferSubTypeEnum.STANDARD):

        sent_token = default_token(sender)
        received_token = default_token(recipient)

        transfer_use = None
        if reason_id is not None:
            transfer_use = str(int(reason_id))
        transfer = make_payment_transfer(amount, token=sent_token, send_user=sender, receive_user=recipient,
                                         transfer_use=transfer_use, is_ghost_transfer=True,
                                         require_sender_approved=False, require_recipient_approved=False,
                                         transfer_subtype=transfer_subtype, transfer_mode=TransferModeEnum.USSD)
        exchanged_amount = None

        if sent_token.id != received_token.id:
            exchange = Exchange()
            exchange.exchange_from_amount(user=recipient, from_token=sent_token, to_token=received_token,
                                          from_amount=amount, prior_task_uuids=[transfer.blockchain_task_uuid],
                                          transfer_mode=TransferModeEnum.USSD)
            exchanged_amount = exchange.to_transfer.transfer_amount

        return exchanged_amount
    def _make_initial_disbursement(self,
                                   initial_disbursement,
                                   auto_resolve=False):
        from server.utils.credit_transfer import make_payment_transfer

        active_org = getattr(g, 'active_organisation',
                             Organisation.master_organisation())
        initial_disbursement = initial_disbursement or active_org.default_disbursement
        if not initial_disbursement:
            return None

        user_id = get_authorising_user_id()
        if user_id is not None:
            sender = User.query.execution_options(show_all=True).get(user_id)
        else:
            sender = self.primary_user

        disbursement = make_payment_transfer(
            initial_disbursement,
            token=self.token,
            send_user=sender,
            receive_user=self.primary_user,
            transfer_subtype=TransferSubTypeEnum.DISBURSEMENT,
            is_ghost_transfer=False,
            require_sender_approved=False,
            require_recipient_approved=False,
            automatically_resolve_complete=auto_resolve)

        return disbursement
def test_get_directory_listing_users(test_client, init_database, dl_processor):
    token1 = Token.query.filter_by(symbol="SM1").first()
    category = dl_processor.selected_business_category
    user = dl_processor.recipient

    def create_user(cat, token, is_market_enabled):
        _user = UserFactory(phone=phone(),
                            business_usage_id=cat.id,
                            is_market_enabled=is_market_enabled)
        create_transfer_account_for_user(_user, token, 200)
        return _user

    market_disabled_user = create_user(category, token1, False)

    # different token user
    token2 = Token.query.filter_by(symbol="SM2").first()
    create_user(category, token2, True)

    # different category user
    category2 = TransferUsage(name='Test', icon='message')
    different_business_category_user = create_user(category2, token1, True)

    shown_user = create_user(category, token1, True)

    # user did more transactions
    more_transactions_user = create_user(category, token1, True)
    make_payment_transfer(100,
                          token=token1,
                          send_user=different_business_category_user,
                          receive_user=more_transactions_user,
                          transfer_use=str(int(category.id)),
                          is_ghost_transfer=False,
                          require_sender_approved=False,
                          require_recipient_approved=False)
    make_payment_transfer(100,
                          token=token1,
                          send_user=different_business_category_user,
                          receive_user=more_transactions_user,
                          transfer_use=str(int(category.id)),
                          is_ghost_transfer=False,
                          require_sender_approved=False,
                          require_recipient_approved=False)

    users = dl_processor.get_directory_listing_users()
    assert len(users) == 2
    assert users[0].id == more_transactions_user.id
    assert users[1].id == shown_user.id
Example #6
0
def test_make_payment_transfer_error(
        test_client, init_database, create_transfer_account_user,
        create_user_with_existing_transfer_account, transfer_amount,
        require_sender_approved, require_recipient_approved,
        require_sufficient_balance, error):
    send_user = create_transfer_account_user
    if require_sender_approved:
        send_user.transfer_account.is_approved = False

    receive_user = create_user_with_existing_transfer_account
    if require_recipient_approved:
        receive_user.transfer_account.is_approved = False

    with pytest.raises(error):
        CreditTransferUtils.make_payment_transfer(
            transfer_amount=transfer_amount,
            token=send_user.transfer_account.token,
            send_user=send_user,
            receive_user=receive_user,
            require_sender_approved=require_sender_approved,
            require_recipient_approved=require_recipient_approved,
            require_sufficient_balance=require_sufficient_balance,
        )
def test_golden_path_send_token(mocker, test_client, init_database,
                                initialised_blockchain_network, init_seed):
    token = Token.query.filter_by(symbol="SM1").first()
    org = OrganisationFactory(country_code=config.DEFAULT_COUNTRY)
    sender = UserFactory(preferred_language="en",
                         phone='+6110011223344',
                         first_name="Bob",
                         last_name="Foo",
                         pin_hash=User.salt_hash_secret('0000'),
                         default_organisation=org)
    create_transfer_account_for_user(sender, token, 4220)

    recipient = UserFactory(preferred_language="sw",
                            phone='+6114433221100',
                            first_name="Joe",
                            last_name="Bar",
                            default_organisation=org)
    create_transfer_account_for_user(recipient, token, 1980)

    usages = TransferUsage.query.filter_by(default=True).order_by(
        TransferUsage.priority).all()
    top_priority = usages[0]
    # Take the last to ensure that we're not going to simply reinforce the existing order
    usage = usages[-1]
    # do two of these transfers to ensure last is is the first shown
    make_payment_transfer(100,
                          token=token,
                          send_user=sender,
                          receive_user=recipient,
                          transfer_use=str(int(usage.id)),
                          is_ghost_transfer=False,
                          require_sender_approved=False,
                          require_recipient_approved=False)

    make_payment_transfer(100,
                          token=token,
                          send_user=sender,
                          receive_user=recipient,
                          transfer_use=str(int(usage.id)),
                          is_ghost_transfer=False,
                          require_sender_approved=False,
                          require_recipient_approved=False)

    def mock_send_message(phone, message):
        messages.append({'phone': phone, 'message': message})

    mocker.patch(f'server.utils.phone._send_twilio_message.submit',
                 mock_send_message)
    mocker.patch(f'server.utils.phone._send_messagebird_message.submit',
                 mock_send_message)
    mocker.patch(f'server.utils.phone._send_at_message.submit',
                 mock_send_message)

    assert get_session() is None
    resp = req("", test_client, sender.phone)
    assert get_session() is not None
    assert "CON Welcome" in resp

    resp = req("1", test_client, sender.phone)
    assert "CON Enter Phone" in resp

    resp = req(recipient.phone, test_client, sender.phone)
    assert "CON Enter Amount" in resp

    resp = req("12.5", test_client, sender.phone)
    assert f"{recipient.user_details()} will receive 12.5 {token.symbol} from {sender.user_details()}" in resp

    resp = req("0000", test_client, sender.phone)
    assert "END Your request has been sent." in resp

    assert default_transfer_account(sender).balance == (4220 - 100 - 100 -
                                                        1250)
    assert default_transfer_account(recipient).balance == (1980 + 100 + 100 +
                                                           1250)

    assert len(messages) == 2
    sent_message = messages[0]
    assert sent_message['phone'] == sender.phone
    assert f"sent 12 SM1 to {recipient.first_name}" in sent_message['message']
    received_message = messages[1]
    assert received_message['phone'] == recipient.phone
    assert f"Umepokea 12 SM1 kutoka kwa {sender.first_name}" in received_message[
        'message']
    def post(self):

        post_data = request.get_json()

        uuid = post_data.get('uuid')
        created = post_data.get('created')

        transfer_use = post_data.get('transfer_use')
        try:
            use_ids = transfer_use.split(',')  # passed as '3,4' etc.
        except AttributeError:
            use_ids = transfer_use
        transfer_mode = post_data.get('transfer_mode')

        transfer_amount = round(Decimal(post_data.get('transfer_amount', 0)), 6)

        transfer_random_key = post_data.get('transfer_random_key')

        pin = post_data.get('pin')

        nfc_serial_number = post_data.get('nfc_id')

        user_id = post_data.get('user_id')
        public_identifier = post_data.get('public_identifier')
        transfer_account_id = post_data.get('transfer_account_id')

        qr_data = post_data.get('qr_data')
        if qr_data is not None:
            qr_data = str(qr_data).strip(" ").strip("\t")

        my_transfer_account_id = post_data.get("my_transfer_account_id")

        is_sending = post_data.get('is_sending', False)

        transfer_card = None
        my_transfer_account = None
        authorised = False
        if transfer_account_id:
            counterparty_transfer_account = TransferAccount.query.get(transfer_account_id)
        else:
            counterparty_transfer_account = None

        if uuid:
            existing_transfer = CreditTransfer.query.filter_by(uuid=uuid).first()

            if existing_transfer:
                # We return a 201 here so that the client removes the uuid from the cache
                response_object = {
                    'message': 'Transfer already in cache',
                    'data': {
                        'credit_transfer': me_credit_transfer_schema.dump(existing_transfer).data,
                    }
                }
                return make_response(jsonify(response_object)), 201

        if qr_data:

            split_qr_data = qr_data.split('-')

            transfer_amount = int(split_qr_data[0])
            transfer_account_id = int(split_qr_data[1])
            user_id = int(split_qr_data[2])
            qr_hash = split_qr_data[3]

            counterparty_user = User.query.get(user_id)

            if not counterparty_user:
                response_object = {
                    'message': 'No such user for ID {}'.format(user_id),
                    'feedback': True,
                }
                return make_response(jsonify(response_object)), 404

            counterparty_transfer_account = TransferAccount.query.get(transfer_account_id)

            if not counterparty_transfer_account:
                response_object = {
                    'message': 'No such Transfer Account for ID {}'.format(transfer_account_id),
                    'feedback': True,
                }
                return make_response(jsonify(response_object)), 404

            if counterparty_transfer_account not in counterparty_user.transfer_accounts:
                if not counterparty_transfer_account:
                    response_object = {
                        'message': 'User {} not authorised for Transfer Account {}.'
                            .format(user_id, transfer_account_id),
                        'feedback': True,
                    }
                    return make_response(jsonify(response_object)), 401

            my_transfer_account = find_transfer_accounts_with_matching_token(
                g.user, counterparty_transfer_account.token
            )

            user_secret = counterparty_user.secret

            if not check_for_any_valid_hash(transfer_amount, transfer_account_id, user_secret, qr_hash):
                response_object = {
                    'message': 'Invalid QR Code',
                    'feedback': True,
                }
                return make_response(jsonify(response_object)), 401

            authorised = True

        elif nfc_serial_number:
            # We treat NFC serials differently because they're automatically authorised under the current version
            transfer_card = TransferCard.query.filter_by(nfc_serial_number=nfc_serial_number).first()

            if transfer_card:
                counterparty_user = transfer_card.user
                counterparty_transfer_account = transfer_card.transfer_account

            if not transfer_card or not counterparty_user or not counterparty_transfer_account:
                response_object = {
                    'message': 'Card not found',
                    'feedback': True
                }
                return make_response(jsonify(response_object)), 404

            authorised = True


        else:
            try:
                counterparty_user, _ = find_user_with_transfer_account_from_identifiers(
                    user_id,
                    public_identifier,
                    transfer_account_id)

            except (NoTransferAccountError, UserNotFoundError) as e:

                if not Web3.isAddress(public_identifier.strip('ethereum:')) or not is_sending:
                    response_object = {
                        'message': str(e),
                        'feedback': True
                    }
                    return make_response(jsonify(response_object)), 400

                my_transfer_account = TransferAccount.query.get(my_transfer_account_id)

                if not my_transfer_account:
                    response_object = {
                        'message': 'Transfer Account not found for my_transfer_account_id {}'.format(
                            my_transfer_account_id)
                    }
                    return make_response(jsonify(response_object)), 400

                #We're sending directly to a blockchain address
                return handle_transfer_to_blockchain_address(transfer_amount,
                                                             my_transfer_account,
                                                             public_identifier.strip('ethereum:'),
                                                             transfer_use,
                                                             transfer_mode=TransferModeEnum.EXTERNAL,
                                                             uuid=uuid)

            if not counterparty_user:
                response_object = {
                    'message': 'User not found',
                    'feedback': True
                }
                return make_response(jsonify(response_object)), 400

            authorised = counterparty_user.verify_password(str(pin))

        if is_sending:
            authorised = True

        if not authorised:
            response_object = {
                'message': 'Not Authorised',
                'feedback': True,
            }
            return make_response(jsonify(response_object)), 401

        if not my_transfer_account:
            if not my_transfer_account_id:
                response_object = {
                    'message': 'You must provide your Transfer Account ID',
                }
                return make_response(jsonify(response_object)), 400

            my_transfer_account = TransferAccount.query.get(my_transfer_account_id)

            if not my_transfer_account:
                response_object = {
                    'message': 'Transfer Account not found for my_transfer_account_id {}'.format(my_transfer_account_id)
                }
                return make_response(jsonify(response_object)), 400

        if my_transfer_account not in g.user.transfer_accounts:
            response_object = {
                'message': 'Transfer account provided does not belong to user',
            }
            return make_response(jsonify(response_object)), 401

        if is_sending:
            send_user = g.user
            send_transfer_account = my_transfer_account
            receive_user = counterparty_user
            receive_transfer_account = counterparty_transfer_account

        else:
            if counterparty_transfer_account is None:
                response_object = {
                    'message': 'Counterparty Transfer Account not specified'
                }
                return make_response(jsonify(response_object)), 400

            send_user = counterparty_user
            send_transfer_account = counterparty_transfer_account
            receive_user = g.user
            receive_transfer_account = my_transfer_account

        if transfer_amount == 0 or transfer_amount > send_transfer_account.balance:

            db.session.commit()

            response_object = {
                'message': 'Insufficient funds',
                'feedback': True,
            }
            return make_response(jsonify(response_object)), 400

        try:
            transfer = make_payment_transfer(transfer_amount=transfer_amount,
                                             send_user=send_user,
                                             send_transfer_account=send_transfer_account,
                                             receive_user=receive_user,
                                             receive_transfer_account=receive_transfer_account,
                                             transfer_use=transfer_use,
                                             transfer_mode=transfer_mode,
                                             uuid=uuid,
                                             transfer_card=transfer_card)

        except AccountNotApprovedError as e:
            db.session.commit()

            if e.is_sender is True:
                response_object = {
                    'message': "Sender is not approved",
                    'feedback': True,
                }
                return make_response(jsonify(response_object)), 400
            elif e.is_sender is False:
                response_object = {
                    'message': "Recipient is not approved",
                    'feedback': True,
                }
                return make_response(jsonify(response_object)), 400
            else:
                response_object = {
                    'message': "Account is not approved",
                    'feedback': True,
                }
                return make_response(jsonify(response_object)), 400


        except InsufficientBalanceError as e:
            db.session.commit()

            response_object = {
                'message': "Insufficient balance",
                'feedback': True,
            }
            return make_response(jsonify(response_object)), 400

        if created:
            try:
                transfer.created = datetime.datetime.strptime(created, "%Y-%m-%dT%H:%M:%S.%fz")
            except ValueError as e:
                pass

        if is_sending:
            push_user_transfer_confirmation(receive_user, transfer_random_key)

        db.session.commit()

        response_object = {
            'message': 'Payment Successful',
            'first_name': counterparty_user.first_name,
            'last_name': counterparty_user.last_name,
            'feedback': True,
            'data': {
                'credit_transfer': me_credit_transfer_schema.dump(transfer).data
            }
        }

        return make_response(jsonify(response_object)), 201
Example #9
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
    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
Example #11
0
def generate_timeseries_metrics(create_organisation):
    # Generates metrics over timeline
    # User1 and User2 made today
    user1 = create_transfer_account_user(first_name='Ricky',
                                         phone="+19025551234",
                                         organisation=create_organisation,
                                         roles=[('BENEFICIARY', 'beneficiary')
                                                ])
    user1.created = zero_time
    user1.default_transfer_account.is_approved = True
    user1.default_transfer_account._make_initial_disbursement(100, True)
    user1._location = 'Sunnyvale'
    attribute_dict = {'custom_attributes': {}}
    attribute_dict['custom_attributes']['colour'] = 'red'
    attribute_dict['custom_attributes']['chips'] = 'Dressed All Over'
    set_custom_attributes(attribute_dict, user1)
    user1.lat = 44.675447
    user1.lng = -63.594995

    user2 = create_transfer_account_user(first_name='Bubbles',
                                         phone="+19025551235",
                                         organisation=create_organisation)
    user2.created = zero_time
    user2.default_transfer_account.is_approved = True
    user2.default_transfer_account._make_initial_disbursement(200, True)
    user2._location = 'Sunnyvale'
    attribute_dict = {'custom_attributes': {}}
    attribute_dict['custom_attributes']['colour'] = 'red'
    attribute_dict['custom_attributes']['chips'] = 'Chicken'
    user2.lat = 44.675447
    user2.lng = -63.594995

    set_custom_attributes(attribute_dict, user2)

    # user3 made yesterday
    user3 = create_transfer_account_user(first_name='Julian',
                                         phone="+19025531230",
                                         organisation=create_organisation)
    user3.default_transfer_account.is_approved = True
    disburse = user3.default_transfer_account._make_initial_disbursement(
        210, True)
    user3.created = zero_time
    user3.created = user3.created - timedelta(days=1) + timedelta(hours=6)
    disburse.created = user3.created - timedelta(days=1) + timedelta(hours=3)
    user3._location = 'Dartmouth'
    attribute_dict = {'custom_attributes': {}}
    attribute_dict['custom_attributes']['colour'] = 'blue'
    attribute_dict['custom_attributes']['chips'] = 'Barbecue'
    set_custom_attributes(attribute_dict, user3)
    user3.lat = 44.668055
    user3.lng = -63.580829

    # user4 made 4 days ago
    user4 = create_transfer_account_user(first_name='Randy',
                                         phone="+19025511230",
                                         organisation=create_organisation)
    user4.created = zero_time
    user4.default_transfer_account.is_approved = True
    disburse = user4.default_transfer_account._make_initial_disbursement(
        201, True)
    user4.created = user4.created - timedelta(days=4) + timedelta(hours=23)
    disburse.created = user4.created - timedelta(days=4) + timedelta(hours=1)
    user4._location = 'Lower Sackville'
    attribute_dict = {'custom_attributes': {}}
    attribute_dict['custom_attributes']['colour'] = 'blue'
    attribute_dict['custom_attributes']['chips'] = 'Barbecue'
    set_custom_attributes(attribute_dict, user4)
    user4.lat = 44.770061
    user4.lng = -63.692723

    # user5/user6 made 10 days ago
    user5 = create_transfer_account_user(first_name='Cory',
                                         phone="+19011531230",
                                         organisation=create_organisation)
    user5.created = zero_time
    user5.default_transfer_account.is_approved = True
    disburse = user5.default_transfer_account._make_initial_disbursement(
        202, True)
    user5.created = user5.created - timedelta(days=10) + timedelta(hours=2)
    disburse.created = user5.created - timedelta(days=10) + timedelta(hours=5)
    user5._location = 'Truro'
    attribute_dict = {'custom_attributes': {}}
    attribute_dict['custom_attributes']['colour'] = 'green'
    attribute_dict['custom_attributes']['chips'] = 'Dressed All Over'
    set_custom_attributes(attribute_dict, user5)
    user5.lat = 45.368075
    user5.lng = -63.256207

    user6 = create_transfer_account_user(first_name='Trevor',
                                         phone="+19025111230",
                                         organisation=create_organisation)
    user6.created = zero_time
    user6.default_transfer_account.is_approved = True
    disburse = user6.default_transfer_account._make_initial_disbursement(
        204, True)
    attribute_dict = {'custom_attributes': {}}
    attribute_dict['custom_attributes']['colour'] = 'red'
    attribute_dict['custom_attributes']['chips'] = 'Jalapeno'
    set_custom_attributes(attribute_dict, user6)
    user6.lat = 44.368363
    user6.lng = -64.526330

    db.session.commit()
    tu1 = TransferUsage.find_or_create("Pepperoni")
    tu2 = TransferUsage.find_or_create("Jalepeno Chips")
    tu3 = TransferUsage.find_or_create("Shopping Carts")
    tu1.created = zero_time - timedelta(days=15) + timedelta(hours=22)
    tu2.created = zero_time - timedelta(days=15) + timedelta(hours=2)
    tu3.created = zero_time - timedelta(days=15) + timedelta(hours=1)

    p1 = make_payment_transfer(
        100,
        create_organisation.token,
        send_user=user1,
        send_transfer_account=user1.default_transfer_account,
        receive_user=user2,
        receive_transfer_account=user2.default_transfer_account,
        transfer_use=str(int(tu1.id)))
    p1.created = zero_time + timedelta(hours=3)

    p2 = make_payment_transfer(
        25,
        create_organisation.token,
        send_user=user3,
        send_transfer_account=user3.default_transfer_account,
        receive_user=user4,
        receive_transfer_account=user4.default_transfer_account,
        transfer_use=str(int(tu1.id)))
    p2.created = zero_time
    p2.created = p2.created - timedelta(days=1) + timedelta(hours=7)

    p3 = make_payment_transfer(
        5,
        create_organisation.token,
        send_user=user4,
        send_transfer_account=user4.default_transfer_account,
        receive_user=user2,
        receive_transfer_account=user2.default_transfer_account,
        transfer_use=str(int(tu2.id)))
    p3.created = zero_time
    p3.created = p3.created - timedelta(days=1) + timedelta(hours=22)

    p4 = make_payment_transfer(
        20,
        create_organisation.token,
        send_user=user5,
        send_transfer_account=user5.default_transfer_account,
        receive_user=user6,
        receive_transfer_account=user6.default_transfer_account,
        transfer_use=str(int(tu3.id)))
    p4.created = zero_time
    p4.created = p4.created - timedelta(days=4) + timedelta(hours=1)

    p5 = make_payment_transfer(
        20,
        create_organisation.token,
        send_user=user6,
        send_transfer_account=user6.default_transfer_account,
        receive_user=user5,
        receive_transfer_account=user5.default_transfer_account,
        transfer_use=str(int(tu2.id)))
    p5.created = zero_time
    p5.created = p5.created - timedelta(days=6) + timedelta(hours=23)
    db.session.commit()
Example #12
0
def test_golden_path_send_token(mocker, test_client, init_database,
                                initialised_blockchain_network, init_seed):
    token = Token.query.filter_by(symbol="SM1").first()
    org = OrganisationFactory(country_code='KE')
    sender = UserFactory(preferred_language="en",
                         phone=make_kenyan_phone(phone()),
                         first_name="Bob",
                         last_name="Foo",
                         pin_hash=User.salt_hash_secret('0000'),
                         default_organisation=org)
    create_transfer_account_for_user(sender, token, 4220)

    recipient = UserFactory(preferred_language="sw",
                            phone=make_kenyan_phone(phone()),
                            first_name="Joe",
                            last_name="Bar",
                            default_organisation=org)
    create_transfer_account_for_user(recipient, token, 1980)

    messages = []
    session_id = 'ATUid_05af06225e6163ec2dc9dc9cf8bc97aa'

    usages = TransferUsage.query.filter_by(default=True).order_by(
        TransferUsage.priority).all()
    top_priority = usages[0]
    # Take the last to ensure that we're not going to simply reinforce the existing order
    usage = usages[-1]
    # do two of these transfers to ensure last is is the first shown
    make_payment_transfer(100,
                          token=token,
                          send_user=sender,
                          receive_user=recipient,
                          transfer_use=str(int(usage.id)),
                          is_ghost_transfer=False,
                          require_sender_approved=False,
                          require_recipient_approved=False)

    make_payment_transfer(100,
                          token=token,
                          send_user=sender,
                          receive_user=recipient,
                          transfer_use=str(int(usage.id)),
                          is_ghost_transfer=False,
                          require_sender_approved=False,
                          require_recipient_approved=False)

    def mock_send_message(phone, message):
        messages.append({'phone': phone, 'message': message})

    mocker.patch(f'server.utils.phone._send_twilio_message.submit',
                 mock_send_message)
    mocker.patch(f'server.utils.phone._send_messagebird_message.submit',
                 mock_send_message)
    mocker.patch(f'server.utils.phone._send_at_message.submit',
                 mock_send_message)

    def req(text):
        response = test_client.post(
            f'/api/v1/ussd/kenya?username={config.EXTERNAL_AUTH_USERNAME}&password={config.EXTERNAL_AUTH_PASSWORD}',
            headers=dict(Accept='application/json'),
            json={
                'sessionId': session_id,
                'phoneNumber': sender.phone,
                'text': text,
                'serviceCode': '*384*23216#'
            })
        assert response.status_code == 200
        return response.data.decode("utf-8")

    def get_session():
        return UssdSession.query.filter_by(session_id=session_id).first()

    assert get_session() is None
    resp = req("")
    assert get_session() is not None
    assert "CON Welcome" in resp

    resp = req("1")
    assert "CON Enter Phone" in resp

    resp = req(recipient.phone)
    assert "CON Enter Amount" in resp

    resp = req("12.5")
    assert "CON Transfer Reason" in resp
    assert f"1. {top_priority.translations['en']}" in resp
    assert "9." in resp

    resp = req("9")
    assert "CON Please specify" in resp
    assert "10. Show previous" in resp
    assert "9." not in resp

    resp = req("10")

    resp = req("4")
    assert "CON Please enter your PIN" in resp

    resp = req("0000")
    assert "CON Send 12.5 SM1" in resp
    # went to second page, should not be the first
    assert f"for {top_priority.translations['en']}" not in resp

    resp = req("1")
    assert "END Your request has been sent." in resp

    assert default_transfer_account(sender).balance == (4220 - 100 - 100 -
                                                        1250)
    assert default_transfer_account(recipient).balance == (1980 + 100 + 100 +
                                                           1250)

    assert len(messages) == 3
    sent_message = messages[1]
    assert sent_message['phone'] == sender.phone
    assert f"sent a payment of 12.50 SM1 to {recipient.first_name}" in sent_message[
        'message']
    received_message = messages[2]
    assert received_message['phone'] == recipient.phone
    assert f"Umepokea 12.50 SM1 kutoka kwa {sender.first_name}" in received_message[
        'message']
Example #13
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()
Example #14
0
def generate_timeseries_metrics(create_organisation):
    # Generates metrics over timeline
    # User1 and User2 made today
    user1 = create_transfer_account_user(first_name='Ricky',
                                         phone="+19025551234",
                                         organisation=create_organisation,
                                         roles=[('BENEFICIARY', 'beneficiary')
                                                ])
    user1.default_transfer_account.is_approved = True
    user1.default_transfer_account._make_initial_disbursement(100, True)
    user1._location = 'Sunnyvale'

    user2 = create_transfer_account_user(first_name='Bubbles',
                                         phone="+19025551235",
                                         organisation=create_organisation)
    user2.default_transfer_account.is_approved = True
    user2.default_transfer_account._make_initial_disbursement(200, True)
    user2._location = 'Sunnyvale'

    # user3 made yesterday
    user3 = create_transfer_account_user(first_name='Julian',
                                         phone="+19025531230",
                                         organisation=create_organisation)
    user3.default_transfer_account.is_approved = True
    disburse = user3.default_transfer_account._make_initial_disbursement(
        210, True)
    user3.created = user3.created - timedelta(days=1)
    disburse.created = user3.created - timedelta(days=1)
    user3._location = 'Dartmouth'

    # user4 made 4 days ago
    user4 = create_transfer_account_user(first_name='Randy',
                                         phone="+19025511230",
                                         organisation=create_organisation)
    user4.default_transfer_account.is_approved = True
    disburse = user4.default_transfer_account._make_initial_disbursement(
        201, True)
    user4.created = user4.created - timedelta(days=4)
    disburse.created = user4.created - timedelta(days=4)
    user4._location = 'Lower Sackville'

    # user5/user6 made 10 days ago
    user5 = create_transfer_account_user(first_name='Cory',
                                         phone="+19011531230",
                                         organisation=create_organisation)
    user5.default_transfer_account.is_approved = True
    disburse = user5.default_transfer_account._make_initial_disbursement(
        202, True)
    user5.created = user5.created - timedelta(days=10)
    disburse.created = user5.created - timedelta(days=10)
    user5._location = 'Truro'

    user6 = create_transfer_account_user(first_name='Trevor',
                                         phone="+19025111230",
                                         organisation=create_organisation)
    user6.default_transfer_account.is_approved = True
    disburse = user6.default_transfer_account._make_initial_disbursement(
        204, True)
    user6.created = user6.created - timedelta(days=10)
    disburse.created = user6.created - timedelta(days=10)

    db.session.commit()

    tu1 = TransferUsage.find_or_create("Pepperoni")
    tu2 = TransferUsage.find_or_create("Jalepeno Chips")
    tu3 = TransferUsage.find_or_create("Shopping Carts")
    tu1.created = tu1.created - timedelta(days=15)
    tu2.created = tu1.created - timedelta(days=15)
    tu3.created = tu1.created - timedelta(days=15)

    p1 = make_payment_transfer(
        100,
        create_organisation.token,
        send_user=user1,
        send_transfer_account=user1.default_transfer_account,
        receive_user=user2,
        receive_transfer_account=user2.default_transfer_account,
        transfer_use=str(int(tu1.id)))

    p2 = make_payment_transfer(
        25,
        create_organisation.token,
        send_user=user3,
        send_transfer_account=user3.default_transfer_account,
        receive_user=user4,
        receive_transfer_account=user4.default_transfer_account,
        transfer_use=str(int(tu1.id)))
    p2.created = p2.created - timedelta(days=1)

    p3 = make_payment_transfer(
        5,
        create_organisation.token,
        send_user=user4,
        send_transfer_account=user4.default_transfer_account,
        receive_user=user2,
        receive_transfer_account=user2.default_transfer_account,
        transfer_use=str(int(tu2.id)))
    p3.created = p3.created - timedelta(days=1)

    p4 = make_payment_transfer(
        20,
        create_organisation.token,
        send_user=user5,
        send_transfer_account=user5.default_transfer_account,
        receive_user=user6,
        receive_transfer_account=user6.default_transfer_account,
        transfer_use=str(int(tu3.id)))
    p4.created = p4.created - timedelta(days=4)

    p4 = make_payment_transfer(
        20,
        create_organisation.token,
        send_user=user6,
        send_transfer_account=user6.default_transfer_account,
        receive_user=user5,
        receive_transfer_account=user5.default_transfer_account,
        transfer_use=str(int(tu2.id)))
    p4.created = p4.created - timedelta(days=6)
    db.session.commit()
Example #15
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
            }
        }