Пример #1
0
    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
Пример #2
0
    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)
Пример #3
0
    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)
Пример #4
0
 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, [])
Пример #5
0
 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))
Пример #6
0
 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, [])
Пример #7
0
    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)
Пример #8
0
    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)
Пример #9
0
    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)
Пример #10
0
    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)
Пример #11
0
 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))
Пример #12
0
 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)
Пример #13
0
    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)
Пример #14
0
    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
Пример #15
0
    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
Пример #16
0
    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
Пример #17
0
    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