Пример #1
0
def subscription_list_post(request):
    """Create a new subscription 

    """
    company = auth_api_key(request)
    form = validate_form(SubscriptionCreateForm, request)

    customer_guid = form.data['customer_guid']
    plan_guid = form.data['plan_guid']
    amount = form.data.get('amount')
    payment_uri = form.data.get('payment_uri')
    if not payment_uri:
        payment_uri = None
    started_at = form.data.get('started_at')
    maximum_retry = int(request.registry.settings.get(
        'billy.transaction.maximum_retry', 
        TransactionModel.DEFAULT_MAXIMUM_RETRY,
    ))

    model = SubscriptionModel(request.session)
    plan_model = PlanModel(request.session)
    customer_model = CustomerModel(request.session)
    tx_model = TransactionModel(request.session)

    customer = customer_model.get(customer_guid)
    if customer.company_guid != company.guid:
        return HTTPForbidden('Can only subscribe to your own customer')
    if customer.deleted:
        return HTTPBadRequest('Cannot subscript to a deleted customer')
    plan = plan_model.get(plan_guid)
    if plan.company_guid != company.guid:
        return HTTPForbidden('Can only subscribe to your own plan')
    if plan.deleted:
        return HTTPBadRequest('Cannot subscript to a deleted plan')

    # create subscription and yield transactions
    with db_transaction.manager:
        guid = model.create(
            customer_guid=customer_guid, 
            plan_guid=plan_guid, 
            amount=amount, 
            payment_uri=payment_uri,
            started_at=started_at, 
        )
        tx_guids = model.yield_transactions([guid])
    # this is not a deferred subscription, just process transactions right away
    if started_at is None:
        with db_transaction.manager:
            tx_model.process_transactions(
                processor=request.processor, 
                guids=tx_guids,
                maximum_retry=maximum_retry,
            )

    subscription = model.get(guid)
    return subscription
Пример #2
0
def subscription_cancel(request):
    """Cancel a subscription

    """
    # TODO: it appears a DELETE request with body is not a good idea
    # for HTTP protocol as many server doesn't support this, this is why
    # we use another view with post method, maybe we should use a better 
    # approach later
    company = auth_api_key(request)
    form = validate_form(SubscriptionCancelForm, request)

    guid = request.matchdict['subscription_guid']
    prorated_refund = asbool(form.data.get('prorated_refund', False))
    refund_amount = form.data.get('refund_amount')
    maximum_retry = int(request.registry.settings.get(
        'billy.transaction.maximum_retry', 
        TransactionModel.DEFAULT_MAXIMUM_RETRY,
    ))

    model = SubscriptionModel(request.session)
    tx_model = TransactionModel(request.session)
    get_and_check_subscription(request, company, guid)
    subscription = model.get(guid)

    # TODO: maybe we can find a better way to integrate this with the 
    # form validation?
    if refund_amount is not None:
        if subscription.amount is not None:
            amount = subscription.amount
        else:
            amount = subscription.plan.amount
        if refund_amount > amount:
            return form_errors_to_bad_request(dict(
                refund_amount=['refund_amount cannot be greater than '
                               'subscription amount {}'.format(amount)]
            ))

    if subscription.canceled:
        return HTTPBadRequest('Cannot cancel a canceled subscription')

    with db_transaction.manager:
        tx_guid = model.cancel(
            guid, 
            prorated_refund=prorated_refund,
            refund_amount=refund_amount, 
        )
    if tx_guid is not None:
        with db_transaction.manager:
            tx_model.process_transactions(
                processor=request.processor, 
                guids=[tx_guid],
                maximum_retry=maximum_retry,
            )

    subscription = model.get(guid)
    return subscription 
Пример #3
0
    def post(self):
        request = self.request
        form = validate_form(CompanyCreateForm, request)
        processor_key = form.data['processor_key']
        # TODO: validate API key in processor?

        model = request.model_factory.create_company_model()
        with db_transaction.manager:
            company = model.create(processor_key=processor_key)
        return company
