def save_model(self, request, obj, form, change): # Send e-mail for manually generated Invoice upon creation if change: super(InvoiceAdmin, self).save_model(request, obj, form, change) return obj.number = get_next_invoice_number(auto=False) super(InvoiceAdmin, self).save_model(request, obj, form, change) member = obj.subscription.member service = get_service_instance() config = service.config subject, message, sms_text = get_invoice_generated_message(obj) if member.email: add_event(service, NEW_INVOICE_EVENT, member=member, object_id=obj.id) invoice_url = service.url + reverse('billing:invoice_detail', args=(obj.id,)) html_content = get_mail_content(subject, message, template_name='billing/mails/notice.html', extra_context={'invoice_url': invoice_url}) # Sender is simulated as being no-reply@company_name_slug.com to avoid the mail # to be delivered to Spams because of origin check. sender = '%s <no-reply@%s>' % (service.project_name, service.domain) msg = EmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" if msg.send(fail_silently=True): obj.reminders_sent = 1 obj.last_reminder = timezone.now() if sms_text: if member.phone: if config.sms_sending_method == Config.HTTP_API: send_sms(member.phone, sms_text) else: QueuedSMS.objects.create(recipient=member.phone, text=sms_text)
def pull_invoice(request, *args, **kwargs): api_signature = request.POST.get('api_signature') try: service = Service.objects.get(api_signature=api_signature) except: notice = "Invalid API Signature." response = {'error': notice} return HttpResponse(json.dumps(response)) db = service.database add_database(db) invoicing_config, update = InvoicingConfig.objects.using(db).get_or_create( service=service) if not invoicing_config.pull_invoice: notice = "Cannot import when not explicitly configured to do so. You must activate " \ "'pull_invoice' in your platform configuration for import to work." response = {'error': notice} return HttpResponse(json.dumps(response)) lang = request.POST.get('lang', "en") activate(lang) missing = [] errors = [] do_pull = True try: number = request.POST['invoice_number'].strip() try: Invoice.objects.using(db).get(number=number) errors.append( "Invoice with number '%s' already exists. Invoice numbers must be unique." % number) do_pull = False except Invoice.DoesNotExist: pass except KeyError: missing.append('invoice_number') do_pull = False try: reference_id = request.POST['reference_id'] except KeyError: reference_id = None missing.append('reference_id') do_pull = False try: amount = request.POST['amount'] amount = float(amount) except KeyError: missing.append('amount') do_pull = False except ValueError: errors.append("Invalid amount '%s'. Expected valid float or int.") do_pull = False try: due_date = request.POST['due_date'] time.strptime(due_date, '%Y-%m-%d') except KeyError: missing.append('due_date') do_pull = False except ValueError: errors.append( "Invalid due_date '%s'. Expected valid date in the format 'YYYY-mm-dd'." ) do_pull = False try: quantity = request.POST['quantity'] except KeyError: missing.append('quantity') do_pull = False except ValueError: errors.append("Invalid quantity '%s'. Expected valid int.") do_pull = False quantity_unit = request.POST.get('quantity_unit', _("Month(s)")) currency_code = request.POST.get('currency_code', 'XAF') if reference_id: try: subscription = Subscription.objects.using(db).select_related( 'member', 'product').get(reference_id=reference_id) except Subscription.DoesNotExist: do_pull = False notice = "reference_id '%s' not found." % reference_id errors.append(notice) if not do_pull: response = {'error': '\n'.join(errors)} if missing: response[ 'missing'] = 'Following parameters are missing: ' + ', '.join( missing) return HttpResponse(json.dumps(response)) product = subscription.product if product: short_description = product.name else: short_description = request.POST.get('short_description', '---') invoice_entries = [] item = InvoiceItem(label=_('Subscription'), amount=amount) entry = InvoiceEntry(item=item, short_description=short_description, quantity=quantity, quantity_unit=quantity_unit, total=amount) invoice_entries.append(entry) invoice = Invoice.objects.using(db).create(number=number, member=subscription.member, subscription=subscription, amount=amount, months_count=quantity, due_date=due_date, entries=invoice_entries) config = service.config member = subscription.member if member.email: with transaction.atomic(using=WALLETS_DB_ALIAS): from echo.models import Balance from echo.utils import notify_for_low_messaging_credit, LOW_MAIL_LIMIT, notify_for_empty_messaging_credit balance, update = Balance.objects.using( WALLETS_DB_ALIAS).get_or_create(service_id=service.id) if 0 < balance.mail_count < LOW_MAIL_LIMIT: notify_for_low_messaging_credit(service, balance) if balance.mail_count <= 0: notify_for_empty_messaging_credit(service, balance) response = { 'success': True, 'warning': "Email not set due to empty mail credit" } return HttpResponse(json.dumps(response)) subject, message, sms_text = get_invoice_generated_message(invoice) try: currency = Currency.objects.using(db).get( code=currency_code).symbol except: try: currency = Currency.active.default().symbol except: currency = currency_code invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, template_name='billing/mails/notice.html', service=service, extra_context={ 'invoice': invoice, 'member_name': member.first_name, 'invoice_url': invoice_url, 'cta': _("Pay now"), 'currency': currency }) sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" balance.mail_count -= 1 balance.save() if getattr(settings, 'UNIT_TESTING', False): msg.send() else: Thread(target=lambda m: m.send(), args=(msg, )).start() response = {'success': True, 'invoice_id': invoice.id} return HttpResponse(json.dumps(response))
def send_invoices(): """ This cron task simply sends the Invoice *invoicing_gap* days before Subscription *expiry* """ service = get_service_instance() config = service.config now = datetime.now() invoicing_config = InvoicingConfig.objects.all()[0] connection = mail.get_connection() try: connection.open() except: logger.error(u"Connexion error", exc_info=True) count, total_amount = 0, 0 reminder_date_time = now + timedelta(days=invoicing_config.gap) subscription_qs = Subscription.objects.filter(monthly_cost__gt=0, expiry=reminder_date_time.date()) logger.debug("%d CR Operators candidate for invoice issuance." % subscription_qs.count()) for subscription in subscription_qs: if subscription.plan.raw_monthly_cost == 0: continue cr_service = subscription.service member = cr_service.member number = get_next_invoice_number() months_count = get_billing_cycle_months_count(subscription.billing_cycle) amount = subscription.monthly_cost * months_count ikwen_price = subscription.monthly_cost hosting = IkwenInvoiceItem(label=_('Website hosting'), price=ikwen_price, amount=subscription.monthly_cost) short_description = _("Continuous Rewarding Program for %s" % cr_service.domain) entry = InvoiceEntry(item=hosting, short_description=short_description, quantity=months_count, total=amount) entries = [entry] invoice = Invoice.objects.create(subscription=subscription, amount=amount, number=number, due_date=subscription.expiry, months_count=months_count, entries=entries) count += 1 total_amount += amount add_event(service, NEW_INVOICE_EVENT, member=member, object_id=invoice.id) paid_by_wallet_debit = False if cr_service.balance >= invoice.amount: pay_with_wallet_balance(invoice) paid_by_wallet_debit = True logger.debug("CR Invoice for %s paid by wallet debit" % cr_service.domain) subject, message, sms_text = get_invoice_generated_message(invoice) if member.email: invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id,)) if paid_by_wallet_debit: subject = _("Thanks for your payment") invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id,)) context = {'wallet_debit': True, 'invoice': invoice, 'config': config, 'invoice_url': invoice_url, 'cta': _("View invoice")} html_content = get_mail_content(subject, '', template_name='billing/mails/notice.html', extra_context=context) else: html_content = get_mail_content(subject, message, template_name='billing/mails/notice.html', extra_context={'invoice_url': invoice_url, 'cta': _("Pay now")}) # Sender is simulated as being no-reply@company_name_slug.com to avoid the mail # to be delivered to Spams because of origin check. sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = EmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" invoice.last_reminder = timezone.now() try: if msg.send(): logger.debug("1st Invoice reminder for %s sent to %s" % (cr_service.domain, member.email)) if not paid_by_wallet_debit: invoice.reminders_sent = 1 invoice.save() else: logger.error(u"Invoice #%s generated but mail not sent to %s" % (number, member.email), exc_info=True) except: logger.error(u"Connexion error on Invoice #%s to %s" % (number, member.email), exc_info=True) path_after = getattr(settings, 'BILLING_AFTER_NEW_INVOICE', None) if path_after: after_new_invoice = import_by_path(path_after) after_new_invoice(invoice) try: connection.close() finally: pass
def send_invoices(): """ This cron task simply sends the Invoice *invoicing_gap* days before Subscription *expiry* """ vendor, config, invoicing_config, connection = _init_base_vars() now = timezone.now() count, total_amount = 0, 0 reminder_date_time = now + timedelta(days=invoicing_config.gap) subscription_qs = Subscription.objects.filter(status=Subscription.ACTIVE, monthly_cost__gt=0, expiry__lt=reminder_date_time.date()) logger.debug("%d Service candidate for invoice issuance." % subscription_qs.count()) for subscription in subscription_qs: if getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue try: pending_invoice = Invoice.objects.get(subscription=subscription, status=Invoice.PENDING) logger.debug("%s found for %s. Skipping" % (pending_invoice, subscription)) continue # Continue if a Pending invoice for this Subscription is found except Invoice.DoesNotExist: pass member = subscription.member number = get_next_invoice_number() months_count = None if config.__dict__.get('separate_billing_cycle', True): months_count = get_billing_cycle_months_count(subscription.billing_cycle) amount = subscription.monthly_cost * months_count else: amount = subscription.product.cost path_before = getattr(settings, 'BILLING_BEFORE_NEW_INVOICE', None) if path_before: before_new_invoice = import_by_path(path_before) val = before_new_invoice(subscription) if val is not None: # Returning a not None value cancels the generation of a new Invoice for this Service continue entries = [] if type(subscription) is Service: from daraja.models import DARAJA partner = subscription.retailer if partner and partner.app.slug != DARAJA: retail_config = ApplicationRetailConfig.objects.get(partner=partner, app=subscription.app) ikwen_price = retail_config.ikwen_monthly_cost else: ikwen_price = subscription.monthly_cost hosting = IkwenInvoiceItem(label=_('Website hosting'), price=ikwen_price, amount=subscription.monthly_cost) short_description = _("Project %s" % subscription.domain) entry = InvoiceEntry(item=hosting, short_description=short_description, quantity=months_count, total=amount) entries = [entry] try: cr_op_profile = CROperatorProfile.objects.get(service=subscription, is_active=True) if cr_op_profile.monthly_cost > 0: plan = cr_op_profile.plan cr_monthly_cost = cr_op_profile.monthly_cost cr_item = IkwenInvoiceItem(label=_('Continuous Rewarding'), price=cr_monthly_cost, amount=cr_monthly_cost) short_description = plan.name cr_amount = months_count * cr_monthly_cost amount += cr_amount entry = InvoiceEntry(item=cr_item, short_description=short_description, quantity=months_count, total=cr_amount) entries.append(entry) except CROperatorProfile.DoesNotExist: pass invoice = Invoice.objects.create(subscription=subscription, member=subscription.member, amount=amount, number=number, due_date=subscription.expiry, months_count=months_count, entries=entries, last_reminder=now) count += 1 total_amount += amount add_event(vendor, NEW_INVOICE_EVENT, member=member, object_id=invoice.id) paid_by_wallet_debit = False if getattr(settings, 'IS_IKWEN', False) and subscription.balance >= invoice.amount: pay_with_wallet_balance(invoice) paid_by_wallet_debit = True logger.debug("Invoice for %s paid by wallet debit" % subscription.domain) subject, message, sms_text = get_invoice_generated_message(invoice) if member.email: activate(member.language) invoice_url = 'http://ikwen.com' + reverse('billing:invoice_detail', args=(invoice.id,)) if paid_by_wallet_debit: subject = _("Thanks for your payment") invoice_url = 'http://ikwen.com' + reverse('billing:invoice_detail', args=(invoice.id,)) context = {'wallet_debit': True, 'invoice': invoice, 'config': config, 'member_name': member.first_name, 'invoice_url': invoice_url, 'cta': _("View invoice")} html_content = get_mail_content(subject, '', template_name='billing/mails/wallet_debit_notice.html', extra_context=context) else: context = {'invoice_url': invoice_url, 'cta': _("Pay now"), 'currency': config.currency_symbol, 'service': vendor, 'config': config, 'logo': config.logo, 'project_name': vendor.project_name, 'company_name': config.company_name, 'member_name': member.first_name, 'invoice': invoice} html_content = get_mail_content(subject, message, template_name='billing/mails/notice.html', extra_context=context) # Sender is simulated as being no-reply@company_name_slug.com to avoid the mail # to be delivered to Spams because of origin check. sender = '%s <no-reply@%s>' % (config.company_name, vendor.domain) msg = EmailMessage(subject, html_content, sender, [member.email]) try: invoice_pdf_file = generate_pdf_invoice(invoicing_config, invoice) msg.attach_file(invoice_pdf_file) except: pass if paid_by_wallet_debit: msg.bcc = ['*****@*****.**'] msg.content_subtype = "html" invoice.last_reminder = timezone.now() try: if msg.send(): logger.debug("1st Invoice reminder for %s sent to %s" % (subscription.domain, member.email)) if not paid_by_wallet_debit: invoice.reminders_sent = 1 invoice.save() else: logger.error(u"Invoice #%s generated but mail not sent to %s" % (number, member.email), exc_info=True) except: logger.error(u"Connexion error on Invoice #%s to %s" % (number, member.email), exc_info=True) if sms_text: if member.phone: if config.sms_sending_method == Config.HTTP_API: send_sms(member.phone, sms_text) else: QueuedSMS.objects.create(recipient=member.phone, text=sms_text) path_after = getattr(settings, 'BILLING_AFTER_NEW_INVOICE', None) if path_after: after_new_invoice = import_by_path(path_after) after_new_invoice(invoice) try: connection.close() finally: if count > 0: report = SendingReport.objects.create(count=count, total_amount=total_amount) sudo_group = Group.objects.get(name=SUDO) add_event(vendor, INVOICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id)
def send_expiry_reminders(): """ This cron task simply sends the Invoice *invoicing_gap* days before Subscription *expiry* """ ikwen_service = get_service_instance() now = datetime.now() invoicing_config = InvoicingConfig.objects.all()[0] connection = mail.get_connection() try: connection.open() except: logger.error(u"Connexion error", exc_info=True) reminder_date_time = now + timedelta(days=invoicing_config.gap) for invoicing_config in InvoicingConfig.objects.exclude( service=ikwen_service): service = invoicing_config.service if service.status != Service.ACTIVE or invoicing_config.pull_invoice: continue db = service.database add_database(db) config = service.basic_config subscription_qs = Subscription.objects.using(db)\ .selected_related('member, product').filter(status=Subscription.ACTIVE, monthly_cost__gt=0, expiry=reminder_date_time.date()) count, total_amount = 0, 0 for subscription in subscription_qs: member = subscription.member number = get_next_invoice_number() months_count = get_billing_cycle_months_count( subscription.billing_cycle) amount = subscription.monthly_cost * months_count path_before = getattr(settings, 'BILLING_BEFORE_NEW_INVOICE', None) if path_before: before_new_invoice = import_by_path(path_before) val = before_new_invoice(subscription) if val is not None: # Returning a not None value cancels the generation of a new Invoice for this Service continue short_description = subscription.product.short_description item = InvoiceItem(label=_('Subscription'), amount=amount) entry = InvoiceEntry(item=item, short_description=short_description, quantity=months_count, total=amount) invoice = Invoice.objects.create(member=member, subscription=subscription, amount=amount, number=number, due_date=subscription.expiry, months_count=months_count, entries=[entry]) count += 1 total_amount += amount subject, message, sms_text = get_invoice_generated_message(invoice) balance, update = Balance.objects.using( WALLETS_DB_ALIAS).get_or_create(service_id=service.id) if member.email: if 0 < balance.mail_count < LOW_MAIL_LIMIT: notify_for_low_messaging_credit(service, balance) if balance.mail_count <= 0 and not getattr( settings, 'UNIT_TESTING', False): notify_for_empty_messaging_credit(service, balance) else: invoice_url = service.url + reverse( 'billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, message, service=service, template_name='billing/mails/notice.html', extra_context={ 'invoice_url': invoice_url, 'cta': _("Pay now"), 'currency': config.currency_symbol }) # Sender is simulated as being no-reply@company_name_slug.com to avoid the mail # to be delivered to Spams because of origin check. sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" msg.service = service invoice.last_reminder = timezone.now() try: with transaction.atomic(using=WALLETS_DB_ALIAS): if msg.send(): balance.mail_count -= 1 balance.save() logger.debug( "1st Invoice reminder for %s sent to %s" % (subscription, member.email)) else: logger.error( u"Invoice #%s generated but mail not sent to %s" % (number, member.email), exc_info=True) except: logger.error(u"Connexion error on Invoice #%s to %s" % (number, member.email), exc_info=True) if sms_text and member.phone: if 0 < balance.sms_count < LOW_SMS_LIMIT: notify_for_low_messaging_credit(service, balance) if balance.sms_count <= 0 and not getattr( settings, 'UNIT_TESTING', False): notify_for_empty_messaging_credit(service, balance) continue try: with transaction.atomic(using=WALLETS_DB_ALIAS): balance.sms_count -= 1 balance.save() phone = member.phone if len( member.phone) > 9 else '237' + member.phone send_sms(phone, sms_text, fail_silently=False) except: logger.error(u"SMS for invoice #%s not sent to %s" % (number, member.email), exc_info=True) path_after = getattr(settings, 'BILLING_AFTER_NEW_INVOICE', None) if path_after: after_new_invoice = import_by_path(path_after) after_new_invoice(invoice) if count > 0: report = SendingReport.objects.using(db).create( count=count, total_amount=total_amount) sudo_group = Group.objects.using(db).get(name=SUDO) add_event(ikwen_service, INVOICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id) try: connection.close() except: pass