def get_rate_response(self): gateway = self.data.get('gateway') try: backend_api_id = SQLMobileBackend.get_backend_api_id(gateway, is_couch_id=True) except Exception as e: log_accounting_error( "Failed to get backend for calculating an sms rate due to: %s" % e ) raise SMSRateCalculatorError("Could not obtain connection information.") country_code = self.data.get('country_code') if country_code == NONMATCHING_COUNTRY: country_code = None direction = self.data.get('direction') gateway_fee = SmsGatewayFee.get_by_criteria( backend_api_id, direction, backend_instance=gateway, country_code=country_code, ) usage_fee = SmsUsageFee.get_by_criteria(direction, self.request.domain) usd_gateway_fee = gateway_fee.amount / gateway_fee.currency.rate_to_default usd_total = usage_fee.amount + usd_gateway_fee return { 'rate': _("%s per 160 character SMS") % fmt_dollar_amount(usd_total), }
def create_wire_credits_invoice(domain_name, account_created_by, account_entry_point, amount, invoice_items, contact_emails): account = BillingAccount.get_or_create_account_by_domain( domain_name, created_by=account_created_by, entry_point=account_entry_point )[0] wire_invoice = WirePrepaymentInvoice.objects.create( domain=domain_name, date_start=datetime.datetime.utcnow(), date_end=datetime.datetime.utcnow(), date_due=None, balance=amount, account=account, ) wire_invoice.items = invoice_items record = WirePrepaymentBillingRecord.generate_record(wire_invoice) if record.should_send_email: try: record.send_email(contact_emails=contact_emails) except Exception as e: log_accounting_error( "Error sending email for WirePrepaymentBillingRecord %d: %s" % (record.id, e.message), show_stack_trace=True, ) else: record.skipped_email = True record.save()
def create_wire_credits_invoice(domain_name, account_created_by, account_entry_point, amount, invoice_items, contact_emails): account = BillingAccount.get_or_create_account_by_domain( domain_name, created_by=account_created_by, created_by_invoicing=True, entry_point=account_entry_point )[0] wire_invoice = WirePrepaymentInvoice.objects.create( domain=domain_name, date_start=datetime.datetime.utcnow(), date_end=datetime.datetime.utcnow(), date_due=None, balance=amount, account=account, ) wire_invoice.items = invoice_items record = WirePrepaymentBillingRecord.generate_record(wire_invoice) try: record.send_email(contact_emails=contact_emails) except Exception as e: log_accounting_error(e.message)
def check_credit_line_balances(): for credit_line in CreditLine.objects.all(): expected_balance = sum(credit_line.creditadjustment_set.values_list('amount', flat=True)) if expected_balance != credit_line.balance: log_accounting_error( 'Credit line %s has balance %s, expected %s' % (credit_line.id, credit_line.balance, expected_balance) )
def get(self, request, *args, **kwargs): statement_id = kwargs.get('statement_id') if statement_id is None: raise Http404() try: invoice_pdf = InvoicePdf.get(statement_id) except ResourceNotFound: raise Http404() try: if not invoice_pdf.is_customer: raise NotImplementedError else: invoice = CustomerInvoice.objects.get(pk=invoice_pdf.invoice_id) except (Invoice.DoesNotExist, WireInvoice.DoesNotExist, CustomerInvoice.DoesNotExist): raise Http404() filename = "%(pdf_id)s_%(edition)s_%(filename)s" % { 'pdf_id': invoice_pdf._id, 'edition': 'customer', 'filename': invoice_pdf.get_filename(invoice), } try: data = invoice_pdf.get_data(invoice) response = HttpResponse(data, content_type='application/pdf') response['Content-Disposition'] = 'inline;filename="%s' % filename except Exception as e: log_accounting_error('Fetching invoice PDF failed: %s' % e) return HttpResponse(_("Could not obtain billing statement. " "An issue has been submitted.")) return response
def post(self, request, *args, **kwargs): if self.async_response is not None: return self.async_response if ('account_basic' in self.request.POST and self.basic_account_form.is_valid()): self.basic_account_form.update_basic_info(self.account) messages.success(request, "Account successfully updated.") return HttpResponseRedirect(self.page_url) elif ('account_contact' in self.request.POST and self.contact_form.is_valid()): self.contact_form.save() messages.success(request, "Account Contact Info successfully updated.") return HttpResponseRedirect(self.page_url) elif ('adjust' in self.request.POST and self.credit_form.is_valid()): try: if self.credit_form.adjust_credit(web_user=request.user.username): messages.success(request, "Successfully adjusted credit.") return HttpResponseRedirect(self.page_url) except CreditLineError as e: log_accounting_error( "failed to add credit in admin UI due to: %s" % e ) messages.error(request, "Issue adding credit: %s" % e) return self.get(request, *args, **kwargs)
def create_wire_credits_invoice(domain_name, amount, invoice_items, contact_emails): wire_invoice = WirePrepaymentInvoice.objects.create( domain=domain_name, date_start=datetime.datetime.utcnow(), date_end=datetime.datetime.utcnow(), date_due=None, balance=amount, ) wire_invoice.items = invoice_items record = WirePrepaymentBillingRecord.generate_record(wire_invoice) if record.should_send_email: try: for email in contact_emails: record.send_email(contact_email=email) except Exception as e: log_accounting_error( "Error sending email for WirePrepaymentBillingRecord %d: %s" % (record.id, six.text_type(e)), show_stack_trace=True, ) else: record.skipped_email = True record.save()
def send_purchase_receipt(payment_record, core_product, domain, template_html, template_plaintext, additional_context): username = payment_record.payment_method.web_user try: web_user = WebUser.get_by_username(username) email = web_user.get_email() name = web_user.first_name except ResourceNotFound: log_accounting_error( "Strange. A payment attempt was made by a user that " "we can't seem to find! %s" % username ) name = email = username context = { 'name': name, 'amount': fmt_dollar_amount(payment_record.amount), 'project': domain, 'date_paid': payment_record.date_created.strftime(USER_DATE_FORMAT), 'product': core_product, 'transaction_id': payment_record.public_transaction_id, } context.update(additional_context) email_html = render_to_string(template_html, context) email_plaintext = render_to_string(template_plaintext, context) send_HTML_email( ugettext("Payment Received - Thank You!"), email, email_html, text_content=email_plaintext, email_from=get_dimagi_from_email_by_product(core_product), )
def warn_subscriptions_still_active(based_on_date=None): ending_date = based_on_date or datetime.date.today() subscriptions_still_active = Subscription.objects.filter( date_end__lte=ending_date, is_active=True, ) for subscription in subscriptions_still_active: log_accounting_error("%s is still active." % subscription)
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 create_wire_invoice(self, balance): # Gather relevant invoices if self.account and self.account.is_customer_billing_account: invoices = CustomerInvoice.objects.filter(account=self.account) else: invoices = Invoice.objects.filter( subscription__subscriber__domain=self.domain.name, is_hidden=False, date_paid__exact=None ).order_by('-date_start') self.account = BillingAccount.get_or_create_account_by_domain( self.domain.name, created_by=self.__class__.__name__, entry_point=EntryPoint.SELF_STARTED )[0] # If no start date supplied, default earliest start date of unpaid invoices if self.date_start: date_start = self.date_start else: date_start = invoices.aggregate(Min('date_start'))['date_start__min'] # If no end date supplied, default latest end date of unpaid invoices if self.date_end: date_end = self.date_end else: date_end = invoices.aggregate(Max('date_end'))['date_end__max'] if not date_end: date_end = datetime.datetime.today() date_due = date_end + datetime.timedelta(DEFAULT_DAYS_UNTIL_DUE) wire_invoice = WireInvoice.objects.create( domain=self.domain.name, date_start=date_start, date_end=date_end, date_due=date_due, balance=balance, ) record = WireBillingRecord.generate_record(wire_invoice) if record.should_send_email: try: for email in self.contact_emails: record.send_email(contact_email=email) except InvoiceEmailThrottledError as e: # Currently wire invoices are never throttled if not self.logged_throttle_error: log_accounting_error(six.text_type(e)) self.logged_throttle_error = True else: record.skipped_email = True record.save() return wire_invoice
def warn_subscriptions_not_active(based_on_date=None): based_on_date = based_on_date or datetime.date.today() subscriptions_not_active = Subscription.objects.filter( Q(date_end=None) | Q(date_end__gt=based_on_date), date_start__lte=based_on_date, is_active=False, ) for subscription in subscriptions_not_active: log_accounting_error("%s is not active" % subscription)
def _handle_card_declined(invoice, payment_method): from corehq.apps.accounting.tasks import send_autopay_failed log_accounting_error( "[Autopay] An automatic payment failed for invoice: {} " "because the card was declined. This invoice will not be automatically paid. " "Not necessarily actionable, but be aware that this happened." .format(invoice.id) ) send_autopay_failed.delay(invoice, payment_method)
def warn_subscriptions_without_domain(): domains_with_active_subscription = Subscription.visible_objects.filter( is_active=True, ).values_list('subscriber__domain', flat=True).distinct() for domain_name in set(domains_with_active_subscription) - set( Domain.get_all_names()): log_accounting_error( 'Domain %s has an active subscription but does not exist.' % domain_name)
def create_wire_invoice(self, balance): # Gather relevant invoices invoices = Invoice.objects.filter( subscription__subscriber__domain=self.domain, is_hidden=False, date_paid__exact=None, ).order_by('-date_start') account = BillingAccount.get_or_create_account_by_domain( self.domain.name, created_by=self.__class__.__name__, entry_point=EntryPoint.SELF_STARTED, )[0] # If no start date supplied, default earliest start date of unpaid invoices if self.date_start: date_start = self.date_start else: date_start = invoices.aggregate( Min('date_start'))['date_start__min'] # If no end date supplied, default latest end date of unpaid invoices if self.date_end: date_end = self.date_end else: date_end = invoices.aggregate(Max('date_end'))['date_end__max'] if not date_end: date_end = datetime.datetime.today() date_due = date_end + datetime.timedelta(DEFAULT_DAYS_UNTIL_DUE) wire_invoice = WireInvoice.objects.create( domain=self.domain.name, date_start=date_start, date_end=date_end, date_due=date_due, balance=balance, ) record = WireBillingRecord.generate_record(wire_invoice) if record.should_send_email: try: record.send_email(contact_emails=self.contact_emails) except InvoiceEmailThrottledError as e: # Currently wire invoices are never throttled if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True else: record.skipped_email = True record.save() return wire_invoice
def warn_active_subscriptions_per_domain_not_one(): for domain_name in Domain.get_all_names(): active_subscription_count = Subscription.visible_objects.filter( subscriber__domain=domain_name, is_active=True, ).count() if active_subscription_count > 1: log_accounting_error("Multiple active subscriptions found for domain %s" % domain_name) elif active_subscription_count == 0 and Domain.get_by_name(domain_name).is_active: log_accounting_error("There is no active subscription for domain %s" % domain_name)
def warn_active_subscriptions_per_domain_not_one(): for domain_name in Domain.get_all_names(): active_subscription_count = Subscription.objects.filter( subscriber__domain=domain_name, is_active=True, ).count() if active_subscription_count > 1: log_accounting_error("Multiple active subscriptions found for domain %s" % domain_name) elif active_subscription_count == 0: log_accounting_error("There is no active subscription for domain %s" % domain_name)
def paginated_list(self): for invoice in self.paginated_invoices.page(self.page).object_list: try: last_billing_record = CustomerBillingRecord.objects.filter( invoice=invoice).latest('date_created') if invoice.is_paid: payment_status = ( _("Paid on %s.") % invoice.date_paid.strftime(USER_DATE_FORMAT)) payment_class = "label label-default" else: payment_status = _("Not Paid") payment_class = "label label-danger" date_due = ((invoice.date_due.strftime(USER_DATE_FORMAT) if not invoice.is_paid else _("Already Paid")) if invoice.date_due else _("None")) yield { 'itemData': { 'id': invoice.id, 'invoice_number': invoice.invoice_number, 'start': invoice.date_start.strftime(USER_DATE_FORMAT), 'end': invoice.date_end.strftime(USER_DATE_FORMAT), 'plan': None, 'payment_status': payment_status, 'payment_class': payment_class, 'date_due': date_due, 'pdfUrl': reverse(BillingStatementPdfView.urlname, args=[ self.domain, last_billing_record.pdf_data_id ]), 'canMakePayment': (not invoice.is_paid and self.can_pay_invoices), 'balance': "%s" % quantize_accounting_decimal(invoice.balance), }, 'template': 'statement-row-template', } except CustomerBillingRecord.DoesNotExist: log_accounting_error( "An invoice was generated for %(invoice_id)d " "(domain: %(domain)s), but no billing record!" % { 'invoice_id': invoice.id, 'domain': self.domain, }, show_stack_trace=True)
def create_wire_invoice(self, balance): # Gather relevant invoices invoices = Invoice.objects.filter( subscription__subscriber__domain=self.domain, is_hidden=False, date_paid__exact=None, ).order_by('-date_start') account = BillingAccount.get_or_create_account_by_domain( self.domain.name, created_by=self.__class__.__name__, created_by_invoicing=True, entry_point=EntryPoint.SELF_STARTED, )[0] # If no start date supplied, default earliest start date of unpaid invoices if self.date_start: date_start = self.date_start else: date_start = invoices.aggregate(Min('date_start'))['date_start__min'] # If no end date supplied, default latest end date of unpaid invoices if self.date_end: date_end = self.date_end else: date_end = invoices.aggregate(Max('date_end'))['date_end__max'] if not date_end: date_end = datetime.datetime.today() date_due = date_end + datetime.timedelta(DEFAULT_DAYS_UNTIL_DUE) # TODO: figure out how to handle line items wire_invoice = WireInvoice.objects.create( domain=self.domain.name, date_start=date_start, date_end=date_end, date_due=date_due, balance=balance, account=account ) record = WireBillingRecord.generate_record(wire_invoice) try: record.send_email(contact_emails=self.contact_emails) except InvoiceEmailThrottledError as e: # Currently wire invoices are never throttled if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True return wire_invoice
def create_billable_for_sms(msg, delay=True): if not msg.domain: return try: from corehq.apps.sms.tasks import store_billable if delay: store_billable.delay(msg) else: store_billable(msg) except Exception as e: log_accounting_error("Errors Creating SMS Billable: %s" % e)
def response_inbound_sms(domain, new_plan_version): """ All Reminder rules utilizing "survey" will be deactivated. """ try: _deactivate_schedules(domain, survey_only=True) except Exception: log_accounting_error( "Failed to downgrade inbound sms for domain %s." % domain.name) return False return True
def _ensure_role(role_slug, apps): Role = apps.get_model('django_prbac', 'Role') try: role = Role.objects.get(slug=role_slug) except Role.DoesNotExist: log_accounting_error( "Could not find the role '%s'. Did you forget to run cchq_prbac_bootstrap?" % role_slug) log_accounting_error("Aborting. You should figure this out.") raise return role
def create_invoices(self): subscriptions = self._get_subscriptions() self._ensure_full_coverage(subscriptions) for subscription in subscriptions: try: self._create_invoice_for_subscription(subscription) except InvoiceAlreadyCreatedError as e: log_accounting_error( "Invoice already existed for domain %s: %s" % (self.domain.name, e), show_stack_trace=True, )
def check_credit_line_balances(): for credit_line in CreditLine.objects.all(): expected_balance = sum(credit_line.creditadjustment_set.values_list('amount', flat=True)) if expected_balance != credit_line.balance: try: # needed for sending to sentry raise CreditLineBalanceMismatchError() except CreditLineBalanceMismatchError: log_accounting_error( f'Credit line {credit_line.id} has balance {credit_line.balance},' f' expected {expected_balance}' )
def _ensure_role(role_slug, apps): Role = apps.get_model('django_prbac', 'Role') try: role = Role.objects.get(slug=role_slug) except Role.DoesNotExist: log_accounting_error( "Could not find the role '%s'. Did you forget to run cchq_prbac_bootstrap?" % role_slug ) log_accounting_error("Aborting. You should figure this out.") raise return role
def response_outbound_sms(domain, new_plan_version): """ Reminder rules will be deactivated. """ try: _deactivate_schedules(domain) except Exception: log_accounting_error( "Failed to downgrade outbound sms for domain %s." % domain.name) return False return True
def warn_subscriptions_still_active(based_on_date=None): ending_date = based_on_date or datetime.date.today() subscriptions_still_active = Subscription.visible_objects.filter( date_end__lte=ending_date, is_active=True, ) for subscription in subscriptions_still_active: try: # needed for sending to sentry raise SubscriptionTaskError() except SubscriptionTaskError: log_accounting_error(f"{subscription} is still active.")
def warn_subscriptions_without_domain(): domains_with_active_subscription = Subscription.visible_objects.filter( is_active=True, ).values_list('subscriber__domain', flat=True).distinct() for domain_name in set(domains_with_active_subscription) - set(Domain.get_all_names()): # we need to put a try/except here so that sentry captures logging try: raise ActiveSubscriptionWithoutDomain() except ActiveSubscriptionWithoutDomain: log_accounting_error( f'Domain {domain_name} has an active subscription but does not exist.' )
def warn_subscriptions_not_active(based_on_date=None): based_on_date = based_on_date or datetime.date.today() subscriptions_not_active = Subscription.visible_objects.filter( Q(date_end=None) | Q(date_end__gt=based_on_date), date_start__lte=based_on_date, is_active=False, ) for subscription in subscriptions_not_active: try: # needed for sending to sentry raise SubscriptionTaskError() except SubscriptionTaskError: log_accounting_error(f"{subscription} is not active.")
def send_subscription_reminder_emails(num_days, exclude_trials=True): today = datetime.date.today() date_in_n_days = today + datetime.timedelta(days=num_days) ending_subscriptions = Subscription.objects.filter(date_end=date_in_n_days) if exclude_trials: ending_subscriptions = ending_subscriptions.filter(is_trial=False) for subscription in ending_subscriptions: try: # only send reminder emails if the subscription isn't renewed if not subscription.is_renewed: subscription.send_ending_reminder_email() except Exception as e: log_accounting_error(e.message)
def _email_invoice(self): record = CustomerBillingRecord.generate_record(self.customer_invoice) try: if self.recipients: for email in self.recipients: record.send_email(contact_email=email) elif self.account.dimagi_contact: record.send_email(contact_email=self.account.dimagi_contact, cc_emails=[settings.ACCOUNTS_EMAIL]) else: record.send_email(contact_email=settings.ACCOUNTS_EMAIL) except InvoiceEmailThrottledError as e: log_accounting_error(str(e))
def response_inbound_sms(domain, new_plan_version): """ All Reminder rules utilizing "survey" will be deactivated. """ try: _deactivate_schedules(domain, survey_only=True) except Exception: log_accounting_error( "Failed to downgrade inbound sms for domain %s." % domain.name ) return False return True
def _handle_card_declined(invoice, payment_method, e): from corehq.apps.accounting.tasks import send_autopay_failed # https://stripe.com/docs/api/python#error_handling body = e.json_body err = body.get('error', {}) log_accounting_error( "[Autopay] An automatic payment failed for invoice: {} " "because the card was declined. This invoice will not be automatically paid. " "Not necessarily actionable, but be aware that this happened. " "error = {}".format(invoice.id, err)) send_autopay_failed.delay(invoice, payment_method)
def response_outbound_sms(domain, new_plan_version): """ Reminder rules will be deactivated. """ try: _deactivate_schedules(domain) except Exception: log_accounting_error( "Failed to downgrade outbound sms for domain %s." % domain.name ) return False return True
def archive_logos(domain_name): try: for app in get_all_apps(domain_name): if isinstance(app, HQMediaMixin): has_archived = app.archive_logos() if has_archived: app.save() except Exception as e: log_accounting_error( "Failed to remove all commcare logos for domain %s." % domain_name, show_stack_trace=True, ) raise e
def _create_invoice_for_subscription(self, subscription): def _get_invoice_start(sub, date_start): if sub.date_start > date_start: return sub.date_start else: return date_start def _get_invoice_end(sub, date_end): if sub.date_end is not None and sub.date_end <= date_end: # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. return sub.date_end - datetime.timedelta(days=1) else: return date_end if subscription.is_trial: # Don't create invoices for trial subscriptions log_accounting_info( "Skipping invoicing for Subscription %s because it's a trial." % subscription.pk ) return if ( subscription.skip_invoicing_if_no_feature_charges and not subscription.plan_version.feature_charges_exist_for_domain(self.domain) ): log_accounting_info( "Skipping invoicing for Subscription %s because there are no feature charges." % subscription.pk ) return invoice_start = _get_invoice_start(subscription, self.date_start) invoice_end = _get_invoice_end(subscription, self.date_end) with transaction.atomic(): invoice = self._generate_invoice(subscription, invoice_start, invoice_end) record = BillingRecord.generate_record(invoice) if record.should_send_email: try: record.send_email(contact_emails=self.recipients) except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True else: record.skipped_email = True record.save() return invoice
def _assign_explicit_community_subscriptions(apps, schema_editor): today = date.today() all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain_name = domain_doc['name'] from_date = date(today.year, today.month, 1) try: while from_date <= today: ensure_explicit_community_subscription(domain_name, from_date) from_date += timedelta(days=1) except Exception as e: log_accounting_error( "During community subscription assignment for domain %s: %s" % (domain_name, e.message))
def send_subscription_reminder_emails(num_days): today = datetime.date.today() date_in_n_days = today + datetime.timedelta(days=num_days) ending_subscriptions = Subscription.objects.filter( date_end=date_in_n_days, do_not_email_reminder=False, is_trial=False) for subscription in ending_subscriptions: try: # only send reminder emails if the subscription isn't renewed if not subscription.is_renewed: subscription.send_ending_reminder_email() except Exception as e: log_accounting_error( "Error sending reminder for subscription %d: %s" % (subscription.id, e.message))
def deactivate_subscriptions(based_on_date=None): """ Deactivates all subscriptions ending today (or, for testing, based on the date specified) """ ending_date = based_on_date or datetime.date.today() ending_subscriptions = Subscription.objects.filter( date_end=ending_date, is_active=True, ) for subscription in ending_subscriptions: try: _deactivate_subscription(subscription, ending_date) except Exception as e: log_accounting_error(e.message)
def generate_invoices(based_on_date=None): """ Generates all invoices for the past month. """ today = based_on_date or datetime.date.today() invoice_start, invoice_end = get_previous_month_date_range(today) log_accounting_info( "Starting up invoices for %(start)s - %(end)s" % { 'start': invoice_start.strftime(USER_DATE_FORMAT), 'end': invoice_end.strftime(USER_DATE_FORMAT), }) all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain = Domain.wrap(domain_doc) try: invoice_factory = DomainInvoiceFactory(invoice_start, invoice_end, domain) invoice_factory.create_invoices() log_accounting_info("Sent invoices for domain %s" % domain.name) except CreditLineError as e: log_accounting_error("There was an error utilizing credits for " "domain %s: %s" % (domain.name, e)) except InvoiceError as e: log_accounting_error("Could not create invoice for domain %s: %s" % (domain.name, e)) except InvoiceAlreadyCreatedError as e: log_accounting_error("Invoice already existed for domain %s: %s" % (domain.name, e)) except Exception as e: log_accounting_error("Error occurred while creating invoice for " "domain %s: %s" % (domain.name, e)) if not settings.UNIT_TESTING: _invoicing_complete_soft_assert(False, "Invoicing is complete!")
def _create_invoice_for_subscription(self, subscription): def _get_invoice_start(sub, date_start): if sub.date_start > date_start: return sub.date_start else: return date_start def _get_invoice_end(sub, date_end): if sub.date_end is not None and sub.date_end <= date_end: # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. return sub.date_end - datetime.timedelta(days=1) else: return date_end if subscription.is_trial: # Don't create invoices for trial subscriptions log_accounting_info( "Skipping invoicing for Subscription %s because it's a trial." % subscription.pk) return if (subscription.skip_invoicing_if_no_feature_charges and not subscription.plan_version.feature_charges_exist_for_domain( self.domain)): log_accounting_info( "Skipping invoicing for Subscription %s because there are no feature charges." % subscription.pk) return invoice_start = _get_invoice_start(subscription, self.date_start) invoice_end = _get_invoice_end(subscription, self.date_end) with transaction.atomic(): invoice = self._generate_invoice(subscription, invoice_start, invoice_end) record = BillingRecord.generate_record(invoice) if record.should_send_email: try: record.send_email(contact_emails=self.recipients) except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True else: record.skipped_email = True record.save() return invoice
def calculate_users_in_all_domains(today=None): today = today or datetime.date.today() for domain in Domain.get_all_names(): num_users = CommCareUser.total_by_domain(domain) record_date = today - relativedelta(days=1) try: DomainUserHistory.objects.create(domain=domain, num_users=num_users, record_date=record_date) except Exception as e: log_accounting_error( "Something went wrong while creating DomainUserHistory for domain %s: %s" % (domain, e), show_stack_trace=True, )
def response_commcare_logo_uploader(self): """Make sure no existing applications are using a logo. """ try: for app in get_all_apps(self.domain.name): has_restored = app.restore_logos() if has_restored: app.save() return True except Exception: log_accounting_error( "Failed to restore all commcare logos for domain %s." % self.domain.name ) return False
def create_invoices(self): subscriptions = self._get_subscriptions() self._ensure_full_coverage(subscriptions) for subscription in subscriptions: try: if subscription.account.is_customer_billing_account: log_accounting_info("Skipping invoice for subscription: %s, because it is part of a Customer " "Billing Account." % (subscription)) elif should_create_invoice(subscription, self.domain, self.date_start, self.date_end): self._create_invoice_for_subscription(subscription) except InvoiceAlreadyCreatedError as e: log_accounting_error( "Invoice already existed for domain %s: %s" % (self.domain.name, e), show_stack_trace=True, )
def response_data_cleanup(domain, new_plan_version): """ Any active automatic case update rules should be deactivated. """ try: AutomaticUpdateRule.objects.filter( domain=domain.name, deleted=False, active=True, ).update(active=False) return True except Exception: log_accounting_error("Failed to deactivate automatic update rules " "for domain %s." % domain.name) return False
def response_commcare_logo_uploader(domain, new_plan_version): """Make sure no existing applications are using a logo. """ try: for app in get_all_apps(domain.name): if isinstance(app, HQMediaMixin): has_restored = app.restore_logos() if has_restored: app.save() return True except Exception: log_accounting_error( "Failed to restore all commcare logos for domain %s." % domain.name) return False
def deactivate_subscriptions(based_on_date=None): """ Deactivates all subscriptions ending today (or, for testing, based on the date specified) """ ending_date = based_on_date or datetime.date.today() ending_subscriptions = Subscription.objects.filter( date_end=ending_date, is_active=True, ) for subscription in ending_subscriptions: try: _deactivate_subscription(subscription, ending_date) except Exception as e: log_accounting_error('Error deactivating subscription %d: %s' % (subscription.id, e.message))
def create_invoice(self): for sub in self.account.subscription_set.filter(do_not_invoice=False): if not sub.plan_version.plan.edition == SoftwarePlanEdition.COMMUNITY \ and should_create_invoice(sub, sub.subscriber.domain, self.date_start, self.date_end): self.subscriptions[sub.plan_version].append(sub) if not self.subscriptions: return try: self._generate_customer_invoice() self._email_invoice() except InvoiceAlreadyCreatedError as e: log_accounting_error( "Invoice already existed for account %s: %s" % (self.account.name, e), show_stack_trace=True, )
def _assign_explicit_community_subscriptions(apps, schema_editor): today = date.today() all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain_name = domain_doc['name'] from_date = date(today.year, today.month, 1) try: while from_date <= today: ensure_explicit_community_subscription(domain_name, from_date) from_date += timedelta(days=1) except Exception as e: log_accounting_error( "During community subscription assignment for domain %s: %s" % (domain_name, e.message) )
def restore_logos(self, domain_name): try: for app in get_all_apps(domain_name): if isinstance(app, HQMediaMixin): has_restored = app.restore_logos() if has_restored: app.save() except ResourceConflict as e: raise self.retry(exc=e) except Exception as e: log_accounting_error( "Failed to restore all commcare logos for domain %s." % domain_name, show_stack_trace=True, ) raise e
def restore_logos(self, domain_name): try: for app in get_all_apps(domain_name): if isinstance(app, ApplicationMediaMixin): has_restored = app.restore_logos() if has_restored: app.save() except ResourceConflict as e: raise self.retry(exc=e) except Exception as e: log_accounting_error( "Failed to restore all commcare logos for domain %s." % domain_name, show_stack_trace=True, ) raise e
def _handle_card_declined(invoice, e): from corehq.apps.accounting.tasks import send_autopay_failed # https://stripe.com/docs/api/python#error_handling body = e.json_body err = body.get('error', {}) log_accounting_error( "[Autopay] An automatic payment failed for invoice: {invoice} ({domain})" "because the card was declined. This invoice will not be automatically paid. " "Not necessarily actionable, but be aware that this happened. " "error = {error}" .format(invoice=invoice.id, domain=invoice.get_domain(), error=err) ) send_autopay_failed.delay(invoice)
def send_subscription_reminder_emails(num_days): today = datetime.date.today() date_in_n_days = today + datetime.timedelta(days=num_days) ending_subscriptions = Subscription.objects.filter( date_end=date_in_n_days, do_not_email_reminder=False, is_trial=False ) for subscription in ending_subscriptions: try: # only send reminder emails if the subscription isn't renewed if not subscription.is_renewed: subscription.send_ending_reminder_email() except Exception as e: log_accounting_error( "Error sending reminder for subscription %d: %s" % (subscription.id, e.message) )
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 response_data_cleanup(domain, new_plan_version): """ Any active automatic case update rules should be deactivated. """ try: AutomaticUpdateRule.by_domain( domain.name, AutomaticUpdateRule.WORKFLOW_CASE_UPDATE, ).update(active=False) AutomaticUpdateRule.clear_caches( domain.name, AutomaticUpdateRule.WORKFLOW_CASE_UPDATE) return True except Exception: log_accounting_error("Failed to deactivate automatic update rules " "for domain %s." % domain.name) return False