Пример #4
0
def subscription_cancel(request):
    """Cancel a subscription

    """
    # TODO: it appears a DELETE request with body is not a good idea
    # for HTTP protocol as many server doesn't support this, this is why
    # we use another view with post method, maybe we should use a better 
    # approach later
    company = auth_api_key(request)
    form = validate_form(SubscriptionCancelForm, request)

    guid = request.matchdict['subscription_guid']
    prorated_refund = asbool(form.data.get('prorated_refund', False))
    refund_amount = form.data.get('refund_amount')
    maximum_retry = int(request.registry.settings.get(
        'billy.transaction.maximum_retry', 
        TransactionModel.DEFAULT_MAXIMUM_RETRY,
    ))

    model = SubscriptionModel(request.session)
    tx_model = TransactionModel(request.session)
    get_and_check_subscription(request, company, guid)

    # TODO: maybe we can find a better way to integrate this with the 
    # form validation?
    if refund_amount is not None:
        subscription = model.get(guid)
        if subscription.amount is not None:
            amount = subscription.amount
        else:
            amount = subscription.plan.amount
        if refund_amount > amount:
            return form_errors_to_bad_request(dict(
                refund_amount=['refund_amount cannot be greater than '
                               'subscription amount {}'.format(amount)]
            ))

    # TODO: make sure the subscription is not already canceled

    with db_transaction.manager:
        tx_guid = model.cancel(
            guid, 
            prorated_refund=prorated_refund,
            refund_amount=refund_amount, 
        )
    if tx_guid is not None:
        with db_transaction.manager:
            tx_model.process_transactions(
                processor=request.processor, 
                guids=[tx_guid],
                maximum_retry=maximum_retry,
            )

    subscription = model.get(guid)
    return subscription 
Пример #5
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)

        form = validate_form(SubscriptionCreateForm, request)

        customer_guid = form.data['customer_guid']
        plan_guid = form.data['plan_guid']
        amount = form.data.get('amount')
        funding_instrument_uri = form.data.get('funding_instrument_uri')
        if not funding_instrument_uri:
            funding_instrument_uri = None
        appears_on_statement_as = form.data.get('appears_on_statement_as')
        if not appears_on_statement_as:
            appears_on_statement_as = None
        started_at = form.data.get('started_at')

        sub_model = request.model_factory.create_subscription_model()
        plan_model = request.model_factory.create_plan_model()
        customer_model = request.model_factory.create_customer_model()
        tx_model = request.model_factory.create_transaction_model()

        customer = customer_model.get(customer_guid)
        if customer.company_guid != company.guid:
            return HTTPForbidden('Can only subscribe to your own customer')
        if customer.deleted:
            return HTTPBadRequest('Cannot subscript to a deleted customer')
        plan = plan_model.get(plan_guid)
        if plan.company_guid != company.guid:
            return HTTPForbidden('Can only subscribe to your own plan')
        if plan.deleted:
            return HTTPBadRequest('Cannot subscript to a deleted plan')

        if funding_instrument_uri is not None:
            processor = request.model_factory.create_processor()
            processor.configure_api_key(customer.company.processor_key)
            processor.validate_funding_instrument(funding_instrument_uri)

        # create subscription and yield transactions
        with db_transaction.manager:
            subscription = sub_model.create(
                customer=customer,
                plan=plan,
                amount=amount,
                funding_instrument_uri=funding_instrument_uri,
                appears_on_statement_as=appears_on_statement_as,
                started_at=started_at,
            )
            invoices = subscription.invoices
        # this is not a deferred subscription, just process transactions right away
        if started_at is None:
            with db_transaction.manager:
                tx_model.process_transactions(invoices[0].transactions)

        return subscription
Пример #6
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)

        form = validate_form(SubscriptionCreateForm, request)

        customer_guid = form.data['customer_guid']
        plan_guid = form.data['plan_guid']
        amount = form.data.get('amount')
        funding_instrument_uri = form.data.get('funding_instrument_uri')
        if not funding_instrument_uri:
            funding_instrument_uri = None
        appears_on_statement_as = form.data.get('appears_on_statement_as')
        if not appears_on_statement_as:
            appears_on_statement_as = None
        started_at = form.data.get('started_at')

        sub_model = request.model_factory.create_subscription_model()
        plan_model = request.model_factory.create_plan_model()
        customer_model = request.model_factory.create_customer_model()
        tx_model = request.model_factory.create_transaction_model()

        customer = customer_model.get(customer_guid)
        if customer.company_guid != company.guid:
            return HTTPForbidden('Can only subscribe to your own customer')
        if customer.deleted:
            return HTTPBadRequest('Cannot subscript to a deleted customer')
        plan = plan_model.get(plan_guid)
        if plan.company_guid != company.guid:
            return HTTPForbidden('Can only subscribe to your own plan')
        if plan.deleted:
            return HTTPBadRequest('Cannot subscript to a deleted plan')

        if funding_instrument_uri is not None:
            processor = request.model_factory.create_processor()
            processor.configure_api_key(customer.company.processor_key)
            processor.validate_funding_instrument(funding_instrument_uri)

        # create subscription and yield transactions
        with db_transaction.manager:
            subscription = sub_model.create(
                customer=customer,
                plan=plan,
                amount=amount,
                funding_instrument_uri=funding_instrument_uri,
                appears_on_statement_as=appears_on_statement_as,
                started_at=started_at,
            )
            invoices = subscription.invoices
        # this is not a deferred subscription, just process transactions right away
        if started_at is None:
            with db_transaction.manager:
                tx_model.process_transactions(invoices[0].transactions)

        return subscription
