def create_invoice_for_subscription(self, subscription): if subscription.is_trial: # Don't create invoices for trial subscriptions logger.info("[BILLING] Skipping invoicing for Subscription " "%s because it's a trial." % subscription.pk) return if subscription.auto_generate_credits: for product_rate in subscription.plan_version.product_rates.all(): CreditLine.add_credit( product_rate.monthly_fee, subscription=subscription, product_type=product_rate.product.product_type ) days_until_due = DEFAULT_DAYS_UNTIL_DUE if subscription.date_delay_invoicing is not None: td = subscription.date_delay_invoicing - self.date_end days_until_due = max(days_until_due, td.days) date_due = self.date_end + datetime.timedelta(days_until_due) if subscription.date_start > self.date_start: invoice_start = subscription.date_start else: invoice_start = self.date_start if subscription.date_end is not None and subscription.date_end <= self.date_end: # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. invoice_end = subscription.date_end - datetime.timedelta(days=1) else: invoice_end = self.date_end invoice = Invoice( subscription=subscription, date_start=invoice_start, date_end=invoice_end, date_due=date_due, is_hidden=subscription.do_not_invoice, ) invoice.save() if subscription.subscriptionadjustment_set.count() == 0: # record that the subscription was created SubscriptionAdjustment.record_adjustment( subscription, method=SubscriptionAdjustmentMethod.TASK, invoice=invoice ) self.generate_line_items(invoice, subscription) invoice.calculate_credit_adjustments() invoice.update_balance() invoice.save() record = BillingRecord.generate_record(invoice) try: record.send_email() except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: logger.error("[BILLING] %s" % e) self.logged_throttle_error = True return invoice
def pay_autopayable_invoices(self, date_due): """ Pays the full balance of all autopayable invoices on date_due """ autopayable_invoices = Invoice.autopayable_invoices(date_due) for invoice in autopayable_invoices: log_accounting_info("[Autopay] Autopaying invoice {}".format(invoice.id)) amount = invoice.balance.quantize(Decimal(10) ** -2) if not amount: continue auto_payer = invoice.subscription.account.auto_pay_user payment_method = StripePaymentMethod.objects.get(web_user=auto_payer) autopay_card = payment_method.get_autopay_card(invoice.subscription.account) if autopay_card is None: continue try: payment_record = payment_method.create_charge( autopay_card, amount_in_dollars=amount, description='Auto-payment for Invoice %s' % invoice.invoice_number, ) except stripe.error.CardError: self._handle_card_declined(invoice, payment_method) continue except payment_method.STRIPE_GENERIC_ERROR as e: self._handle_card_errors(invoice, e) continue else: with transaction.atomic(): invoice.pay_invoice(payment_record) invoice.subscription.account.last_payment_method = LastPayment.CC_AUTO invoice.account.save() self._send_payment_receipt(invoice, payment_record)
def setUp(self): super(TestBillingRecord, self).setUp() self.billing_contact = generator.create_arbitrary_web_user_name() self.dimagi_user = generator.create_arbitrary_web_user_name( is_dimagi=True) self.domain = Domain(name='test') self.domain.save() self.invoice_start, self.invoice_end = get_previous_month_date_range() self.currency = generator.init_default_currency() self.account = generator.billing_account(self.dimagi_user, self.billing_contact) self.subscription_length = 4 # months subscription_start_date = datetime.date(2016, 2, 23) subscription_end_date = add_months_to_date(subscription_start_date, self.subscription_length) self.subscription = generator.generate_domain_subscription( self.account, self.domain, date_start=subscription_start_date, date_end=subscription_end_date, ) self.invoice = Invoice( subscription=self.subscription, date_start=self.invoice_start, date_end=self.invoice_end, is_hidden=False, ) self.billing_record = BillingRecord(invoice=self.invoice)
def test_get_autopayable_invoices_returns_nothing(self): """ Invoice.autopayable_invoices() should not return invoices if the customer does not have an autopay method """ not_autopayable_invoice = Invoice.objects.filter(subscription=self.non_autopay_subscription) date_due = not_autopayable_invoice.first().date_due autopayable_invoices = Invoice.autopayable_invoices(date_due) self.assertItemsEqual(autopayable_invoices, [])
def pay_autopayable_invoices(self, date_due): """ Pays the full balance of all autopayable invoices on date_due """ autopayable_invoices = Invoice.autopayable_invoices(date_due) for invoice in autopayable_invoices: try: self._pay_invoice(invoice) except Exception as e: log_accounting_error("Error autopaying invoice %d: %s" % (invoice.id, e.message))
def test_get_autopayable_invoices_returns_nothing(self): """ Invoice.autopayable_invoices() should not return invoices if the customer does not have an autopay method """ not_autopayable_invoice = Invoice.objects.filter( subscription=self.non_autopay_subscription) date_due = not_autopayable_invoice.first().date_due autopayable_invoices = Invoice.autopayable_invoices(date_due) self.assertItemsEqual(autopayable_invoices, [])
def test_get_autopayable_invoices(self, fake_customer): self._create_autopay_method(fake_customer) autopayable_invoice = Invoice.objects.filter(subscription=self.subscription) date_due = autopayable_invoice.first().date_due autopayable_invoices = Invoice.autopayable_invoices(date_due) self.assertItemsEqual(autopayable_invoices, autopayable_invoice)
def test_get_autopayable_invoices(self, fake_customer): """ Invoice.autopayable_invoices() should return invoices that can be automatically paid """ self._create_autopay_method(fake_customer) autopayable_invoice = Invoice.objects.filter(subscription=self.subscription) date_due = autopayable_invoice.first().date_due autopayable_invoices = Invoice.autopayable_invoices(date_due) self.assertItemsEqual(autopayable_invoices, autopayable_invoice)
def test_get_autopayable_invoices(self, fake_customer): """ Invoice.autopayable_invoices() should return invoices that can be automatically paid """ self._create_autopay_method(fake_customer) autopayable_invoice = Invoice.objects.filter( subscription=self.subscription) date_due = autopayable_invoice.first().date_due autopayable_invoices = Invoice.autopayable_invoices(date_due) self.assertItemsEqual(autopayable_invoices, autopayable_invoice)
def test_get_autopayable_invoices(self, fake_customer): fake_customer.__get__ = mock.Mock(return_value=self.fake_stripe_customer) self.payment_method = StripePaymentMethod(web_user=self.web_user.username, customer_id=self.fake_stripe_customer.id) self.payment_method.set_autopay(self.fake_card, self.account, self.domain) self.payment_method.save() autopayable_invoice = Invoice.objects.filter(subscription=self.subscription) date_due = autopayable_invoice.first().date_due autopayable_invoices = Invoice.autopayable_invoices(date_due) self.assertItemsEqual(autopayable_invoices, autopayable_invoice)
def pay_autopayable_invoices(self, date_due=Ellipsis, domain=None): """ Pays the full balance of all autopayable invoices on date_due Note: we use Ellipsis as the default value for date_due because date_due can actually be None in the db. """ autopayable_invoices = Invoice.autopayable_invoices(date_due) if domain is not None: autopayable_invoices = autopayable_invoices.filter( subscription__subscriber__domain=domain) for invoice in autopayable_invoices: try: self._pay_invoice(invoice) except Exception as e: log_accounting_error("Error autopaying invoice %d: %s" % (invoice.id, e))
def setUp(self): super(TestBillingRecord, self).setUp() self.billing_contact = generator.arbitrary_web_user() self.dimagi_user = generator.arbitrary_web_user(is_dimagi=True) self.domain = Domain(name='test') self.domain.save() self.invoice_start, self.invoice_end = get_previous_month_date_range() self.currency = generator.init_default_currency() self.account = generator.billing_account(self.dimagi_user, self.billing_contact) self.subscription, self.subscription_length = generator.generate_domain_subscription_from_date( datetime.date.today(), self.account, self.domain.name ) self.invoice = Invoice( subscription=self.subscription, date_start=self.invoice_start, date_end=self.invoice_end, is_hidden=False, ) self.billing_record = BillingRecord(invoice=self.invoice)
def pay_autopayable_invoices(self, date_due): """ Pays the full balance of all autopayable invoices on date_due """ autopayable_invoices = Invoice.autopayable_invoices(date_due) for invoice in autopayable_invoices: logging.info("[Billing][Autopay] Autopaying invoice {}".format(invoice.id)) amount = invoice.balance.quantize(Decimal(10) ** -2) auto_payer = invoice.subscription.account.auto_pay_user payment_method = StripePaymentMethod.objects.get(web_user=auto_payer) autopay_card = payment_method.get_autopay_card(invoice.subscription.account) if autopay_card is None: continue try: payment_record = payment_method.create_charge(autopay_card, amount_in_dollars=amount) except stripe.error.CardError: self._handle_card_declined(invoice, payment_method) continue except payment_method.STRIPE_GENERIC_ERROR as e: self._handle_card_errors(invoice, payment_method, e) continue else: invoice.pay_invoice(payment_record) self._send_payment_receipt(invoice, payment_record)
def create(self): if self.subscription is None: raise InvoiceError("Cannot create an invoice without a subscription.") days_until_due = DEFAULT_DAYS_UNTIL_DUE if self.subscription.date_delay_invoicing is not None: td = self.subscription.date_delay_invoicing - self.date_end days_until_due = max(days_until_due, td.days) date_due = self.date_end + datetime.timedelta(days_until_due) invoice = Invoice( subscription=self.subscription, date_start=self.date_start, date_end=self.date_end, date_due=date_due ) invoice.save() self.generate_line_items(invoice) invoice.update_balance() if invoice.balance == Decimal("0.0"): invoice.billingrecord_set.all().delete() invoice.lineitem_set.all().delete() invoice.delete() return None invoice.calculate_credit_adjustments() invoice.update_balance() # generate PDF invoice.save() billing_record = BillingRecord(invoice=invoice) invoice_pdf = InvoicePdf() invoice_pdf.generate_pdf(billing_record) return invoice
def sidebar_items(self): items = [] user_is_admin = self.couch_user.is_domain_admin(self.domain) project_info = [] if user_is_admin: from corehq.apps.domain.views import EditBasicProjectInfoView, EditDeploymentProjectInfoView project_info.extend([ { 'title': _(EditBasicProjectInfoView.page_title), 'url': reverse(EditBasicProjectInfoView.urlname, args=[self.domain]) }, { 'title': _(EditDeploymentProjectInfoView.page_title), 'url': reverse(EditDeploymentProjectInfoView.urlname, args=[self.domain]) } ]) from corehq.apps.domain.views import EditMyProjectSettingsView project_info.append({ 'title': _(EditMyProjectSettingsView.page_title), 'url': reverse(EditMyProjectSettingsView.urlname, args=[self.domain]) }) can_view_orgs = (user_is_admin and self.project and self.project.organization) if can_view_orgs: try: ensure_request_has_privilege(self._request, privileges.CROSS_PROJECT_REPORTS) except PermissionDenied: can_view_orgs = False if can_view_orgs: from corehq.apps.domain.views import OrgSettingsView project_info.append({ 'title': _(OrgSettingsView.page_title), 'url': reverse(OrgSettingsView.urlname, args=[self.domain]) }) items.append((_('Project Information'), project_info)) if user_is_admin: from corehq.apps.domain.views import CommTrackSettingsView if self.project.commtrack_enabled: commtrack_settings = [ { 'title': _(CommTrackSettingsView.page_title), 'url': reverse(CommTrackSettingsView.urlname, args=[self.domain]) }, ] items.append((_('CommTrack'), commtrack_settings)) administration = [ { 'title': _('CommCare Exchange'), 'url': reverse('domain_snapshot_settings', args=[self.domain]) }, { 'title': _('Multimedia Sharing'), 'url': reverse('domain_manage_multimedia', args=[self.domain]) } ] def forward_name(repeater_type=None, **context): if repeater_type == 'FormRepeater': return _("Forward Forms") elif repeater_type == 'ShortFormRepeater': return _("Forward Form Stubs") elif repeater_type == 'CaseRepeater': return _("Forward Cases") administration.extend([ {'title': _('Data Forwarding'), 'url': reverse('domain_forwarding', args=[self.domain]), 'subpages': [ {'title': forward_name, 'urlname': 'add_repeater'} ]} ]) administration.append({ 'title': _('Feature Previews'), 'url': reverse('feature_previews', args=[self.domain]) }) items.append((_('Project Administration'), administration)) from corehq.apps.users.models import WebUser if isinstance(self.couch_user, WebUser): user_is_billing_admin, billing_account = BillingAccountAdmin.get_admin_status_and_account( self.couch_user, self.domain) if user_is_billing_admin or self.couch_user.is_superuser: from corehq.apps.domain.views import ( DomainSubscriptionView, EditExistingBillingAccountView, DomainBillingStatementsView, ConfirmSubscriptionRenewalView, ) subscription = [ { 'title': DomainSubscriptionView.page_title, 'url': reverse(DomainSubscriptionView.urlname, args=[self.domain]), 'subpages': [ { 'title': ConfirmSubscriptionRenewalView.page_title, 'urlname': ConfirmSubscriptionRenewalView.urlname, 'url': reverse(ConfirmSubscriptionRenewalView.urlname, args=[self.domain]), } ] }, ] if billing_account is not None: subscription.append( { 'title': EditExistingBillingAccountView.page_title, 'url': reverse(EditExistingBillingAccountView.urlname, args=[self.domain]), }, ) if (billing_account is not None and Invoice.exists_for_domain(self.domain) ): subscription.append( { 'title': DomainBillingStatementsView.page_title, 'url': reverse(DomainBillingStatementsView.urlname, args=[self.domain]), } ) items.append((_('Subscription'), subscription)) if self.couch_user.is_superuser: from corehq.apps.domain.views import EditInternalDomainInfoView, EditInternalCalculationsView internal_admin = [{ 'title': _(EditInternalDomainInfoView.page_title), 'url': reverse(EditInternalDomainInfoView.urlname, args=[self.domain]) }, { 'title': _(EditInternalCalculationsView.page_title), 'url': reverse(EditInternalCalculationsView.urlname, args=[self.domain]) }] items.append((_('Internal Data (Dimagi Only)'), internal_admin)) return items
def create_invoice_for_subscription(self, subscription): if subscription.is_trial: # Don't create invoices for trial subscriptions logger.info("[BILLING] Skipping invoicing for Subscription " "%s because it's a trial." % subscription.pk) return if subscription.auto_generate_credits: for product_rate in subscription.plan_version.product_rates.all(): CreditLine.add_credit( product_rate.monthly_fee, subscription=subscription, product_type=product_rate.product.product_type, ) days_until_due = DEFAULT_DAYS_UNTIL_DUE if subscription.date_delay_invoicing is not None: td = subscription.date_delay_invoicing - self.date_end days_until_due = max(days_until_due, td.days) date_due = self.date_end + datetime.timedelta(days_until_due) if subscription.date_start > self.date_start: invoice_start = subscription.date_start else: invoice_start = self.date_start if (subscription.date_end is not None and subscription.date_end <= self.date_end): # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. invoice_end = subscription.date_end - datetime.timedelta(days=1) else: invoice_end = self.date_end invoice = Invoice( subscription=subscription, date_start=invoice_start, date_end=invoice_end, date_due=date_due, is_hidden=subscription.do_not_invoice, ) invoice.save() if subscription.subscriptionadjustment_set.count() == 0: # record that the subscription was created SubscriptionAdjustment.record_adjustment( subscription, method=SubscriptionAdjustmentMethod.TASK, invoice=invoice, ) self.generate_line_items(invoice, subscription) invoice.calculate_credit_adjustments() invoice.update_balance() invoice.save() record = BillingRecord.generate_record(invoice) try: record.send_email() except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: logger.error("[BILLING] %s" % e) self.logged_throttle_error = True return invoice