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 save_model(self, request, obj, form, change): # Send e-mail for manually generated Invoice upon creation super(SubscriptionAdmin, self).save_model(request, obj, form, change) if change: return # Send e-mail only if e-mail is a valid one. It will be agreed that if a client # does not have an e-mail. we create a fake e-mail that contains his phone number. # So e-mail containing phone number are invalid. member = obj.member service = get_service_instance() config = service.config subject, message, sms_text = get_subscription_registered_message(obj) # This helps differentiates from fake email accounts created as [email protected] if member.email.find(member.phone) < 0: add_event(service, SUBSCRIPTION_EVENT, member=member, object_id=obj.id, model=subscription_model_name) html_content = get_mail_content(subject, message, template_name='billing/mails/notice.html') # 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" Thread(target=lambda m: m.send(), args=(msg,)).start() 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 suspend_customers_services(): """ This cron task shuts down service and sends notice of Service suspension for Invoices which tolerance is exceeded. """ vendor, config, invoicing_config, connection = _init_base_vars() now = timezone.now() count, total_amount = 0, 0 deadline = now - timedelta(days=invoicing_config.tolerance) invoice_qs = Invoice.objects.filter(due_date__lte=deadline, status=Invoice.OVERDUE) print ("%d invoice(s) candidate for service suspension." % invoice_qs.count()) for invoice in invoice_qs: subscription = invoice.subscription if not subscription: continue if getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue _set_actual_vendor(subscription, vendor, config) invoice.status = Invoice.EXCEEDED invoice.save() action = getattr(settings, 'SERVICE_SUSPENSION_ACTION', None) if action: count += 1 total_amount += invoice.amount action = import_by_path(action) try: action(subscription) except: logger.error("Error while processing subscription %s" % str(subscription), exc_info=True) continue member = subscription.member add_event(vendor, SERVICE_SUSPENDED_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_service_suspension_message(invoice) if member.email: msg = _get_email_msg(member, subject, message, invoice, vendor, config) print ("Sending mail to %s" % member.email) try: if msg.send(): print ("Mail sent to %s" % member.email) else: print ("Sending mail to %s failed" % member.email) logger.error(u"Notice of suspension for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: print ("Sending mail to %s failed" % member.email) logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.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) 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, SUSPENSION_NOTICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id)
def send_invoice_overdue_notices(): """ This cron task sends notice of Invoice overdue """ vendor, config, invoicing_config, connection = _init_base_vars() now = timezone.now() count, total_amount = 0, 0 invoice_qs = Invoice.objects.filter(Q(status=Invoice.PENDING) | Q(status=Invoice.OVERDUE), due_date__lt=now, overdue_notices_sent__lt=3) print ("%d invoice(s) candidate for overdue notice." % invoice_qs.count()) for invoice in invoice_qs: subscription = invoice.subscription if subscription and getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue _set_actual_vendor(subscription, vendor, config) if invoice.last_overdue_notice: diff = now - invoice.last_overdue_notice else: invoice.status = Invoice.OVERDUE invoice.save() if not invoice.last_overdue_notice or diff.days == invoicing_config.overdue_delay: print ("Processing invoice for Service %s" % str(invoice.subscription)) count += 1 total_amount += invoice.amount member = invoice.subscription.member add_event(vendor, OVERDUE_NOTICE_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_invoice_overdue_message(invoice) if member.email: msg = _get_email_msg(member, subject, message, invoice, vendor, config) invoice.last_overdue_notice = timezone.now() print ("Sending mail to %s" % member.email) try: if msg.send(): print ("Mail sent to %s" % member.email) invoice.overdue_notices_sent += 1 else: print ("Sending mail to %s failed" % member.email) logger.error(u"Overdue notice for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: print ("Sending mail to %s failed" % member.email) logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.number, member.email), exc_info=True) invoice.save() 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) 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, OVERDUE_NOTICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id)
def send_invoice_reminders(): """ This cron task sends Invoice reminder notice to the client if unpaid """ vendor, config, invoicing_config, connection = _init_base_vars() now = timezone.now() count, total_amount = 0, 0 invoice_qs = Invoice.objects.filter(status=Invoice.PENDING, due_date__gte=now.date(), last_reminder__isnull=False) print ("%d invoice(s) candidate for reminder." % invoice_qs.count()) for invoice in invoice_qs: subscription = invoice.subscription if getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue _set_actual_vendor(subscription, vendor, config) diff = now - invoice.last_reminder if diff.days == invoicing_config.reminder_delay: print ("Processing invoice for Service %s" % str(invoice.subscription)) count += 1 total_amount += invoice.amount member = invoice.member add_event(vendor, INVOICE_REMINDER_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_invoice_reminder_message(invoice) if member.email: msg = _get_email_msg(member, subject, message, invoice, vendor, config) invoice.last_reminder = timezone.now() print ("Sending mail to %s" % member.email) try: if msg.send(): print ("Mail sent to %s" % member.email) invoice.reminders_sent += 1 else: print ("Sending mail to %s failed" % member.email) logger.error(u"Reminder mail for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: print ("Sending mail to %s failed" % member.email) logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.number, member.email), exc_info=True) invoice.save() 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) 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, REMINDERS_SENT_EVENT, group_id=sudo_group.id, object_id=report.id)
def get(self, request, *args, **kwargs): action = request.GET.get('action') if action == 'invite_school': # Send a request to see his children online to the school service = get_service_instance() member = request.user school_id = request.GET['school_id'] kids_details = request.GET['kids_details'] school = Service.objects.get(pk=school_id) db = school.database add_database(db) event_type, change = EventType.objects.using(db) \ .get_or_create(codename=PARENT_REQUEST_KID, renderer='ikwen_foulassi.foulassi.events.render_parent_request_kid') kid_request = KidRequest.objects.using(db).create(parent=member, kids_details=kids_details) Event.objects.using(db).create(type=event_type, object_id_list=[kid_request.id]) try: if member.gender == FEMALE: parent_name = _("Mrs %s" % member.full_name) elif member.gender == MALE: parent_name = _("Mr %s" % member.full_name) else: # Unknown gender parent_name = _("The parent %s" % member.full_name) subject = _("I would like to follow my kids in your school on Foulassi.") cta_url = school.url + reverse('foulassi:event_list') html_content = get_mail_content(subject, template_name='foulassi/mails/invite_school.html', extra_context={'parent': member, 'kids_details': kids_details, 'IKWEN_MEDIA_URL': MEDIA_URL, 'MEMBER_AVATAR': MEMBER_AVATAR, 'cta_url': cta_url}) sender = '%s via ikwen Foulassi <no-reply@%s>' % (parent_name, service.domain) msg = EmailMessage(subject, html_content, sender, [school.config.contact_email.strip()]) msg.content_subtype = "html" msg.cc = ["*****@*****.**"] if member.email: msg.extra_headers = {'Reply-To': member.email} Thread(target=lambda m: m.send(), args=(msg,)).start() sms_text = _("%(parent_name)s would like to follow his kid(s) below on ikwen Foulassi:\n" "%(kids_details)s" % {'parent_name': parent_name, 'kids_details': kids_details}) if member.phone: if len(member.phone) == 9: member.phone = '237' + member.phone send_sms(member.phone, sms_text) except: pass return HttpResponse(json.dumps({'success': True}, 'content-type: text/json')) return super(SearchSchool, self).get(request, *args, **kwargs)
def send_billed_sms(weblet, parent_list, text): config = weblet.config balance = check_messaging_balance(weblet) for parent in parent_list: member = parent.member phone = member.phone activate(member.language) page_count = count_pages(text) if balance.sms_count < page_count: break phone = slugify(phone).replace('-', '') if len(phone) == 9: phone = '237' + phone try: with db_transaction.atomic(using='wallets'): balance.sms_count -= page_count balance.save() send_sms(phone, text, get_sms_label(config)) except: pass
def send_code(self, request, new_code=False): service = get_service_instance() member = request.user code = ''.join( [random.SystemRandom().choice(string.digits) for _ in range(4)]) do_send = False try: current = request.session[ 'code'] # Test whether there's a pending code in session if new_code: request.session['code'] = code do_send = True except KeyError: request.session['code'] = code do_send = True if do_send: phone = slugify(member.phone).replace('-', '') if len(phone) == 9: phone = '237' + phone # This works only for Cameroon text = 'Your %s confirmation code is %s' % (service.project_name, code) try: main_link = service.config.sms_api_script_url if not main_link: main_link = getattr(settings, 'SMS_MAIN_LINK', None) send_sms(phone, text, script_url=main_link, fail_silently=False) except: fallback_link = getattr(settings, 'SMS_FALLBACK_LINK', None) send_sms(phone, text, script_url=fallback_link, fail_silently=False)
def notify_profiles(debug=False): t0 = datetime.now() total_revival, total_mail, total_sms = 0, 0, 0 logger.debug("Starting cyclic revival") today = t0.date() queryset = CyclicRevival.objects.select_related('service')\ .filter(next_run_on=today, hour_of_sending=t0.hour, end_on__gt=today, is_active=True) for revival in queryset: try: refreshed = CyclicRevival.objects.get(pk=revival.id) if refreshed.is_running: continue refreshed.is_running = True refreshed.save() total_revival += 1 except CyclicRevival.DoesNotExist: continue service = revival.service db = service.database add_database(db) balance = Balance.objects.using(WALLETS_DB_ALIAS).get( service_id=service.id) if balance.mail_count == 0 and balance.sms_count == 0: try: notify_for_empty_messaging_credit(service, balance) except: revival.is_running = False revival.save() logger.error( "Failed to notify %s for empty messaging credit." % service, exc_info=True) continue if 0 < balance.mail_count < LOW_MAIL_LIMIT or 0 < balance.sms_count < LOW_SMS_LIMIT: try: notify_for_low_messaging_credit(service, balance) except: revival.is_running = False revival.save() logger.error("Failed to notify %s for low messaging credit." % service, exc_info=True) label = get_sms_label(service.config) notified_empty_mail_credit = False notified_empty_sms_credit = False if debug: member_queryset = Member.objects.using(db).filter( is_superuser=True) else: member_queryset = Member.objects.using(db).all() total = member_queryset.count() try: profile_tag = ProfileTag.objects.using(db).get( pk=revival.profile_tag_id) except ProfileTag.DoesNotExist: revival.delete() continue set_counters(profile_tag) revival_local = CyclicRevival.objects.using(db).get(pk=revival.id) chunks = total / 500 + 1 for i in range(chunks): start = i * 500 finish = (i + 1) * 500 for member in member_queryset.order_by( 'date_joined')[start:finish]: try: profile = MemberProfile.objects.using(db).get( member=member) except MemberProfile.DoesNotExist: continue match = set(profile.tag_fk_list) & {profile_tag.id} if len(match) > 0: if member.email: CyclicTarget.objects.using(db).get_or_create( revival=revival_local, member=member) revival.set_next_run_date() connection = mail.get_connection() try: connection.open() except: logger.error(u"Connexion error", exc_info=True) break logger.debug("Running revival %s for %s" % (revival.mail_subject, revival.service)) for target in revival_local.cyclictarget_set.select_related('member'): member = target.member if member.language: activate(member.language) else: activate('en') subject = revival.mail_subject message = revival.mail_content.replace('$client', member.first_name) sender = '%s <no-reply@%s>' % (service.project_name, service.domain) try: currency = Currency.objects.using(using=db).get(is_base=True) except Currency.DoesNotExist: currency = None product_list = [] if service.app.slug == 'kakocase': product_list = Product.objects.using(db).filter( pk__in=revival.items_fk_list) extra_context = { 'revival': revival, 'currency': currency, 'media_url': getattr(settings, 'CLUSTER_MEDIA_URL') + service.project_name_slug + '/', 'product_list': product_list } try: html_content = get_mail_content( subject, message, template_name='revival/mails/default.html', service=service, extra_context=extra_context) except: logger.error( "Could not render mail for member %s, Cyclic revival on %s" % (member.username, profile_tag), exc_info=True) break msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" msg.type = XEmailObject.REVIVAL if balance.mail_count == 0 and not notified_empty_mail_credit: notify_for_empty_messaging_credit(service, balance) notified_empty_mail_credit = True else: try: with transaction.atomic(using=WALLETS_DB_ALIAS): if not debug: balance.mail_count -= 1 balance.save() if msg.send(): target.revival_count += 1 target.save() total_mail += 1 increment_history_field( profile_tag, 'cyclic_revival_mail_history') else: logger.error( "Cyclic revival with subject %s not sent for member %s" % (subject, member.email), exc_info=True) except: logger.error( "Critical error in revival %s when processing Mail sending for member %s" % (revival.id, member.email), exc_info=True) if revival.sms_text: if balance.sms_count == 0 and not notified_empty_sms_credit: notify_for_empty_messaging_credit(service, balance) notified_empty_sms_credit = True else: sms_text = revival.sms_text.replace( '$client', member.first_name) page_count = count_pages(sms_text) try: with transaction.atomic(using=WALLETS_DB_ALIAS): balance.sms_count -= page_count balance.save() send_sms(recipient=member.phone, text=sms_text, fail_silently=False) total_sms += 1 increment_history_field( profile_tag, 'cyclic_revival_sms_history') SMSObject.objects.create(recipient=member.phone, text=sms_text, label=label) except: logger.error( "Critical error in revival %s when processing SMS sending for member %s" % (revival.id, member.email), exc_info=True) if balance.mail_count == 0 and balance.sms_count == 0: break revival.is_running = False revival.save() try: connection.close() except: pass diff = datetime.now() - t0 logger.debug("%d revivals run. %d mails and %d SMS sent in %s" % (total_revival, total_mail, total_sms, diff))
def send_order_confirmation_sms(buyer_name, buyer_phone, order): service = get_service_instance() config = service.config script_url = getattr(settings, 'SMS_API_SCRIPT_URL', config.sms_api_script_url) if not script_url: return details_max_length = 90 details = order.get_products_as_string() if len(details) > details_max_length: tokens = details.split(',') while len(details) > details_max_length: tokens = tokens[:-1] details = ','.join(tokens) details += ' ...' client_text = _("Order successful:\n" "%(details)s\n" "Your RCC is %(rcc)s\n" "Thank you." % {'details': details, 'rcc': order.rcc.upper()}) iao_text = "Order from %(buyer_name)s:\n" \ "%(details)s\n" \ "RCC: %(rcc)s" % {'buyer_name': buyer_name[:20], 'details': details, 'rcc': order.rcc.upper()} iao_phones = [phone.strip() for phone in config.notification_phone.split(',') if phone.strip()] client_page_count = count_pages(client_text) iao_page_count = count_pages(iao_text) needed_credit = client_page_count + iao_page_count * len(iao_phones) balance, update = Balance.objects.using(WALLETS_DB_ALIAS).get_or_create(service_id=service.id) if needed_credit < balance.mail_count < LOW_SMS_LIMIT: try: notify_for_low_messaging_credit(service, balance) except: logger.error("Failed to notify %s for low messaging credit." % service, exc_info=True) if balance.sms_count < needed_credit: try: notify_for_empty_messaging_credit(service, balance) except: logger.error("Failed to notify %s for empty messaging credit." % service, exc_info=True) return buyer_phone = buyer_phone.strip() buyer_phone = slugify(buyer_phone).replace('-', '') if buyer_phone and len(buyer_phone) == 9: buyer_phone = '237' + buyer_phone # This works only for Cameroon try: with transaction.atomic(using=WALLETS_DB_ALIAS): balance.sms_count -= client_page_count balance.save() send_sms(buyer_phone, client_text, script_url=script_url, fail_silently=False) except: pass for phone in iao_phones: phone = slugify(phone).replace('-', '') if len(phone) == 9: phone = '237' + phone try: with transaction.atomic(using=WALLETS_DB_ALIAS): balance.sms_count -= iao_page_count balance.save() send_sms(phone, iao_text, script_url=script_url, fail_silently=False) except: pass
def run_test(self, request): from echo.utils import count_pages from echo.models import Balance, SMSObject revival_id = request.GET['revival_id'] test_email_list = request.GET['test_email_list'].split(',') test_phone_list = request.GET['test_phone_list'].split(',') service = get_service_instance() revival = CyclicRevival.objects.using(UMBRELLA).get(pk=revival_id) balance = Balance.objects.using(WALLETS_DB_ALIAS).get( service_id=service.id) if balance.mail_count == 0 and balance.sms_count == 0: response = {'error': 'Insufficient Email and SMS credit'} return HttpResponse(json.dumps(response)) connection = mail.get_connection() try: connection.open() except: response = { 'error': 'Failed to connect to mail server. Please check your internet' } return HttpResponse(json.dumps(response)) config = service.config warning = [] for email in test_email_list: if balance.mail_count == 0: warning.append('Insufficient email Credit') break email = email.strip() subject = revival.mail_subject try: member = Member.objects.filter(email=email)[0] message = revival.mail_content.replace('$client', member.first_name) except: message = revival.mail_content.replace('$client', _("<Unknown>")) sender = '%s <no-reply@%s>' % (config.company_name, service.domain) media_url = ikwen_settings.CLUSTER_MEDIA_URL + service.project_name_slug + '/' product_list = [] if service.app.slug == 'kakocase': product_list = Product.objects.filter( pk__in=revival.items_fk_list) try: currency = Currency.objects.get(is_base=True) except Currency.DoesNotExist: currency = None html_content = get_mail_content( subject, message, template_name='revival/mails/default.html', extra_context={ 'media_url': media_url, 'product_list': product_list, 'revival': revival, 'currency': currency }) msg = EmailMessage(subject, html_content, sender, [email]) msg.content_subtype = "html" with transaction.atomic(using='wallets'): try: balance.mail_count -= 1 balance.save() if not msg.send(): transaction.rollback(using='wallets') warning.append('Mail not sent to %s' % email) except: transaction.rollback(using='wallets') try: connection.close() except: pass if revival.sms_text: label = get_sms_label(config) for phone in test_phone_list: if balance.sms_count == 0: warning.append('Insufficient SMS Credit') break try: member = Member.objects.filter(phone=phone)[0] sms_text = revival.sms_text.replace( '$client', member.first_name) except: sms_text = revival.mail_content.replace('$client', "") page_count = count_pages(sms_text) with transaction.atomic(): try: balance.sms_count -= page_count balance.save() phone = phone.strip() if len(phone) == 9: phone = '237' + phone send_sms(recipient=phone, text=revival.sms_text, fail_silently=False) SMSObject.objects.create(recipient=phone, text=revival.sms_text, label=label) except: transaction.rollback() warning.append('SMS not sent to %s' % phone) response = {'success': True, 'warning': warning} return HttpResponse(json.dumps(response))
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 suspend_customers_services(): """ This cron task shuts down service and sends notice of Service suspension for Invoices which tolerance is exceeded. """ vendor = get_service_instance() config = vendor.config now = timezone.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 deadline = now - timedelta(days=invoicing_config.tolerance) invoice_qs = Invoice.objects.filter(due_date__lte=deadline, status=Invoice.OVERDUE) print("%d invoice(s) candidate for service suspension." % invoice_qs.count()) for invoice in invoice_qs: subscription = invoice.subscription if not subscription: continue if getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue if subscription.retailer: vendor = subscription.retailer config = vendor.config invoice.status = Invoice.EXCEEDED invoice.save() action = getattr(settings, 'SERVICE_SUSPENSION_ACTION', None) if action: count += 1 total_amount += invoice.amount action = import_by_path(action) try: action(subscription) except: logger.error("Error while processing subscription %s" % str(subscription), exc_info=True) continue member = subscription.member add_event(vendor, SERVICE_SUSPENDED_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_service_suspension_message( invoice) if member.email: activate(member.language) invoice_url = 'http://ikwen.com' + reverse( 'billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, message, template_name='billing/mails/notice.html', extra_context={ 'member_name': member.first_name, 'invoice': invoice, '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 }) # 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]) msg.content_subtype = "html" print("Sending mail to %s" % member.email) try: if msg.send(): print("Mail sent to %s" % member.email) else: print("Sending mail to %s failed" % member.email) logger.error( u"Notice of suspension for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: print("Sending mail to %s failed" % member.email) logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.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) 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, SUSPENSION_NOTICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id)
def send_invoice_overdue_notices(): """ This cron task sends notice of Invoice overdue """ vendor = get_service_instance() config = vendor.config now = timezone.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 invoice_qs = Invoice.objects.filter(Q(status=Invoice.PENDING) | Q(status=Invoice.OVERDUE), due_date__lt=now, overdue_notices_sent__lt=3) print("%d invoice(s) candidate for overdue notice." % invoice_qs.count()) for invoice in invoice_qs: subscription = invoice.subscription if subscription and getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue if subscription.retailer: vendor = subscription.retailer config = vendor.config if invoice.last_overdue_notice: diff = now - invoice.last_overdue_notice else: invoice.status = Invoice.OVERDUE invoice.save() if not invoice.last_overdue_notice or diff.days == invoicing_config.overdue_delay: print("Processing invoice for Service %s" % str(invoice.subscription)) count += 1 total_amount += invoice.amount member = invoice.subscription.member add_event(vendor, OVERDUE_NOTICE_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_invoice_overdue_message(invoice) if member.email: activate(member.language) invoice_url = 'http://ikwen.com' + reverse( 'billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, message, template_name='billing/mails/notice.html', extra_context={ 'member_name': member.first_name, 'invoice': invoice, '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 }) # 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]) msg.content_subtype = "html" invoice.last_overdue_notice = timezone.now() print("Sending mail to %s" % member.email) try: if msg.send(): print("Mail sent to %s" % member.email) invoice.overdue_notices_sent += 1 else: print("Sending mail to %s failed" % member.email) logger.error( u"Overdue notice for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: print("Sending mail to %s failed" % member.email) logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.number, member.email), exc_info=True) invoice.save() 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) 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, OVERDUE_NOTICES_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
def suspend_subscriptions(): """ This cron task shuts down service and sends notice of Service suspension for Invoices which tolerance is exceeded. """ ikwen_service = get_service_instance() now = datetime.now() connection = mail.get_connection() try: connection.open() except: logger.error(u"Connexion error", exc_info=True) for invoicing_config in InvoicingConfig.objects.all(): service = invoicing_config.service if service.status != Service.ACTIVE: continue config = service.basic_config db = service.database add_database(db) deadline = now - timedelta(days=invoicing_config.tolerance) invoice_qs = Invoice.objects.using(db).select_related('subscription')\ .filter(due_date__lte=deadline, status=Invoice.OVERDUE) count, total_amount = 0, 0 for invoice in invoice_qs: due_date = invoice.due_date due_datetime = datetime(due_date.year, due_date.month, due_date.day) diff = now - due_datetime subscription = invoice.subscription tolerance = subscription.tolerance if diff.days < tolerance: continue invoice.status = Invoice.EXCEEDED invoice.save() count += 1 total_amount += invoice.amount subscription.status = Subscription.SUSPENDED subscription.save() member = subscription.member add_event(service, SERVICE_SUSPENDED_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_service_suspension_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={ 'member_name': member.first_name, 'invoice': invoice, '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.service = service msg.content_subtype = "html" try: with transaction.atomic(using=WALLETS_DB_ALIAS): if msg.send(): balance.mail_count -= 1 balance.save() else: logger.error( u"Notice of suspension for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: print "Sending mail to %s failed" % member.email logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.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 overdue notice for invoice #%s not sent to %s" % (invoice.number, member.phone), exc_info=True) 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, SUSPENSION_NOTICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id) try: connection.close() except: pass
def save_formset(self, request, form, formset, change): instances = formset.save(commit=False) total_amount = 0 instance = None for instance in instances: if isinstance(instance, Payment): total_amount += instance.amount if instance.cashier: # Instances with non null cashier are those that previously existed. # setting them to None allows to ignore them at the end of the loop # since we want to undertake action only for newly added Payment instance = None continue instance.cashier = request.user instance.save() if instance: # TODO: Check if the last payment is newly added # Notice email is sent only for the last saved Payment, # this is why this code is not run within the "for" loop above member = instance.invoice.subscription.member service = get_service_instance() config = service.config invoice = instance.invoice s = invoice.service days = get_days_count(invoice.months_count) if s.status == Service.SUSPENDED: invoicing_config = get_invoicing_config_instance() days -= invoicing_config.tolerance # Catch-up days that were offered before service suspension expiry = datetime.now() + timedelta(days=days) expiry = expiry.date() elif s.expiry: expiry = s.expiry + timedelta(days=days) else: expiry = datetime.now() + timedelta(days=days) expiry = expiry.date() s.expiry = expiry s.status = Service.ACTIVE if invoice.is_one_off: s.version = Service.FULL s.save() share_payment_and_set_stats(invoice, invoice.months_count) if s.retailer: db = s.retailer.database Service.objects.using(db).filter(pk=s.id).update( expiry=s.expiry, status=s.status, version=s.version) sudo_group = Group.objects.using(UMBRELLA).get(name=SUDO) add_event(service, PAYMENT_CONFIRMATION, group_id=sudo_group.id, object_id=invoice.id) add_event(service, PAYMENT_CONFIRMATION, member=member, object_id=invoice.id) subject, message, sms_text = get_payment_confirmation_message( instance, member) if member.email: try: currency = Currency.active.default().symbol except: currency = config.currency_code invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id, )) subject, message, sms_text = get_payment_confirmation_message( instance, member) html_content = get_mail_content( subject, message, template_name='billing/mails/notice.html', extra_context={ 'member_name': member.first_name, 'invoice': invoice, 'cta': _("View invoice"), 'invoice_url': invoice_url, 'currency': currency }) sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = EmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" Thread(target=lambda m: m.send(), args=(msg, )).start() 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) if total_amount >= invoice.amount: invoice.status = AbstractInvoice.PAID invoice.save()
def send_invoice_overdue_notices(): """ This cron task sends notice of Invoice overdue """ ikwen_service = get_service_instance() now = datetime.now() connection = mail.get_connection() try: connection.open() except: logger.error(u"Connexion error", exc_info=True) for invoicing_config in InvoicingConfig.objects.exclude( service=ikwen_service): service = invoicing_config.service if service.status != Service.ACTIVE: continue config = service.basic_config db = service.database add_database(db) invoice_qs = Invoice.objects.using(db).select_related('subscription')\ .filter(Q(status=Invoice.PENDING) | Q(status=Invoice.OVERDUE), due_date__lt=now, overdue_notices_sent__lt=3) count, total_amount = 0, 0 for invoice in invoice_qs: if invoice.last_overdue_notice: diff = now - invoice.last_overdue_notice else: invoice.status = Invoice.OVERDUE invoice.save() if invoice.last_overdue_notice and diff.days != invoicing_config.overdue_delay: continue count += 1 total_amount += invoice.amount member = invoice.subscription.member add_event(service, OVERDUE_NOTICE_EVENT, member=member, object_id=invoice.id) subject, message, sms_text = get_invoice_overdue_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 = 'http://ikwen.com' + reverse( 'billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, message, service=service, template_name='billing/mails/notice.html', extra_context={ 'member_name': member.first_name, 'invoice': invoice, '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.service = service msg.content_subtype = "html" invoice.last_overdue_notice = timezone.now() try: with transaction.atomic(using=WALLETS_DB_ALIAS): if msg.send(): invoice.overdue_notices_sent += 1 balance.mail_count -= 1 balance.save() else: logger.error( u"Overdue notice for Invoice #%s not sent to %s" % (invoice.number, member.email), exc_info=True) except: logger.error(u"Connexion error on Invoice #%s to %s" % (invoice.number, member.email), exc_info=True) invoice.save() 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 overdue notice for invoice #%s not sent to %s" % (invoice.number, member.phone), exc_info=True) 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, OVERDUE_NOTICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id) try: connection.close() except: pass