Пример #7
0
def company_list_post(request):
    """Create a new company

    """
    form = validate_form(CompanyCreateForm, request)
    processor_key = form.data['processor_key']

    model = CompanyModel(request.session)
    # TODO: do validation here
    with db_transaction.manager:
        guid = model.create(processor_key=processor_key)
    company = model.get(guid)
    return company
Пример #8
0
def company_list_post(request):
    """Create a new company

    """
    form = validate_form(CompanyCreateForm, request)
    processor_key = form.data['processor_key']

    model = CompanyModel(request.session)
    # TODO: do validation here
    with db_transaction.manager:
        guid = model.create(processor_key=processor_key)
    company = model.get(guid)
    return company
Пример #9
0
def subscription_list_post(request):
    """Create a new subscription 

    """
    company = auth_api_key(request)
    form = validate_form(SubscriptionCreateForm, request)

    customer_guid = form.data['customer_guid']
    plan_guid = form.data['plan_guid']
    amount = form.data.get('amount')
    payment_uri = form.data.get('payment_uri')
    started_at = form.data.get('started_at')
    maximum_retry = int(request.registry.settings.get(
        'billy.transaction.maximum_retry', 
        TransactionModel.DEFAULT_MAXIMUM_RETRY,
    ))

    model = SubscriptionModel(request.session)
    plan_model = PlanModel(request.session)
    customer_model = CustomerModel(request.session)
    tx_model = TransactionModel(request.session)

    customer = customer_model.get(customer_guid)
    if customer.company_guid != company.guid:
        return HTTPForbidden('Can only subscribe to your own customer')
    plan = plan_model.get(plan_guid)
    if plan.company_guid != company.guid:
        return HTTPForbidden('Can only subscribe to your own plan')
    # TODO: make sure user cannot subscribe to a deleted plan or customer

    # create subscription and yield transactions
    with db_transaction.manager:
        guid = model.create(
            customer_guid=customer_guid, 
            plan_guid=plan_guid, 
            amount=amount, 
            payment_uri=payment_uri,
            started_at=started_at, 
        )
        tx_guids = model.yield_transactions([guid])
    # this is not a deferred subscription, just process transactions right away
    if started_at is None:
        with db_transaction.manager:
            tx_model.process_transactions(
                processor=request.processor, 
                guids=tx_guids,
                maximum_retry=maximum_retry,
            )

    subscription = model.get(guid)
    return subscription
Пример #10
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)
        form = validate_form(CustomerCreateForm, request)
       
        processor_uri = form.data.get('processor_uri')

        # TODO: make sure user cannot create a customer to a deleted company

        model = request.model_factory.create_customer_model()
        with db_transaction.manager:
            customer = model.create(
                processor_uri=processor_uri,
                company=company,
            )
        return customer
Пример #11
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)
        form = validate_form(CustomerCreateForm, request)

        processor_uri = form.data.get('processor_uri')

        # TODO: make sure user cannot create a customer to a deleted company

        model = request.model_factory.create_customer_model()
        with db_transaction.manager:
            customer = model.create(
                processor_uri=processor_uri,
                company=company,
            )
        return customer
Пример #12
0
    def post(self):
        request = self.request
        form = validate_form(CompanyCreateForm, request)
        processor_key = form.data['processor_key']

        def make_url(company):
            company_res = CompanyResource(request, company, self.context, company.guid)
            callback_index = CallbackIndex(company, request, company_res)
            callback = Callback(company, request, callback_index)
            return request.resource_url(callback, external=True)

        model = request.model_factory.create_company_model()
        with db_transaction.manager:
            company = model.create(
                processor_key=processor_key,
                make_callback_url=make_url,
            )
        return company
