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
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
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
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
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
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
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 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
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
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
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 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
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
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
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
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
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