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 super(TestTransactionModel, 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) 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/cards/tester', )
def setUp(self): from billy.models.company import CompanyModel from billy.models.customer import CustomerModel from billy.models.plan import PlanModel super(TestSubscriptionModel, self).setUp() # build the basic scenario for plan model self.company_model = CompanyModel(self.session) self.customer_model = CustomerModel(self.session) self.plan_model = PlanModel(self.session) with db_transaction.manager: self.company_guid = self.company_model.create('my_secret_key') self.daily_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_DAILY, ) self.weekly_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_WEEKLY, ) self.monthly_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_tom_guid = self.customer_model.create( company_guid=self.company_guid, )
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
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
def test_customer_list(self): from billy.models.customer import CustomerModel customer_model = CustomerModel(self.testapp.session) with db_transaction.manager: guids = [] for i in range(4): with freeze_time('2013-08-16 00:00:{:02}'.format(i + 1)): guid = customer_model.create(self.company_guid) guids.append(guid) guids = list(reversed(guids)) res = self.testapp.get( '/v1/customers', extra_environ=dict(REMOTE_USER=self.api_key), status=200, ) items = res.json['items'] result_guids = [item['guid'] for item in items] self.assertEqual(result_guids, guids)
def test_create_subscription_to_other_company_customer(self): from billy.models.company import CompanyModel from billy.models.customer import CustomerModel company_model = CompanyModel(self.testapp.session) customer_model = CustomerModel(self.testapp.session) with db_transaction.manager: other_company_guid = company_model.create( processor_key='MOCK_PROCESSOR_KEY', ) other_customer_guid = customer_model.create( company_guid=other_company_guid) self.testapp.post( '/v1/subscriptions', dict( customer_guid=other_customer_guid, plan_guid=self.plan_guid, ), extra_environ=dict(REMOTE_USER=self.api_key), status=403, )
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
def setUp(self): from billy.models.company import CompanyModel from billy.models.customer import CustomerModel from billy.models.plan import PlanModel self.settings = {'billy.processor_factory': DummyProcessor} super(TestSubscriptionViews, self).setUp() company_model = CompanyModel(self.testapp.session) customer_model = CustomerModel(self.testapp.session) plan_model = PlanModel(self.testapp.session) with db_transaction.manager: self.company_guid = company_model.create( processor_key='MOCK_PROCESSOR_KEY', ) self.customer_guid = customer_model.create( company_guid=self.company_guid) self.plan_guid = plan_model.create( company_guid=self.company_guid, frequency=plan_model.FREQ_WEEKLY, plan_type=plan_model.TYPE_CHARGE, amount=10, ) company = company_model.get(self.company_guid) self.api_key = str(company.api_key)
def test_get_subscription_of_other_company(self): from billy.models.company import CompanyModel from billy.models.customer import CustomerModel from billy.models.plan import PlanModel company_model = CompanyModel(self.testapp.session) customer_model = CustomerModel(self.testapp.session) plan_model = PlanModel(self.testapp.session) with db_transaction.manager: other_company_guid = company_model.create( processor_key='MOCK_PROCESSOR_KEY', ) other_customer_guid = customer_model.create( company_guid=other_company_guid) other_plan_guid = plan_model.create( company_guid=other_company_guid, frequency=plan_model.FREQ_WEEKLY, plan_type=plan_model.TYPE_CHARGE, amount=10, ) other_company = company_model.get(other_company_guid) other_api_key = str(other_company.api_key) res = self.testapp.post( '/v1/subscriptions', dict( customer_guid=other_customer_guid, plan_guid=other_plan_guid, ), extra_environ=dict(REMOTE_USER=other_api_key), status=200, ) other_guid = res.json['guid'] self.testapp.get( '/v1/subscriptions/{}'.format(other_guid), extra_environ=dict(REMOTE_USER=self.api_key), status=403, )
def test_main_with_crash(self): from pyramid.paster import get_appsettings from billy.models import setup_database 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.scripts import initializedb from billy.scripts import process_transactions class MockProcessor(object): def __init__(self): self.charges = {} self.tx_sn = 0 self.called_times = 0 def create_customer(self, customer): return 'MOCK_PROCESSOR_CUSTOMER_ID' def prepare_customer(self, customer, payment_uri=None): pass def charge(self, transaction): self.called_times += 1 if self.called_times == 2: raise KeyboardInterrupt guid = transaction.guid if guid in self.charges: return self.charges[guid] self.charges[guid] = self.tx_sn self.tx_sn += 1 mock_processor = MockProcessor() cfg_path = os.path.join(self.temp_dir, 'config.ini') with open(cfg_path, 'wt') as f: f.write( textwrap.dedent("""\ [app:main] use = egg:billy sqlalchemy.url = sqlite:///%(here)s/billy.sqlite """)) initializedb.main([initializedb.__file__, cfg_path]) settings = get_appsettings(cfg_path) settings = setup_database({}, **settings) session = settings['session'] company_model = CompanyModel(session) customer_model = CustomerModel(session) plan_model = PlanModel(session) subscription_model = SubscriptionModel(session) with db_transaction.manager: company_guid = company_model.create('my_secret_key') plan_guid = plan_model.create( company_guid=company_guid, plan_type=plan_model.TYPE_CHARGE, amount=10, frequency=plan_model.FREQ_MONTHLY, ) customer_guid = customer_model.create(company_guid=company_guid, ) subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, payment_uri='/v1/cards/tester', ) subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, payment_uri='/v1/cards/tester', ) with self.assertRaises(KeyboardInterrupt): process_transactions.main( [process_transactions.__file__, cfg_path], processor=mock_processor) process_transactions.main([process_transactions.__file__, cfg_path], processor=mock_processor) # here is the story, we have two subscriptions here # # Subscription1 # Subscription2 # # And the time is not advanced, so we should only have two transactions # to be yielded and processed. However, we assume bad thing happens # durring the process. We let the second call to charge of processor # raises a KeyboardInterrupt error. So, it would look like this # # charge for transaction from Subscription1 # charge for transaction from Subscription2 (Crash) # # Then, we perform the process_transactions again, if it works # correctly, the first transaction is already yield and processed. # # charge for transaction from Subscription2 # # So, there would only be two charges in processor. This is mainly # for making sure we won't duplicate charges/payouts self.assertEqual(len(mock_processor.charges), 2)
def create_customer_model(self): """Create a customer model """ return CustomerModel(self)
def make_one(self, *args, **kwargs): from billy.models.customer import CustomerModel return CustomerModel(*args, **kwargs)