Пример #13
0
    def post(self):
        request = self.request
        form = validate_form(CompanyCreateForm, request)
        processor_key = form.data['processor_key']

        def make_url(company):
            company_res = CompanyResource(request, company, self.context,
                                          company.guid)
            callback_index = CallbackIndex(company, request, company_res)
            callback = Callback(company, request, callback_index)
            return request.resource_url(callback, external=True)

        model = request.model_factory.create_company_model()
        with db_transaction.manager:
            company = model.create(
                processor_key=processor_key,
                make_callback_url=make_url,
            )
        return company
Пример #14
0
def plan_list_post(request):
    """Create a new plan 

    """
    company = auth_api_key(request)
    form = validate_form(PlanCreateForm, request)

    plan_type = form.data['plan_type']
    amount = form.data['amount']
    frequency = form.data['frequency']
    interval = form.data['interval']
    if interval is None:
        interval = 1
    company_guid = company.guid

    # TODO: make sure user cannot create a post to a deleted company

    model = PlanModel(request.session)
    type_map = dict(
        charge=model.TYPE_CHARGE,
        payout=model.TYPE_PAYOUT,
    )
    plan_type = type_map[plan_type]
    freq_map = dict(
        daily=model.FREQ_DAILY,
        weekly=model.FREQ_WEEKLY,
        monthly=model.FREQ_MONTHLY,
        yearly=model.FREQ_YEARLY,
    )
    frequency = freq_map[frequency]

    with db_transaction.manager:
        guid = model.create(
            company_guid=company_guid,
            plan_type=plan_type,
            amount=amount,
            frequency=frequency,
            interval=interval,
        )
    plan = model.get(guid)
    return plan
Пример #15
0
def plan_list_post(request):
    """Create a new plan 

    """
    company = auth_api_key(request)
    form = validate_form(PlanCreateForm, request)
    
    plan_type = form.data['plan_type']
    amount = form.data['amount']
    frequency = form.data['frequency']
    interval = form.data['interval']
    if interval is None:
        interval = 1
    company_guid = company.guid

    # TODO: make sure user cannot create a post to a deleted company

    model = PlanModel(request.session)
    type_map = dict(
        charge=model.TYPE_CHARGE,
        payout=model.TYPE_PAYOUT,
    )
    plan_type = type_map[plan_type]
    freq_map = dict(
        daily=model.FREQ_DAILY,
        weekly=model.FREQ_WEEKLY,
        monthly=model.FREQ_MONTHLY,
        yearly=model.FREQ_YEARLY,
    )
    frequency = freq_map[frequency]

    with db_transaction.manager:
        guid = model.create(
            company_guid=company_guid, 
            plan_type=plan_type,
            amount=amount, 
            frequency=frequency, 
            interval=interval, 
        )
    plan = model.get(guid)
    return plan 
Пример #16
0
def customer_list_post(request):
    """Create a new customer 

    """
    company = auth_api_key(request)
    form = validate_form(CustomerCreateForm, request)
    
    external_id = form.data.get('external_id')
    company_guid = company.guid

    # TODO: make sure user cannot create a customer to a deleted company

    model = CustomerModel(request.session)
    # TODO: do validation here
    with db_transaction.manager:
        guid = model.create(
            external_id=external_id,
            company_guid=company_guid, 
        )
    customer = model.get(guid)
    return customer 
Пример #17
0
    def put(self):
        request = self.request

        invoice = self.context.entity
        form = validate_form(InvoiceUpdateForm, request)
        model = request.model_factory.create_invoice_model()
        tx_model = request.model_factory.create_transaction_model()

        funding_instrument_uri = form.data.get('funding_instrument_uri')

        with db_transaction.manager:
            transactions = model.update_funding_instrument_uri(
                invoice=invoice,
                funding_instrument_uri=funding_instrument_uri,
            )

        # funding_instrument_uri is set, just process all transactions right away
        if funding_instrument_uri and transactions:
            with db_transaction.manager:
                tx_model.process_transactions(transactions)

        return invoice
