def test_subscription_cancel_with_refund_amount(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # let's cancel and refund the latest transaction with amount 566 cent with db_transaction.manager: refund_guid = model.cancel(guid, refund_amount=566) transaction = tx_model.get(refund_guid) self.assertEqual(transaction.refund_to_guid, tx_guids[0]) self.assertEqual(transaction.subscription_guid, guid) self.assertEqual(transaction.transaction_type, tx_model.TYPE_REFUND) self.assertEqual(transaction.amount, 566)
def test_subscription_cancel_with_prorated_refund_rounding(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with freeze_time('2013-06-01'): with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # 17 / 30 days, the rate should be 1 - 0.56666..., which is # 0.43333... with freeze_time('2013-06-18'): with db_transaction.manager: refund_guid = model.cancel(guid, prorated_refund=True) transaction = tx_model.get(refund_guid) self.assertEqual(transaction.amount, decimal.Decimal('4.33'))
def test_subscription_cancel_with_prorated_refund_and_amount_overwrite(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with freeze_time('2013-06-01'): with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, amount=10000, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # it is a monthly plan, there is 30 days in June, and only # 6 days are elapsed, so 6 / 30 days, the rate should be 1 - 0.2 = 0.8 # and we have 100 dollars as the amount, we should return 80 dollars to # customer with freeze_time('2013-06-07'): with db_transaction.manager: refund_guid = model.cancel(guid, prorated_refund=True) transaction = tx_model.get(refund_guid) self.assertEqual(transaction.amount, 8000)
def test_subscription_cancel_with_prorated_refund_rounding(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with freeze_time('2013-06-01'): with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # 17 / 30 days, the rate should be 1 - 0.56666..., which is # 0.43333... with freeze_time('2013-06-18'): with db_transaction.manager: refund_guid = model.cancel(guid, prorated_refund=True) transaction = tx_model.get(refund_guid) self.assertEqual(transaction.amount, 433)
def test_subscription_cancel_with_refund_amount(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # let's cancel and refund the latest transaction with amount 5.66 with db_transaction.manager: refund_guid = model.cancel(guid, refund_amount=5.66) transaction = tx_model.get(refund_guid) self.assertEqual(transaction.refund_to_guid, tx_guids[0]) self.assertEqual(transaction.subscription_guid, guid) self.assertEqual(transaction.transaction_type, tx_model.TYPE_REFUND) self.assertEqual(transaction.amount, decimal.Decimal('5.66'))
def test_subscription_cancel_with_prorated_refund_and_amount_overwrite( self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with freeze_time('2013-06-01'): with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, amount=100, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # it is a monthly plan, there is 30 days in June, and only # 6 days are elapsed, so 6 / 30 days, the rate should be 1 - 0.2 = 0.8 # and we have 100 as the amount, we should return 80 to customer with freeze_time('2013-06-07'): with db_transaction.manager: refund_guid = model.cancel(guid, prorated_refund=True) transaction = tx_model.get(refund_guid) # the orignal price is 10, then overwritten by subscription as 100 # and we refund half, then the refund amount should be 50 self.assertEqual(transaction.amount, decimal.Decimal('80'))
def test_subscription_cancel_with_prorated_refund(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with freeze_time('2013-06-01'): with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # it is a monthly plan, there is 30 days in June, and only # 6 days are elapsed, so 6 / 30 days, the rate should be 1 - 0.2 = 0.8 # and we have 10 as the amount, we should return 8 to customer with freeze_time('2013-06-07'): with db_transaction.manager: refund_guid = model.cancel(guid, prorated_refund=True) transaction = tx_model.get(refund_guid) self.assertEqual(transaction.refund_to_guid, tx_guids[0]) self.assertEqual(transaction.subscription_guid, guid) self.assertEqual(transaction.transaction_type, tx_model.TYPE_REFUND) self.assertEqual(transaction.amount, decimal.Decimal('8'))
def test_get_transaction(self): from billy.models.transaction import TransactionModel transaction_model = TransactionModel(self.testapp.session) res = self.testapp.get( '/v1/transactions/{}'.format(self.transaction_guid), extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) transaction = transaction_model.get(self.transaction_guid) self.assertEqual(res.json['guid'], transaction.guid) self.assertEqual(res.json['created_at'], transaction.created_at.isoformat()) self.assertEqual(res.json['updated_at'], transaction.updated_at.isoformat()) self.assertEqual(res.json['scheduled_at'], transaction.scheduled_at.isoformat()) self.assertEqual(res.json['amount'], str(transaction.amount)) self.assertEqual(res.json['payment_uri'], transaction.payment_uri) self.assertEqual(res.json['transaction_type'], 'charge') self.assertEqual(res.json['status'], 'init') self.assertEqual(res.json['error_message'], None) self.assertEqual(res.json['failure_count'], 0) self.assertEqual(res.json['external_id'], None) self.assertEqual(res.json['subscription_guid'], transaction.subscription_guid)
def test_yield_transactions_for_specific_subscriptions(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid1 = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) guid2 = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions([guid1, guid2]) self.assertEqual(len(tx_guids), 2) subscription_guids = [ tx_model.get(tx_guid).subscription_guid for tx_guid in tx_guids ] self.assertEqual(set(subscription_guids), set([guid1, guid2]))
def test_yield_transactions_for_specific_subscriptions(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid1 = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) guid2 = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions([guid1, guid2]) self.assertEqual(len(tx_guids), 2) subscription_guids = [tx_model.get(tx_guid).subscription_guid for tx_guid in tx_guids] self.assertEqual(set(subscription_guids), set([guid1, guid2]))
def test_subscription_cancel_not_done_transactions(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) # okay, 08-16, 09-16, 10-16, 11-16, so we should have 4 new transactions # and we assume the transactions status as shown as following: # # [DONE, RETRYING, INIT, FAILED] # # when we cancel the subscription, the status should be # # [DONE, CANCELED, CANCELED, FAILED] # init_status = [ tx_model.STATUS_DONE, tx_model.STATUS_RETRYING, tx_model.STATUS_INIT, tx_model.STATUS_FAILED, ] with freeze_time('2013-11-16'): with db_transaction.manager: tx_guids = model.yield_transactions() for tx_guid, status in zip(tx_guids, init_status): transaction = tx_model.get(tx_guid) transaction.status = status self.session.add(transaction) self.session.add(transaction) with db_transaction.manager: model.cancel(guid) transactions = [tx_model.get(tx_guid) for tx_guid in tx_guids] status_list = [tx.status for tx in transactions] self.assertEqual(status_list, [ tx_model.STATUS_DONE, tx_model.STATUS_CANCELED, tx_model.STATUS_CANCELED, tx_model.STATUS_FAILED, ])
def test_cancel_subscription_with_refund_amount(self): from billy.models.subscription import SubscriptionModel from billy.models.transaction import TransactionModel subscription_model = SubscriptionModel(self.testapp.session) tx_model = TransactionModel(self.testapp.session) now = datetime.datetime.utcnow() with db_transaction.manager: subscription_guid = subscription_model.create(customer_guid=self.customer_guid, plan_guid=self.plan_guid) tx_guid = tx_model.create( subscription_guid=subscription_guid, transaction_type=tx_model.TYPE_CHARGE, amount=1000, scheduled_at=now, ) subscription = subscription_model.get(subscription_guid) subscription.period = 1 subscription.next_transaction_at = datetime.datetime(2013, 8, 23) self.testapp.session.add(subscription) transaction = tx_model.get(tx_guid) transaction.status = tx_model.STATUS_DONE transaction.external_id = "MOCK_BALANCED_DEBIT_URI" self.testapp.session.add(transaction) refund_called = [] def mock_refund(transaction): refund_called.append(transaction) return "MOCK_PROCESSOR_REFUND_URI" mock_processor = flexmock(DummyProcessor) (mock_processor.should_receive("refund").replace_with(mock_refund).once()) res = self.testapp.post( "/v1/subscriptions/{}/cancel".format(subscription_guid), dict(refund_amount=234), extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) subscription = res.json transaction = refund_called[0] self.testapp.session.add(transaction) self.assertEqual(transaction.refund_to.guid, tx_guid) self.assertEqual(transaction.subscription_guid, subscription_guid) self.assertEqual(transaction.amount, 234) self.assertEqual(transaction.status, tx_model.STATUS_DONE) res = self.testapp.get("/v1/transactions", extra_environ=dict(REMOTE_USER=self.api_key), status=200) guids = [item["guid"] for item in res.json["items"]] self.assertEqual(set(guids), set([tx_guid, transaction.guid]))
def transaction_get(request): """Get and return a transaction """ company = auth_api_key(request) model = TransactionModel(request.session) guid = request.matchdict['transaction_guid'] transaction = model.get(guid) if transaction is None: return HTTPNotFound('No such transaction {}'.format(guid)) if transaction.subscription.customer.company_guid != company.guid: return HTTPForbidden('You have no permission to access transaction {}' .format(guid)) return transaction
def transaction_get(request): """Get and return a transaction """ company = auth_api_key(request) model = TransactionModel(request.session) guid = request.matchdict['transaction_guid'] transaction = model.get(guid) if transaction is None: return HTTPNotFound('No such transaction {}'.format(guid)) if transaction.subscription.customer.company_guid != company.guid: return HTTPForbidden( 'You have no permission to access transaction {}'.format(guid)) return transaction
def test_server_info_with_transaction(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 company_model = CompanyModel(self.testapp.session) customer_model = CustomerModel(self.testapp.session) plan_model = PlanModel(self.testapp.session) subscription_model = SubscriptionModel(self.testapp.session) transaction_model = TransactionModel(self.testapp.session) with db_transaction.manager: company_guid = company_model.create( processor_key='MOCK_PROCESSOR_KEY', ) customer_guid = customer_model.create( company_guid=company_guid ) plan_guid = plan_model.create( company_guid=company_guid, frequency=plan_model.FREQ_WEEKLY, plan_type=plan_model.TYPE_CHARGE, amount=10, ) subscription_guid = subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, ) transaction_guid = transaction_model.create( subscription_guid=subscription_guid, transaction_type=transaction_model.TYPE_CHARGE, amount=10, payment_uri='/v1/cards/tester', scheduled_at=datetime.datetime.utcnow(), ) transaction = transaction_model.get(transaction_guid) res = self.testapp.get('/', status=200) self.assertEqual(res.json['last_transaction_created_at'], transaction.created_at.isoformat())
def test_transaction(self): from billy.models.transaction import TransactionModel from billy.renderers import transaction_adapter transaction_model = TransactionModel(self.testapp.session) transaction = transaction_model.get(self.transaction_guid) json_data = transaction_adapter(transaction, self.dummy_request) expected = dict( guid=transaction.guid, transaction_type='charge', status='init', amount=transaction.amount, payment_uri=transaction.payment_uri, external_id=transaction.external_id, failure_count=transaction.failure_count, error_message=transaction.error_message, created_at=transaction.created_at.isoformat(), updated_at=transaction.updated_at.isoformat(), scheduled_at=transaction.scheduled_at.isoformat(), subscription_guid=transaction.subscription_guid, ) self.assertEqual(json_data, expected) def assert_type(transaction_type, expected_type): transaction.transaction_type = transaction_type json_data = transaction_adapter(transaction, self.dummy_request) self.assertEqual(json_data['transaction_type'], expected_type) assert_type(TransactionModel.TYPE_CHARGE, 'charge') assert_type(TransactionModel.TYPE_PAYOUT, 'payout') assert_type(TransactionModel.TYPE_REFUND, 'refund') def assert_status(transaction_status, expected_status): transaction.status = transaction_status json_data = transaction_adapter(transaction, self.dummy_request) self.assertEqual(json_data['status'], expected_status) assert_status(TransactionModel.STATUS_INIT, 'init') assert_status(TransactionModel.STATUS_RETRYING, 'retrying') assert_status(TransactionModel.STATUS_FAILED, 'failed') assert_status(TransactionModel.STATUS_DONE, 'done') assert_status(TransactionModel.STATUS_CANCELED, 'canceled')
def test_cancel_subscription_with_bad_arguments(self): from billy.models.subscription import SubscriptionModel from billy.models.transaction import TransactionModel subscription_model = SubscriptionModel(self.testapp.session) tx_model = TransactionModel(self.testapp.session) now = datetime.datetime.utcnow() with db_transaction.manager: subscription_guid = subscription_model.create( customer_guid=self.customer_guid, plan_guid=self.plan_guid, amount=100, ) tx_guid = tx_model.create( subscription_guid=subscription_guid, transaction_type=tx_model.TYPE_CHARGE, amount=100, scheduled_at=now, ) subscription = subscription_model.get(subscription_guid) subscription.period = 1 subscription.next_transaction_at = datetime.datetime(2013, 8, 23) self.testapp.session.add(subscription) transaction = tx_model.get(tx_guid) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.testapp.session.add(transaction) def assert_bad_parameters(kwargs): self.testapp.post( '/v1/subscriptions/{}/cancel'.format(subscription_guid), kwargs, extra_environ=dict(REMOTE_USER=self.api_key), status=400, ) assert_bad_parameters(dict(prorated_refund=True, refund_amount=10)) assert_bad_parameters(dict(refund_amount='100.01'))
def test_subscription_cancel_with_wrong_arguments(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # we should not allow both prorated_refund and refund_amount to # be set with self.assertRaises(ValueError): model.cancel(guid, prorated_refund=True, refund_amount=10) # we should not allow refunding amount that grather than original # subscription amount with self.assertRaises(ValueError): model.cancel(guid, refund_amount=decimal.Decimal('10.01'))
def test_subscription_cancel_with_wrong_arguments(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() transaction = tx_model.get(tx_guids[0]) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.session.add(transaction) # we should not allow both prorated_refund and refund_amount to # be set with self.assertRaises(ValueError): model.cancel(guid, prorated_refund=True, refund_amount=10) # we should not allow refunding amount that grather than original # subscription amount with self.assertRaises(ValueError): model.cancel(guid, refund_amount=1001)
def test_cancel_subscription_with_prorated_refund(self): from billy.models.subscription import SubscriptionModel from billy.models.transaction import TransactionModel subscription_model = SubscriptionModel(self.testapp.session) tx_model = TransactionModel(self.testapp.session) now = datetime.datetime.utcnow() with db_transaction.manager: subscription_guid = subscription_model.create( customer_guid=self.customer_guid, plan_guid=self.plan_guid, amount=100, ) tx_guid = tx_model.create( subscription_guid=subscription_guid, transaction_type=tx_model.TYPE_CHARGE, amount=100, scheduled_at=now, ) subscription = subscription_model.get(subscription_guid) subscription.period = 1 subscription.next_transaction_at = datetime.datetime(2013, 8, 23) self.testapp.session.add(subscription) transaction = tx_model.get(tx_guid) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.testapp.session.add(transaction) refund_called = [] def mock_refund(transaction): refund_called.append(transaction) return 'MOCK_PROCESSOR_REFUND_URI' mock_processor = flexmock(DummyProcessor) ( mock_processor .should_receive('refund') .replace_with(mock_refund) .once() ) with freeze_time('2013-08-17'): canceled_at = datetime.datetime.utcnow() res = self.testapp.post( '/v1/subscriptions/{}/cancel'.format(subscription_guid), dict(prorated_refund=True), extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) subscription = res.json self.assertEqual(subscription['canceled'], True) self.assertEqual(subscription['canceled_at'], canceled_at.isoformat()) transaction = refund_called[0] self.testapp.session.add(transaction) self.assertEqual(transaction.refund_to.guid, tx_guid) self.assertEqual(transaction.subscription_guid, subscription_guid) # only one day is elapsed, and it is a weekly plan, so # it should be 100 - (100 / 7) and round to cent, 85.71 self.assertEqual(transaction.amount, decimal.Decimal('85.71')) self.assertEqual(transaction.status, tx_model.STATUS_DONE) res = self.testapp.get( '/v1/transactions', extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) guids = [item['guid'] for item in res.json['items']] self.assertEqual(set(guids), set([tx_guid, transaction.guid]))
def test_yield_transactions(self): from billy.models.transaction import TransactionModel model = self.make_one(self.session) tx_model = TransactionModel(self.session) now = datetime.datetime.utcnow() with db_transaction.manager: guid = model.create( customer_guid=self.customer_tom_guid, plan_guid=self.monthly_plan_guid, ) tx_guids = model.yield_transactions() self.assertEqual(len(tx_guids), 1) subscription = model.get(guid) transactions = subscription.transactions self.assertEqual(len(transactions), 1) transaction = transactions[0] self.assertEqual(transaction.guid, tx_guids[0]) self.assertEqual(transaction.subscription_guid, guid) self.assertEqual(transaction.amount, subscription.plan.amount) self.assertEqual(transaction.transaction_type, TransactionModel.TYPE_CHARGE) self.assertEqual(transaction.scheduled_at, now) self.assertEqual(transaction.created_at, now) self.assertEqual(transaction.updated_at, now) self.assertEqual(transaction.status, TransactionModel.STATUS_INIT) # we should not yield new transaction as the datetime is the same with db_transaction.manager: tx_guids = model.yield_transactions() self.assertFalse(tx_guids) subscription = model.get(guid) self.assertEqual(len(subscription.transactions), 1) # should not yield new transaction as 09-16 is the date with freeze_time('2013-09-15'): with db_transaction.manager: tx_guids = model.yield_transactions() self.assertFalse(tx_guids) subscription = model.get(guid) self.assertEqual(len(subscription.transactions), 1) # okay, should yield new transaction now with freeze_time('2013-09-16'): with db_transaction.manager: tx_guids = model.yield_transactions() scheduled_at = datetime.datetime.utcnow() self.assertEqual(len(tx_guids), 1) subscription = model.get(guid) self.assertEqual(len(subscription.transactions), 2) transaction = tx_model.get(tx_guids[0]) self.assertEqual(transaction.subscription_guid, guid) self.assertEqual(transaction.amount, subscription.plan.amount) self.assertEqual(transaction.transaction_type, TransactionModel.TYPE_CHARGE) self.assertEqual(transaction.scheduled_at, scheduled_at) self.assertEqual(transaction.created_at, scheduled_at) self.assertEqual(transaction.updated_at, scheduled_at) self.assertEqual(transaction.status, TransactionModel.STATUS_INIT)
def test_cancel_subscription_with_refund_amount(self): from billy.models.subscription import SubscriptionModel from billy.models.transaction import TransactionModel subscription_model = SubscriptionModel(self.testapp.session) tx_model = TransactionModel(self.testapp.session) now = datetime.datetime.utcnow() with db_transaction.manager: subscription_guid = subscription_model.create( customer_guid=self.customer_guid, plan_guid=self.plan_guid, ) tx_guid = tx_model.create( subscription_guid=subscription_guid, transaction_type=tx_model.TYPE_CHARGE, amount=10, scheduled_at=now, ) subscription = subscription_model.get(subscription_guid) subscription.period = 1 subscription.next_transaction_at = datetime.datetime(2013, 8, 23) self.testapp.session.add(subscription) transaction = tx_model.get(tx_guid) transaction.status = tx_model.STATUS_DONE transaction.external_id = 'MOCK_BALANCED_DEBIT_URI' self.testapp.session.add(transaction) refund_called = [] def mock_refund(transaction): refund_called.append(transaction) return 'MOCK_PROCESSOR_REFUND_URI' mock_processor = flexmock(DummyProcessor) (mock_processor.should_receive('refund').replace_with( mock_refund).once()) res = self.testapp.post( '/v1/subscriptions/{}/cancel'.format(subscription_guid), dict(refund_amount='2.34'), extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) subscription = res.json transaction = refund_called[0] self.testapp.session.add(transaction) self.assertEqual(transaction.refund_to.guid, tx_guid) self.assertEqual(transaction.subscription_guid, subscription_guid) self.assertEqual(transaction.amount, decimal.Decimal('2.34')) self.assertEqual(transaction.status, tx_model.STATUS_DONE) res = self.testapp.get( '/v1/transactions', extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) guids = [item['guid'] for item in res.json['items']] self.assertEqual(set(guids), set([tx_guid, transaction.guid]))