Example #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
Example #2
0
def get_and_check_customer(request, company):
    """Get and check permission to access a customer

    """
    model = CustomerModel(request.session)
    guid = request.matchdict['customer_guid']
    customer = model.get(guid)
    if customer is None:
        raise HTTPNotFound('No such customer {}'.format(guid))
    if customer.company_guid != company.guid:
        raise HTTPForbidden('You have no permission to access customer {}'
                            .format(guid))
    return customer 
Example #3
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
Example #4
0
def customer_get(request):
    """Get and return a customer 

    """
    company = auth_api_key(request)
    model = CustomerModel(request.session)
    guid = request.matchdict['customer_guid']
    customer = model.get(guid)
    if customer is None:
        return HTTPNotFound('No such customer {}'.format(guid))
    if customer.company_guid != company.guid:
        return HTTPForbidden('You have no permission to access customer {}'
                             .format(guid))
    return customer 
Example #5
0
def customer_delete(request):
    """Delete and return customer

    """
    company = auth_api_key(request)
    model = CustomerModel(request.session)
    customer = get_and_check_customer(request, company)
    if customer.deleted:
        return HTTPBadRequest('Customer {} was already deleted'
                              .format(customer.guid))
    with db_transaction.manager:
        model.delete(customer.guid)
    customer = model.get(customer.guid)
    return customer
Example #6
0
 def test_customer(self):
     from billy.models.customer import CustomerModel
     from billy.renderers import customer_adapter
     customer_model = CustomerModel(self.testapp.session)
     customer = customer_model.get(self.customer_guid)
     json_data = customer_adapter(customer, self.dummy_request)
     expected = dict(
         guid=customer.guid,
         external_id=customer.external_id, 
         created_at=customer.created_at.isoformat(),
         updated_at=customer.updated_at.isoformat(),
         company_guid=customer.company_guid, 
         deleted=customer.deleted, 
     )
     self.assertEqual(json_data, expected)