Пример #18
0
    def put(self):
        request = self.request

        invoice = self.context.entity
        form = validate_form(InvoiceUpdateForm, request)
        model = request.model_factory.create_invoice_model()
        tx_model = request.model_factory.create_transaction_model()

        funding_instrument_uri = form.data.get('funding_instrument_uri')
       
        with db_transaction.manager:
            transactions = model.update_funding_instrument_uri(
                invoice=invoice,
                funding_instrument_uri=funding_instrument_uri,
            )

        # funding_instrument_uri is set, just process all transactions right away
        if funding_instrument_uri and transactions:
            with db_transaction.manager:
                tx_model.process_transactions(transactions)

        return invoice
Пример #19
0
    def refund(self):
        """Issue a refund to customer

        """
        request = self.request
        invoice = self.context.entity
        form = validate_form(InvoiceRefundForm, request)
        invoice_model = request.model_factory.create_invoice_model()
        tx_model = request.model_factory.create_transaction_model()

        amount = form.data['amount']

        with db_transaction.manager:
            transactions = invoice_model.refund(
                invoice=invoice,
                amount=amount,
            )

        # funding_instrument_uri is set, just process all transactions right away
        if transactions:
            with db_transaction.manager:
                tx_model.process_transactions(transactions)
        return invoice
Пример #20
0
    def refund(self):
        """Issue a refund to customer

        """
        request = self.request
        invoice = self.context.entity
        form = validate_form(InvoiceRefundForm, request)
        invoice_model = request.model_factory.create_invoice_model()
        tx_model = request.model_factory.create_transaction_model()

        amount = form.data['amount']

        with db_transaction.manager:
            transactions = invoice_model.refund(
                invoice=invoice,
                amount=amount,
            )

        # funding_instrument_uri is set, just process all transactions right away
        if transactions:
            with db_transaction.manager:
                tx_model.process_transactions(transactions)
        return invoice
Пример #21
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)
        form = validate_form(PlanCreateForm, request)
       
        plan_type = form.data['plan_type']
        amount = form.data['amount']
        frequency = form.data['frequency']
        interval = form.data['interval']
        if interval is None:
            interval = 1

        # TODO: make sure user cannot create a post to a deleted company

        model = request.model_factory.create_plan_model()
        type_map = dict(
            charge=model.TYPE_CHARGE,
            payout=model.TYPE_PAYOUT,
        )
        plan_type = type_map[plan_type]
        freq_map = dict(
            daily=model.FREQ_DAILY,
            weekly=model.FREQ_WEEKLY,
            monthly=model.FREQ_MONTHLY,
            yearly=model.FREQ_YEARLY,
        )
        frequency = freq_map[frequency]

        with db_transaction.manager:
            plan = model.create(
                company=company,
                plan_type=plan_type,
                amount=amount,
                frequency=frequency,
                interval=interval,
            )
        return plan
Пример #22
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)
        form = validate_form(PlanCreateForm, request)

        plan_type = form.data['plan_type']
        amount = form.data['amount']
        frequency = form.data['frequency']
        interval = form.data['interval']
        if interval is None:
            interval = 1

        # TODO: make sure user cannot create a post to a deleted company

        model = request.model_factory.create_plan_model()
        with db_transaction.manager:
            plan = model.create(
                company=company,
                plan_type=plan_type,
                amount=amount,
                frequency=frequency,
                interval=interval,
            )
        return plan
Пример #23
0
    def post(self):
        request = self.request
        company = authenticated_userid(request)
        form = validate_form(PlanCreateForm, request)
       
        plan_type = form.data['plan_type']
        amount = form.data['amount']
        frequency = form.data['frequency']
        interval = form.data['interval']
        if interval is None:
            interval = 1

        # TODO: make sure user cannot create a post to a deleted company

        model = request.model_factory.create_plan_model()
        with db_transaction.manager:
            plan = model.create(
                company=company,
                plan_type=plan_type,
                amount=amount,
                frequency=frequency,
                interval=interval,
            )
        return plan
Пример #24
0
    def post(self):
        request = self.request

        form = validate_form(InvoiceCreateForm, request)
        model = request.model_factory.create_invoice_model()
        customer_model = request.model_factory.create_customer_model()
        tx_model = request.model_factory.create_transaction_model()
        company = authenticated_userid(request)

        customer_guid = form.data['customer_guid']
        amount = form.data['amount']
        funding_instrument_uri = form.data.get('funding_instrument_uri')
        if not funding_instrument_uri:
            funding_instrument_uri = None
        title = form.data.get('title')
        if not title:
            title = None
        external_id = form.data.get('external_id')
        if not external_id:
            external_id = None
        appears_on_statement_as = form.data.get('appears_on_statement_as')
        if not appears_on_statement_as:
            appears_on_statement_as = None
        items = parse_items(
            request=request,
            prefix='item_',
            keywords=('type', 'name', 'volume', 'amount', 'unit', 'quantity'),
        )
        if not items:
            items = None
        adjustments = parse_items(
            request=request,
            prefix='adjustment_',
            keywords=('amount', 'reason'),
        )
        if not adjustments:
            adjustments = None
        # TODO: what about negative effective amount?

        customer = customer_model.get(customer_guid)
        if customer.company != company:
            return HTTPForbidden(
                'Can only create an invoice for your own customer')
        if customer.deleted:
            return HTTPBadRequest(
                'Cannot create an invoice for a deleted customer')

        # Notice: I think it is better to validate the funding instrument URI
        # even before the record is created. Otherwse, the user can only knows
        # what's wrong after we try to submit it to the underlying processor.
        # (he can read the transaction failure log and eventually realize
        # the processing was failed)
        # The idea here is to advance error as early as possible.
        if funding_instrument_uri is not None:
            processor = request.model_factory.create_processor()
            processor.configure_api_key(customer.company.processor_key)
            processor.validate_funding_instrument(funding_instrument_uri)

        with db_transaction.manager:
            invoice = model.create(
                customer=customer,
                amount=amount,
                funding_instrument_uri=funding_instrument_uri,
                title=title,
                items=items,
                adjustments=adjustments,
                external_id=external_id,
                appears_on_statement_as=appears_on_statement_as,
            )
        # funding_instrument_uri is set, just process all transactions right away
        if funding_instrument_uri is not None:
            transactions = list(invoice.transactions)
            if transactions:
                with db_transaction.manager:
                    tx_model.process_transactions(transactions)
        return invoice
Пример #25
0
    def post(self):
        request = self.request

        form = validate_form(InvoiceCreateForm, request)
        model = request.model_factory.create_invoice_model()
        customer_model = request.model_factory.create_customer_model()
        tx_model = request.model_factory.create_transaction_model()
        company = authenticated_userid(request)
       
        customer_guid = form.data['customer_guid']
        amount = form.data['amount']
        funding_instrument_uri = form.data.get('funding_instrument_uri')
        if not funding_instrument_uri:
            funding_instrument_uri = None
        title = form.data.get('title')
        if not title:
            title = None
        external_id = form.data.get('external_id')
        if not external_id:
            external_id = None
        appears_on_statement_as = form.data.get('appears_on_statement_as')
        if not appears_on_statement_as:
            appears_on_statement_as = None
        items = parse_items(
            request=request,
            prefix='item_',
            keywords=('type', 'name', 'volume', 'amount', 'unit', 'quantity'),
        )
        if not items:
            items = None
        adjustments = parse_items(
            request=request,
            prefix='adjustment_',
            keywords=('amount', 'reason'),
        )
        if not adjustments:
            adjustments = None
        # TODO: what about negative effective amount?

        customer = customer_model.get(customer_guid)
        if customer.company != company:
            return HTTPForbidden('Can only create an invoice for your own customer')
        if customer.deleted:
            return HTTPBadRequest('Cannot create an invoice for a deleted customer')
       
        # Notice: I think it is better to validate the funding instrument URI
        # even before the record is created. Otherwse, the user can only knows
        # what's wrong after we try to submit it to the underlying processor.
        # (he can read the transaction failure log and eventually realize
        # the processing was failed)
        # The idea here is to advance error as early as possible.
        if funding_instrument_uri is not None:
            processor = request.model_factory.create_processor()
            processor.configure_api_key(customer.company.processor_key)
            processor.validate_funding_instrument(funding_instrument_uri)

        with db_transaction.manager:
            invoice = model.create(
                customer=customer,
                amount=amount,
                funding_instrument_uri=funding_instrument_uri,
                title=title,
                items=items,
                adjustments=adjustments,
                external_id=external_id,
                appears_on_statement_as=appears_on_statement_as,
            )
        # funding_instrument_uri is set, just process all transactions right away
        if funding_instrument_uri is not None:
            transactions = list(invoice.transactions)
            if transactions:
                with db_transaction.manager:
                    tx_model.process_transactions(transactions)
        return invoice