Example #7
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 
Example #8
0
class TestBalancedProcessorModel(ModelTestCase):

    def setUp(self):
        from billy.models.company import CompanyModel
        from billy.models.customer import CustomerModel
        from billy.models.plan import PlanModel
        from billy.models.subscription import SubscriptionModel
        from billy.models.transaction import TransactionModel
        super(TestBalancedProcessorModel, self).setUp()
        # build the basic scenario for transaction model
        self.company_model = CompanyModel(self.session)
        self.customer_model = CustomerModel(self.session)
        self.plan_model = PlanModel(self.session)
        self.subscription_model = SubscriptionModel(self.session)
        self.transaction_model = TransactionModel(self.session)
        with db_transaction.manager:
            self.company_guid = self.company_model.create('my_secret_key')
            self.plan_guid = self.plan_model.create(
                company_guid=self.company_guid,
                plan_type=self.plan_model.TYPE_CHARGE,
                amount=10,
                frequency=self.plan_model.FREQ_MONTHLY,
            )
            self.customer_guid = self.customer_model.create(
                company_guid=self.company_guid,
            )
            self.subscription_guid = self.subscription_model.create(
                customer_guid=self.customer_guid,
                plan_guid=self.plan_guid,
                payment_uri='/v1/credit_card/tester',
            )

    def make_one(self, *args, **kwargs):
        from billy.models.processors.balanced_payments import BalancedProcessor
        return BalancedProcessor(*args, **kwargs)

    def test_create_customer(self):
        import balanced

        customer = self.customer_model.get(self.customer_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock balanced customer instance
        mock_balanced_customer = (
            flexmock(uri='MOCK_BALANCED_CUSTOMER_URI')
            .should_receive('save')
            .replace_with(lambda: mock_balanced_customer)
            .once()
            .mock()
        )

        class BalancedCustomer(object):
            pass
        flexmock(BalancedCustomer).new_instances(mock_balanced_customer) 

        processor = self.make_one(customer_cls=BalancedCustomer)
        customer_id = processor.create_customer(customer)
        self.assertEqual(customer_id, 'MOCK_BALANCED_CUSTOMER_URI')

    def test_prepare_customer_with_card(self):
        import balanced

        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock balanced.Customer instance
        mock_balanced_customer = (
            flexmock()
            .should_receive('add_card')
            .with_args('/v1/cards/my_card')
            .once()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        processor.prepare_customer(customer, '/v1/cards/my_card')

    def test_prepare_customer_with_bank_account(self):
        import balanced

        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock balanced.Customer instance
        mock_balanced_customer = (
            flexmock()
            .should_receive('add_bank_account')
            .with_args('/v1/bank_accounts/my_account')
            .once()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        processor.prepare_customer(customer, '/v1/bank_accounts/my_account')

    def test_prepare_customer_with_none_payment_uri(self):
        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # mock balanced.Customer instance
        mock_balanced_customer = (
            flexmock()
            .should_receive('add_bank_account')
            .never()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .never()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        processor.prepare_customer(customer, None)

    def test_prepare_customer_with_bad_payment_uri(self):
        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # mock balanced.Customer instance
        mock_balanced_customer = flexmock()

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        with self.assertRaises(ValueError):
            processor.prepare_customer(customer, '/v1/bitcoin/12345')

    def _test_operation(
        self, 
        cls_name, 
        processor_method_name, 
        api_method_name,
        extra_api_kwargs,
    ):
        import balanced

        tx_model = self.transaction_model
        with db_transaction.manager:
            guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=10,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            transaction = tx_model.get(guid)
            self.customer_model.update(
                guid=transaction.subscription.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        transaction = tx_model.get(guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock result page object of balanced.RESOURCE.query.filter(...)

        def mock_one():
            raise balanced.exc.NoResultFound

        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(mock_one)
            .once()
            .mock()
        )

        # mock balanced.RESOURCE.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.RESOURCE class
        class Resource(object): 
            pass
        Resource.query = mock_query

        # mock balanced.RESOURCE instance
        mock_resource = flexmock(uri='MOCK_BALANCED_RESOURCE_URI')

        # mock balanced.Customer instance
        kwargs = dict(
            amount=transaction.amount,
            meta={'billy.transaction_guid': transaction.guid},
            description=(
                'Generated by Billy from subscription {}, scheduled_at={}'
                .format(transaction.subscription.guid, transaction.scheduled_at)
            )
        )
        kwargs.update(extra_api_kwargs)
        mock_balanced_customer = (
            flexmock()
            .should_receive(api_method_name)
            .with_args(**kwargs)
            .replace_with(lambda **kw: mock_resource)
            .once()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(
            customer_cls=BalancedCustomer, 
            **{cls_name: Resource}
        )
        method = getattr(processor, processor_method_name)
        balanced_tx_id = method(transaction)
        self.assertEqual(balanced_tx_id, 'MOCK_BALANCED_RESOURCE_URI')

    def _test_operation_with_created_record(
        self, 
        cls_name, 
        processor_method_name,
    ):
        tx_model = self.transaction_model
        with db_transaction.manager:
            guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=10,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            transaction = tx_model.get(guid)
        transaction = tx_model.get(guid)

        # mock balanced.RESOURCE instance
        mock_resource = flexmock(uri='MOCK_BALANCED_RESOURCE_URI')

        # mock result page object of balanced.RESOURCE.query.filter(...)
        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(lambda: mock_resource)
            .once()
            .mock()
        )

        # mock balanced.RESOURCE.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.RESOURCE class
        class Resource(object): 
            pass
        Resource.query = mock_query

        processor = self.make_one(**{cls_name: Resource})
        method = getattr(processor, processor_method_name)
        balanced_res_uri = method(transaction)
        self.assertEqual(balanced_res_uri, 'MOCK_BALANCED_RESOURCE_URI')

    def test_charge(self):
        self._test_operation(
            cls_name='debit_cls', 
            processor_method_name='charge',
            api_method_name='debit',
            extra_api_kwargs=dict(source_uri='/v1/credit_card/tester'),
        )

    def test_charge_with_created_record(self):
        self._test_operation_with_created_record(
            cls_name='debit_cls',
            processor_method_name='charge',
        )

    def test_payout(self):
        self._test_operation(
            cls_name='credit_cls', 
            processor_method_name='payout',
            api_method_name='credit',
            extra_api_kwargs=dict(destination_uri='/v1/credit_card/tester'),
        )

    def test_payout_with_created_record(self):
        self._test_operation_with_created_record(
            cls_name='credit_cls',
            processor_method_name='payout',
        )

    def test_refund(self):
        import balanced

        tx_model = self.transaction_model
        with db_transaction.manager:
            charge_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=100,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            charge_transaction = tx_model.get(charge_guid)
            charge_transaction.status = tx_model.STATUS_DONE
            charge_transaction.external_id = 'MOCK_BALANCED_DEBIT_URI'
            self.session.add(charge_transaction)
            self.session.flush()

            refund_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_REFUND,
                refund_to_guid=charge_guid,
                amount=56,
                scheduled_at=datetime.datetime.utcnow(),
            )

        transaction = tx_model.get(refund_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock result page object of balanced.Refund.query.filter(...)

        def mock_one():
            raise balanced.exc.NoResultFound

        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(mock_one)
            .once()
            .mock()
        )

        # mock balanced.Refund.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.Refund class
        class Refund(object): 
            pass
        Refund.query = mock_query

        # mock balanced.Refund instance
        mock_refund = flexmock(uri='MOCK_BALANCED_REFUND_URI')

        # mock balanced.Debit instance
        kwargs = dict(
            amount=transaction.amount,
            meta={'billy.transaction_guid': transaction.guid},
            description=(
                'Generated by Billy from subscription {}, scheduled_at={}'
                .format(transaction.subscription.guid, transaction.scheduled_at)
            )
        )
        mock_balanced_debit = (
            flexmock()
            .should_receive('refund')
            .with_args(**kwargs)
            .replace_with(lambda **kw: mock_refund)
            .once()
            .mock()
        )

        # mock balanced.Debit class
        class BalancedDebit(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedDebit)
            .should_receive('find')
            .with_args('MOCK_BALANCED_DEBIT_URI')
            .replace_with(lambda _: mock_balanced_debit)
            .once()
        )

        processor = self.make_one(
            refund_cls=Refund,
            debit_cls=BalancedDebit,
        )
        refund_uri = processor.refund(transaction)
        self.assertEqual(refund_uri, 'MOCK_BALANCED_REFUND_URI')

    def test_refund_with_created_record(self):
        tx_model = self.transaction_model
        with db_transaction.manager:
            charge_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=100,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            charge_transaction = tx_model.get(charge_guid)
            charge_transaction.status = tx_model.STATUS_DONE
            charge_transaction.external_id = 'MOCK_BALANCED_DEBIT_URI'
            self.session.add(charge_transaction)
            self.session.flush()

            refund_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_REFUND,
                refund_to_guid=charge_guid,
                amount=56,
                scheduled_at=datetime.datetime.utcnow(),
            )

        transaction = tx_model.get(refund_guid)

        # mock balanced.Refund instance
        mock_refund = flexmock(uri='MOCK_BALANCED_REFUND_URI')

        # mock result page object of balanced.Refund.query.filter(...)

        def mock_one():
            return mock_refund

        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(mock_one)
            .once()
            .mock()
        )

        # mock balanced.Refund.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.Refund class
        class Refund(object): 
            pass
        Refund.query = mock_query

        processor = self.make_one(refund_cls=Refund)
        refund_uri = processor.refund(transaction)
        self.assertEqual(refund_uri, 'MOCK_BALANCED_REFUND_URI')
Example #9
0
class TestBalancedProcessorModel(ModelTestCase):

    def setUp(self):
        from billy.models.company import CompanyModel
        from billy.models.customer import CustomerModel
        from billy.models.plan import PlanModel
        from billy.models.subscription import SubscriptionModel
        from billy.models.transaction import TransactionModel
        super(TestBalancedProcessorModel, self).setUp()
        # build the basic scenario for transaction model
        self.company_model = CompanyModel(self.session)
        self.customer_model = CustomerModel(self.session)
        self.plan_model = PlanModel(self.session)
        self.subscription_model = SubscriptionModel(self.session)
        self.transaction_model = TransactionModel(self.session)
        with db_transaction.manager:
            self.company_guid = self.company_model.create('my_secret_key')
            self.plan_guid = self.plan_model.create(
                company_guid=self.company_guid,
                plan_type=self.plan_model.TYPE_CHARGE,
                amount=10,
                frequency=self.plan_model.FREQ_MONTHLY,
            )
            self.customer_guid = self.customer_model.create(
                company_guid=self.company_guid,
            )
            self.subscription_guid = self.subscription_model.create(
                customer_guid=self.customer_guid,
                plan_guid=self.plan_guid,
                payment_uri='/v1/credit_card/tester',
            )

    def make_one(self, *args, **kwargs):
        from billy.models.processors.balanced_payments import BalancedProcessor
        return BalancedProcessor(*args, **kwargs)

    def test_create_customer(self):
        import balanced

        customer = self.customer_model.get(self.customer_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock balanced customer instance
        mock_balanced_customer = (
            flexmock(uri='MOCK_BALANCED_CUSTOMER_URI')
            .should_receive('save')
            .replace_with(lambda: mock_balanced_customer)
            .once()
            .mock()
        )

        class BalancedCustomer(object):
            pass
        flexmock(BalancedCustomer).new_instances(mock_balanced_customer) 

        processor = self.make_one(customer_cls=BalancedCustomer)
        customer_id = processor.create_customer(customer)
        self.assertEqual(customer_id, 'MOCK_BALANCED_CUSTOMER_URI')

    def test_prepare_customer_with_card(self):
        import balanced

        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock balanced.Customer instance
        mock_balanced_customer = (
            flexmock()
            .should_receive('add_card')
            .with_args('/v1/cards/my_card')
            .once()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        processor.prepare_customer(customer, '/v1/cards/my_card')

    def test_prepare_customer_with_bank_account(self):
        import balanced

        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock balanced.Customer instance
        mock_balanced_customer = (
            flexmock()
            .should_receive('add_bank_account')
            .with_args('/v1/bank_accounts/my_account')
            .once()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        processor.prepare_customer(customer, '/v1/bank_accounts/my_account')

    def test_prepare_customer_with_none_payment_uri(self):
        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # mock balanced.Customer instance
        mock_balanced_customer = (
            flexmock()
            .should_receive('add_bank_account')
            .never()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .never()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        processor.prepare_customer(customer, None)

    def test_prepare_customer_with_bad_payment_uri(self):
        with db_transaction.manager:
            self.customer_model.update(
                guid=self.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        customer = self.customer_model.get(self.customer_guid)

        # mock balanced.Customer instance
        mock_balanced_customer = flexmock()

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(customer_cls=BalancedCustomer)
        with self.assertRaises(ValueError):
            processor.prepare_customer(customer, '/v1/bitcoin/12345')

    def _test_operation(
        self, 
        cls_name, 
        processor_method_name, 
        api_method_name,
        extra_api_kwargs,
    ):
        import balanced

        tx_model = self.transaction_model
        with db_transaction.manager:
            guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=10,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            transaction = tx_model.get(guid)
            self.customer_model.update(
                guid=transaction.subscription.customer_guid,
                external_id='MOCK_BALANCED_CUSTOMER_URI',
            )
        transaction = tx_model.get(guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock result page object of balanced.RESOURCE.query.filter(...)

        def mock_one():
            raise balanced.exc.NoResultFound

        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(mock_one)
            .once()
            .mock()
        )

        # mock balanced.RESOURCE.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.RESOURCE class
        class Resource(object): 
            pass
        Resource.query = mock_query

        # mock balanced.RESOURCE instance
        mock_resource = flexmock(uri='MOCK_BALANCED_RESOURCE_URI')

        # mock balanced.Customer instance
        kwargs = dict(
            amount=int(transaction.amount * 100),
            meta={'billy.transaction_guid': transaction.guid},
            description=(
                'Generated by Billy from subscription {}, scheduled_at={}'
                .format(transaction.subscription.guid, transaction.scheduled_at)
            )
        )
        kwargs.update(extra_api_kwargs)
        mock_balanced_customer = (
            flexmock()
            .should_receive(api_method_name)
            .with_args(**kwargs)
            .replace_with(lambda **kw: mock_resource)
            .once()
            .mock()
        )

        # mock balanced.Customer class
        class BalancedCustomer(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedCustomer)
            .should_receive('find')
            .with_args('MOCK_BALANCED_CUSTOMER_URI')
            .replace_with(lambda _: mock_balanced_customer)
            .once()
        )

        processor = self.make_one(
            customer_cls=BalancedCustomer, 
            **{cls_name: Resource}
        )
        method = getattr(processor, processor_method_name)
        balanced_tx_id = method(transaction)
        self.assertEqual(balanced_tx_id, 'MOCK_BALANCED_RESOURCE_URI')

    def _test_operation_with_created_record(
        self, 
        cls_name, 
        processor_method_name,
    ):
        tx_model = self.transaction_model
        with db_transaction.manager:
            guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=10,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            transaction = tx_model.get(guid)
        transaction = tx_model.get(guid)

        # mock balanced.RESOURCE instance
        mock_resource = flexmock(uri='MOCK_BALANCED_RESOURCE_URI')

        # mock result page object of balanced.RESOURCE.query.filter(...)
        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(lambda: mock_resource)
            .once()
            .mock()
        )

        # mock balanced.RESOURCE.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.RESOURCE class
        class Resource(object): 
            pass
        Resource.query = mock_query

        processor = self.make_one(**{cls_name: Resource})
        method = getattr(processor, processor_method_name)
        balanced_res_uri = method(transaction)
        self.assertEqual(balanced_res_uri, 'MOCK_BALANCED_RESOURCE_URI')

    def test_charge(self):
        self._test_operation(
            cls_name='debit_cls', 
            processor_method_name='charge',
            api_method_name='debit',
            extra_api_kwargs=dict(source_uri='/v1/credit_card/tester'),
        )

    def test_charge_with_created_record(self):
        self._test_operation_with_created_record(
            cls_name='debit_cls',
            processor_method_name='charge',
        )

    def test_payout(self):
        self._test_operation(
            cls_name='credit_cls', 
            processor_method_name='payout',
            api_method_name='credit',
            extra_api_kwargs=dict(destination_uri='/v1/credit_card/tester'),
        )

    def test_payout_with_created_record(self):
        self._test_operation_with_created_record(
            cls_name='credit_cls',
            processor_method_name='payout',
        )

    def test_refund(self):
        import balanced

        tx_model = self.transaction_model
        with db_transaction.manager:
            charge_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=100,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            charge_transaction = tx_model.get(charge_guid)
            charge_transaction.status = tx_model.STATUS_DONE
            charge_transaction.external_id = 'MOCK_BALANCED_DEBIT_URI'
            self.session.add(charge_transaction)
            self.session.flush()

            refund_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_REFUND,
                refund_to_guid=charge_guid,
                amount=56,
                scheduled_at=datetime.datetime.utcnow(),
            )

        transaction = tx_model.get(refund_guid)

        # make sure API key is set correctly
        (
            flexmock(balanced)
            .should_receive('configure')
            .with_args('my_secret_key')
            .once()
        )

        # mock result page object of balanced.Refund.query.filter(...)

        def mock_one():
            raise balanced.exc.NoResultFound

        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(mock_one)
            .once()
            .mock()
        )

        # mock balanced.Refund.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.Refund class
        class Refund(object): 
            pass
        Refund.query = mock_query

        # mock balanced.Refund instance
        mock_refund = flexmock(uri='MOCK_BALANCED_REFUND_URI')

        # mock balanced.Debit instance
        kwargs = dict(
            amount=int(transaction.amount * 100),
            meta={'billy.transaction_guid': transaction.guid},
            description=(
                'Generated by Billy from subscription {}, scheduled_at={}'
                .format(transaction.subscription.guid, transaction.scheduled_at)
            )
        )
        mock_balanced_debit = (
            flexmock()
            .should_receive('refund')
            .with_args(**kwargs)
            .replace_with(lambda **kw: mock_refund)
            .once()
            .mock()
        )

        # mock balanced.Debit class
        class BalancedDebit(object): 
            def find(self, uri):
                pass
        (
            flexmock(BalancedDebit)
            .should_receive('find')
            .with_args('MOCK_BALANCED_DEBIT_URI')
            .replace_with(lambda _: mock_balanced_debit)
            .once()
        )

        processor = self.make_one(
            refund_cls=Refund,
            debit_cls=BalancedDebit,
        )
        refund_uri = processor.refund(transaction)
        self.assertEqual(refund_uri, 'MOCK_BALANCED_REFUND_URI')

    def test_refund_with_created_record(self):
        tx_model = self.transaction_model
        with db_transaction.manager:
            charge_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_CHARGE,
                amount=100,
                payment_uri='/v1/credit_card/tester',
                scheduled_at=datetime.datetime.utcnow(),
            )
            charge_transaction = tx_model.get(charge_guid)
            charge_transaction.status = tx_model.STATUS_DONE
            charge_transaction.external_id = 'MOCK_BALANCED_DEBIT_URI'
            self.session.add(charge_transaction)
            self.session.flush()

            refund_guid = tx_model.create(
                subscription_guid=self.subscription_guid,
                transaction_type=tx_model.TYPE_REFUND,
                refund_to_guid=charge_guid,
                amount=56,
                scheduled_at=datetime.datetime.utcnow(),
            )

        transaction = tx_model.get(refund_guid)

        # mock balanced.Refund instance
        mock_refund = flexmock(uri='MOCK_BALANCED_REFUND_URI')

        # mock result page object of balanced.Refund.query.filter(...)

        def mock_one():
            return mock_refund

        mock_page = (
            flexmock()
            .should_receive('one')
            .replace_with(mock_one)
            .once()
            .mock()
        )

        # mock balanced.Refund.query
        mock_query = (
            flexmock()
            .should_receive('filter')
            .with_args(**{'meta.billy.transaction_guid': transaction.guid})
            .replace_with(lambda **kw: mock_page)
            .mock()
        )

        # mock balanced.Refund class
        class Refund(object): 
            pass
        Refund.query = mock_query

        processor = self.make_one(refund_cls=Refund)
        refund_uri = processor.refund(transaction)
        self.assertEqual(refund_uri, 'MOCK_BALANCED_REFUND_URI')