def set_dara_stats(partner_original, service_partner, invoice, dara_earnings): set_counters(partner_original) increment_history_field(partner_original, 'turnover_history', dara_earnings) increment_history_field(partner_original, 'earnings_history', dara_earnings) increment_history_field(partner_original, 'transaction_count_history') set_counters(service_partner) increment_history_field(service_partner, 'earnings_history', dara_earnings) increment_history_field(service_partner, 'transaction_count_history') ikwen_service = get_service_instance() try: config = ikwen_service.config activate(partner_original.member.language) subject = _("New transaction on %s" % config.company_name) dashboard_url = 'https://daraja.ikwen.com' + reverse( 'daraja:dashboard') html_content = get_mail_content( subject, template_name='daraja/mails/new_transaction.html', extra_context={ 'currency_symbol': config.currency_symbol, 'amount': invoice.amount, 'dara_earnings': dara_earnings, 'tx_date': invoice.updated_on.strftime('%Y-%m-%d'), 'tx_time': invoice.updated_on.strftime('%H:%M:%S'), 'account_balance': partner_original.balance, 'dashboard_url': dashboard_url }) sender = 'ikwen Daraja <*****@*****.**>' msg = XEmailMessage(subject, html_content, sender, [partner_original.member.email]) msg.content_subtype = "html" if getattr(settings, 'UNIT_TESTING', False): msg.send() else: Thread(target=lambda m: m.send(), args=(msg, )).start() except: logger.error("Failed to notify %s Dara after follower purchase." % partner_original, exc_info=True)
def rerun_complete_revivals(debug=False): """ Re-run Revivals with status = COMPLETE to keep users engaged """ t0 = datetime.now() total_revival, total_mail = 0, 0 three_days_ago = timezone.now() - timedelta(days=3) for revival in Revival.objects.select_related('service').filter( status=COMPLETE, is_active=True): try: refreshed = Revival.objects.get(pk=revival.id) if refreshed.is_running: continue refreshed.is_running = True refreshed.save() total_revival += 1 except Revival.DoesNotExist: continue try: mail_renderer = import_by_path(revival.mail_renderer) kwargs = {} if revival.get_kwargs: get_kwargs = import_by_path(revival.get_kwargs) kwargs = get_kwargs(revival) except: revival.is_running = False revival.save() logger.error("Error when starting revival %s for %s" % (revival.mail_renderer, revival.service), exc_info=True) continue service = revival.service db = revival.service.database add_database(db) balance = Balance.objects.using(WALLETS_DB_ALIAS).get( service_id=service.id) if balance.mail_count == 0: revival.is_running = False revival.save() try: notify_for_empty_messaging_credit(service, balance) except: logger.error( "Failed to notify %s for empty messaging credit." % service, exc_info=True) continue if 0 < balance.mail_count < LOW_MAIL_LIMIT: try: notify_for_low_messaging_credit(service, balance) except: logger.error("Failed to notify %s for low messaging credit." % service, exc_info=True) tk = revival.model_name.split('.') model = get_model(tk[0], tk[1]) try: obj = model._default_manager.using(db).get(pk=revival.object_id) except ObjectDoesNotExist: revival.is_running = False revival.save() continue try: profile_tag = ProfileTag.objects.using(db).get( pk=revival.profile_tag_id) except: revival.is_running = False revival.save() continue set_counters_many(profile_tag) revival_local = Revival.objects.using(db).get(pk=revival.id) target_queryset = revival_local.target_set.select_related( 'member').filter(revived_on__lte=three_days_ago, revival_count__lt=MAX_AUTO_REWARDS) if target_queryset.count() == 0: revival.is_running = False revival.save() continue revival.run_on = timezone.now() revival.status = STARTED revival.save() connection = mail.get_connection() try: connection.open() except: revival.is_running = False revival.save() logger.error(u"Connexion error", exc_info=True) continue logger.debug("Running rerun_complete_revivals() %s for %s" % (revival.mail_renderer, revival.service)) for target in target_queryset.order_by('updated_on')[:MAX_BATCH_SEND]: if not debug and balance.mail_count == 0: revival.is_running = False revival.save() try: notify_for_empty_messaging_credit(service, balance) except: logger.error( "Failed to notify %s for empty messaging credit." % service, exc_info=True) break member = target.member if debug and not member.is_superuser: continue if member.language: activate(member.language) else: activate('en') if getattr(settings, 'UNIT_TESTING', False): sender, subject, html_content = mail_renderer( target, obj, revival, **kwargs) else: try: sender, subject, html_content = mail_renderer( target, obj, revival, **kwargs) except: logger.error( "Could not render mail for member %s, Revival %s, Obj: %s" % (member.email, revival.mail_renderer, str(obj)), exc_info=True) continue if not html_content: continue if debug: subject = 'Test remind - ' + subject msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" msg.type = XEmailObject.REVIVAL 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.revived_on = t0 target.save() total_mail += 1 increment_history_field(profile_tag, 'smart_revival_history') else: logger.error("Member %s not notified for Content %s" % (member.email, str(obj)), exc_info=True) except: logger.error("Member %s not notified for Content %s" % (member.email, str(obj)), exc_info=True) revival.is_running = False revival.progress += 1 revival.save() try: connection.close() except: revival.is_running = False revival.save() diff = datetime.now() - t0 logger.debug( "rerun_complete_revivals() run %d revivals. %d mails sent in %s" % (total_revival, total_mail, diff))
def notify_profiles(debug=False): """ Cron job that revive users by mail. Must be configured to run with a settings file having 'umbrella' as default database. :return: """ t0 = datetime.now() seven_hours_ago = t0 - timedelta(hours=7) total_revival, total_mail = 0, 0 for revival in Revival.objects.select_related('service').exclude( status=COMPLETE, is_active=False): try: refreshed = Revival.objects.get(pk=revival.id) if refreshed.is_running: continue refreshed.is_running = True refreshed.save() total_revival += 1 except Revival.DoesNotExist: continue try: mail_renderer = import_by_path(revival.mail_renderer) kwargs = {} if revival.get_kwargs: get_kwargs = import_by_path(revival.get_kwargs) kwargs = get_kwargs(revival) except: revival.is_running = False revival.save() logger.error("Error when starting revival %s for %s" % (revival.mail_renderer, revival.service), exc_info=True) 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: revival.is_running = False revival.save() try: notify_for_empty_messaging_credit(service, balance) except: logger.error( "Failed to notify %s for empty messaging credit." % service, exc_info=True) continue if 0 < balance.mail_count < LOW_MAIL_LIMIT: try: notify_for_low_messaging_credit(service, balance) except: logger.error("Failed to notify %s for low messaging credit." % service, exc_info=True) tk = revival.model_name.split('.') model = get_model(tk[0], tk[1]) try: obj = model._default_manager.using(db).get(pk=revival.object_id) except ObjectDoesNotExist: revival.is_running = False revival.save() continue try: profile_tag = ProfileTag.objects.using(db).get( pk=revival.profile_tag_id) except: revival.is_running = False revival.save() continue if revival.status != PENDING: revival.is_running = False revival.save() continue set_counters(profile_tag) revival_local = Revival.objects.using(db).get(pk=revival.id) if debug: member_queryset = Member.objects.using(db).filter( is_superuser=True) else: member_queryset = Member.objects.using(db).filter( date_joined__lte=seven_hours_ago) total = member_queryset.count() chunks = total / 500 + 1 target_count = 0 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: ref_tag = ProfileTag.objects.using(db).get(slug=REFERRAL) profile = MemberProfile.objects.using(db).create( member=member, tag_fk_list=[ref_tag.id]) if revival.profile_tag_id in profile.tag_fk_list: if debug: tag = ProfileTag.objects.using(db).get( pk=revival.profile_tag_id) print "Profiles matching on %s for member %s" % ( tag, member) if member.email: Target.objects.using(db).get_or_create( revival=revival_local, member=member) target_count += 1 if target_count == 0: revival.is_running = False revival.save() continue revival.run_on = datetime.now() revival.status = STARTED revival.total = revival_local.target_set.all().count() revival.save() connection = mail.get_connection() try: connection.open() except: revival.is_running = False revival.save() logger.error(u"Connexion error", exc_info=True) continue logger.debug("Running notify_profiles() %s for %s" % (revival.mail_renderer, revival.service)) for target in revival_local.target_set.select_related('member').filter( notified=False)[:MAX_BATCH_SEND]: if not debug and balance.mail_count == 0: revival.is_running = False revival.save() try: notify_for_empty_messaging_credit(service, balance) except: logger.error( "Failed to notify %s for empty messaging credit." % service, exc_info=True) break member = target.member if member.language: activate(member.language) else: activate('en') if getattr(settings, 'UNIT_TESTING', False): sender, subject, html_content = mail_renderer( target, obj, revival, **kwargs) else: try: sender, subject, html_content = mail_renderer( target, obj, revival, **kwargs) except: logger.error( "Could not render mail for member %s, Revival %s, Obj: %s" % (member.email, revival.mail_renderer, str(obj)), exc_info=True) continue if not html_content: continue if debug: subject = 'Test - ' + subject msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" msg.type = XEmailObject.REVIVAL 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.notified = True target.revived_on = t0 target.save() total_mail += 1 increment_history_field(profile_tag, 'smart_revival_history') else: logger.error("Member %s not notified for Content %s" % (member.email, str(obj)), exc_info=True) except: logger.error("Member %s not notified for Content %s" % (member.email, str(obj)), exc_info=True) revival.progress += 1 revival.save() revival.is_running = False if revival.progress > 0 and revival.progress >= revival.total: revival.status = COMPLETE revival.save() try: connection.close() except: revival.is_running = False revival.save() diff = datetime.now() - t0 logger.debug("notify_profiles() run %d revivals. %d mails sent in %s" % (total_revival, total_mail, diff))
def confirm_service_invoice_payment(request, *args, **kwargs): """ This view is run after successful user cashout "MOMO_AFTER_CHECKOUT" """ status = request.GET['status'] message = request.GET['message'] operator_tx_id = request.GET['operator_tx_id'] phone = request.GET['phone'] tx_id = kwargs['tx_id'] extra_months = int(kwargs['extra_months']) try: tx = MoMoTransaction.objects.using(WALLETS_DB_ALIAS).get(pk=tx_id, is_running=True) if not getattr(settings, 'DEBUG', False): tx_timeout = getattr(settings, 'IKWEN_PAYMENT_GATEWAY_TIMEOUT', 15) * 60 expiry = tx.created_on + timedelta(seconds=tx_timeout) if datetime.now() > expiry: return HttpResponse("Transaction %s timed out." % tx_id) tx.status = status tx.message = 'OK' if status == MoMoTransaction.SUCCESS else message tx.processor_tx_id = operator_tx_id tx.phone = phone tx.is_running = False tx.save() except: raise Http404("Transaction %s not found" % tx_id) if status != MoMoTransaction.SUCCESS: return HttpResponse("Notification for transaction %s received with status %s" % (tx_id, status)) invoice_id = tx.object_id amount = tx.amount signature = tx.task_id callback_signature = kwargs.get('signature') no_check_signature = request.GET.get('ncs') if getattr(settings, 'DEBUG', False): if not no_check_signature: if callback_signature != signature: return HttpResponse('Invalid transaction signature') else: if callback_signature != signature: return HttpResponse('Invalid transaction signature') now = datetime.now() ikwen_service = get_service_instance() invoice = Invoice.objects.get(pk=invoice_id) invoice.paid += amount invoice.status = Invoice.PAID invoice.save() payment = Payment.objects.create(invoice=invoice, method=Payment.MOBILE_MONEY, amount=amount, processor_tx_id=tx.processor_tx_id) service = invoice.service total_months = invoice.months_count + extra_months days = get_days_count(total_months) invoicing_config = get_invoicing_config_instance() if service.status == Service.SUSPENDED: days -= invoicing_config.tolerance # Catch-up days that were offered before service suspension expiry = now + timedelta(days=days) expiry = expiry.date() elif service.expiry: expiry = service.expiry + timedelta(days=days) else: expiry = now + timedelta(days=days) expiry = expiry.date() service.expiry = expiry service.status = Service.ACTIVE if invoice.is_one_off: service.version = Service.FULL try: support_bundle = SupportBundle.objects.get(type=SupportBundle.TECHNICAL, channel=SupportBundle.PHONE, cost=0) token = ''.join([random.SystemRandom().choice(string.digits) for i in range(6)]) support_expiry = now + timedelta(days=support_bundle.duration) SupportCode.objects.create(service=service, token=token, bundle=support_bundle, balance=support_bundle.quantity, expiry=support_expiry) logger.debug("Free Support Code created for %s" % service) except SupportBundle.DoesNotExist: logger.error("Free Support Code not created for %s" % service, exc_info=True) service.save() mean = tx.wallet is_early_payment = False if service.app.slug == 'kakocase' or service.app.slug == 'webnode': if invoice.due_date <= now.date(): is_early_payment = True refill_tsunami_messaging_bundle(service, is_early_payment) share_payment_and_set_stats(invoice, total_months, mean) member = service.member vendor = service.retailer vendor_is_dara = vendor and vendor.app.slug == DARAJA if vendor and not vendor_is_dara: add_database_to_settings(vendor.database) sudo_group = Group.objects.using(vendor.database).get(name=SUDO) else: vendor = ikwen_service sudo_group = Group.objects.using(UMBRELLA).get(name=SUDO) add_event(vendor, PAYMENT_CONFIRMATION, member=member, object_id=invoice.id) add_event(vendor, PAYMENT_CONFIRMATION, group_id=sudo_group.id, object_id=invoice.id) try: invoice_pdf_file = generate_pdf_invoice(invoicing_config, invoice) except: invoice_pdf_file = None if member.email: activate(member.language) invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id,)) subject, message, sms_text = get_payment_confirmation_message(payment, member) html_content = get_mail_content(subject, message, service=vendor, template_name='billing/mails/notice.html', extra_context={'member_name': member.first_name, 'invoice': invoice, 'cta': _("View invoice"), 'invoice_url': invoice_url, 'early_payment': is_early_payment}) sender = '%s <no-reply@%s>' % (vendor.config.company_name, ikwen_service.domain) msg = XEmailMessage(subject, html_content, sender, [member.email]) if vendor != ikwen_service and not vendor_is_dara: msg.service = vendor if invoice_pdf_file: msg.attach_file(invoice_pdf_file) msg.content_subtype = "html" if getattr(settings, 'UNIT_TESTING', False): msg.send() else: Thread(target=lambda m: m.send(), args=(msg,)).start() return HttpResponse("Notification received")
def after_save(self, request, obj, *args, **kwargs): object_id = kwargs.get('object_id') if object_id: return number = get_next_invoice_number() months_count = get_billing_cycle_months_count(obj.billing_cycle) try: amount = float(request.POST.get('amount')) except: amount = obj.monthly_cost * months_count product = obj.product if product: short_description = product.name else: short_description = request.POST.get('short_description', '---') obj.details = short_description obj.save() invoice_entries = [] item = InvoiceItem(label=_('Subscription'), amount=amount) entry = InvoiceEntry(item=item, short_description=short_description, quantity=months_count, total=amount) invoice_entries.append(entry) invoice = Invoice.objects.create(number=number, subscription=obj, amount=amount, months_count=months_count, due_date=obj.expiry, entries=invoice_entries, is_one_off=True) email = request.POST.get('email') member_id = request.POST.get('member_id') if member_id: member = Member.objects.get(pk=member_id) if member_id else None elif email: try: member = Member.objects.filter(email=email)[0] except: member = Member.objects.create_user(email, DEFAULT_GHOST_PWD, email=email, is_ghost=True) else: return obj.member = member obj.save() service = get_service_instance() config = service.config 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: 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.mail_count == 0 and not getattr(settings, 'UNIT_TESTING', False): try: notify_for_empty_messaging_credit(service, balance) except: logger.error( "Failed to notify %s for empty messaging credit." % service, exc_info=True) return if product: subject = _("Your invoice for subscription to %s" % product.name) else: if short_description != '---': subject = _("Your invoice for " + short_description) else: subject = _("Your invoice for subscription") try: currency = currencies(request)['CURRENCY'].symbol except: currency = config.currency_symbol invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, template_name='billing/mails/notice.html', 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, [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()
def confirm_correction_payment(request, *args, **kwargs): status = request.GET['status'] message = request.GET['message'] operator_tx_id = request.GET['operator_tx_id'] phone = request.GET['phone'] tx_id = kwargs['tx_id'] try: tx = MoMoTransaction.objects.using(WALLETS_DB_ALIAS).get(pk=tx_id) if not getattr(settings, 'DEBUG', False): tx_timeout = getattr(settings, 'IKWEN_PAYMENT_GATEWAY_TIMEOUT', 15) * 60 expiry = tx.created_on + timedelta(seconds=tx_timeout) if datetime.now() > expiry: return HttpResponse("Transaction %s timed out." % tx_id) except: raise Http404("Transaction %s not found" % tx_id) callback_signature = kwargs.get('signature') no_check_signature = request.GET.get('ncs') if getattr(settings, 'DEBUG', False): if not no_check_signature: if callback_signature != tx.task_id: return HttpResponse('Invalid transaction signature') else: if callback_signature != tx.task_id: return HttpResponse('Invalid transaction signature') if status != MoMoTransaction.SUCCESS: return HttpResponse( "Notification for transaction %s received with status %s" % (tx_id, status)) school = get_service_instance() school_config = SchoolConfig.objects.get(service=school) ikwen_charges = tx.amount * school_config.my_kids_share_rate / 100 teacher_earnings = tx.amount * (100 - school_config.my_kids_share_rate) / 100 tx.status = status tx.message = message tx.processor_tx_id = operator_tx_id tx.phone = phone tx.is_running = False tx.fees = ikwen_charges tx.save() # mean = tx.wallet # # amount = tx.amount - ikwen_charges # payment = Payment.objects.get(object_id=tx.object_id) assignment = Assignment.objects.get(title=tx.message.rstrip(' correction')) correction = assignment.assignmentcorrection parent = request.user subject = assignment.subject classroom = assignment.classroom teacher = subject.get_teacher(classroom=classroom) daraja = Application.objects.get(slug=DARAJA) try: dara_weblet = Service.objects.using(UMBRELLA).get( app=daraja, member=teacher.member) dara_db = dara_weblet.database dara_weblet_self = Service.objects.using(dara_db).get( pk=dara_weblet.id) set_counters(dara_weblet_self) increment_history_field(dara_weblet_self, 'turnover_history', teacher_earnings) increment_history_field(dara_weblet_self, 'earnings_history', teacher_earnings) increment_history_field(dara_weblet_self, 'transaction_count_history') # share_payment_and_set_stats(invoice, mean) except: logger.error("The teacher %s doesn't yet have a Dara account" % teacher) foulassi_weblet = get_service_instance() try: currency = Currency.active.default().symbol except: currency = school_config.currency_code body = _( "A student just purchase the correction of %(assignment_title)s in %(subject_name)s" % { 'assignment_title': assignment.title, 'subject_name': subject.name }) member_teacher = teacher.member subject = _("New correction of %s paid" % correction) cta_url = 'https://daraja.ikwen.com' + reverse('daraja:dashboard') html_content = get_mail_content( subject, template_name='foulassi/mails/correction_paid.html', extra_context={ 'teacher': member_teacher.first_name, 'classroom': classroom, 'cta_url': cta_url, 'subject': assignment.subject.name, 'assignment': assignment, 'currency': currency }) sender = '%s <no-reply@%s>' % (school_config.company_name, school.domain) msg = XEmailMessage(subject, html_content, sender, [member_teacher.email]) msg.bcc = ['*****@*****.**', '*****@*****.**'] msg.content_subtype = "html" try: msg.send() except Exception as e: logger.debug(e.message) send_push(foulassi_weblet, member_teacher, subject, body, cta_url) body = _( "You just pay the correction of %(assignment_title)s from %(subject_name)s " % { 'assignment_title': assignment.title, 'subject_name': subject.name }) subject = _("New correction paid") if parent.email: html_content = get_mail_content( subject, template_name='foulassi/mails/correction_paid_parent_notif.html', extra_context={ 'classroom': classroom, 'parent_name': parent.first_name, 'subject': assignment.subject.name, 'assignment': assignment, 'currency': currency }) sender = '%s <no-reply@%s>' % (school_config.company_name, school.domain) msg = XEmailMessage(subject, html_content, sender, [parent.email]) msg.bcc = ['*****@*****.**', '*****@*****.**'] msg.content_subtype = "html" try: msg.send() except Exception as e: logger.debug(e.message) send_push(foulassi_weblet, parent, subject, body, cta_url) return HttpResponse( "Notification for transaction %s received with status %s" % (tx_id, status))
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 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 confirm_service_invoice_payment(request, *args, **kwargs): """ This view is run after successful user cashout "MOMO_AFTER_CHECKOUT" """ tx = kwargs[ 'tx'] # Decoration with @momo_gateway_callback makes 'tx' available in kwargs extra_months = int(kwargs['extra_months']) payment = Payment.objects.select_related().get(pk=tx.object_id) payment.processor_tx_id = tx.processor_tx_id payment.save() invoice = payment.invoice now = datetime.now() ikwen_service = get_service_instance() invoice.paid += tx.amount if invoice.paid >= invoice.amount: invoice.status = Invoice.PAID invoice.save() service = invoice.service total_months = invoice.months_count + extra_months days = get_days_count(total_months) invoicing_config = get_invoicing_config_instance() if service.status == Service.SUSPENDED: days -= invoicing_config.tolerance # Catch-up days that were offered before service suspension expiry = now + timedelta(days=days) expiry = expiry.date() elif service.expiry: expiry = service.expiry + timedelta(days=days) else: expiry = now + timedelta(days=days) expiry = expiry.date() service.expiry = expiry service.status = Service.ACTIVE if invoice.is_one_off: service.version = Service.FULL try: support_bundle = SupportBundle.objects.get( type=SupportBundle.TECHNICAL, channel=SupportBundle.PHONE, cost=0) token = ''.join([ random.SystemRandom().choice(string.digits) for i in range(6) ]) support_expiry = now + timedelta(days=support_bundle.duration) SupportCode.objects.create(service=service, token=token, bundle=support_bundle, balance=support_bundle.quantity, expiry=support_expiry) logger.debug("Free Support Code created for %s" % service) except SupportBundle.DoesNotExist: logger.error("Free Support Code not created for %s" % service, exc_info=True) service.save() mean = tx.wallet is_early_payment = False if service.app.slug == 'kakocase' or service.app.slug == 'webnode': if invoice.due_date <= now.date(): is_early_payment = True refill_tsunami_messaging_bundle(service, is_early_payment) share_payment_and_set_stats(invoice, total_months, mean, tx) member = service.member vendor = service.retailer vendor_is_dara = vendor and vendor.app.slug == DARAJA if vendor and not vendor_is_dara: add_database_to_settings(vendor.database) sudo_group = Group.objects.using(vendor.database).get(name=SUDO) else: vendor = ikwen_service sudo_group = Group.objects.using(UMBRELLA).get(name=SUDO) add_event(vendor, PAYMENT_CONFIRMATION, member=member, object_id=invoice.id) add_event(vendor, PAYMENT_CONFIRMATION, group_id=sudo_group.id, object_id=invoice.id) try: invoice_pdf_file = generate_pdf_invoice(invoicing_config, invoice) except: invoice_pdf_file = None if member.email: activate(member.language) invoice_url = ikwen_service.url + reverse('billing:invoice_detail', args=(invoice.id, )) subject, message, sms_text = get_payment_confirmation_message( payment, member) html_content = get_mail_content( subject, message, service=vendor, template_name='billing/mails/notice.html', extra_context={ 'member_name': member.first_name, 'invoice': invoice, 'cta': _("View invoice"), 'invoice_url': invoice_url, 'early_payment': is_early_payment }) sender = '%s <no-reply@%s>' % (vendor.config.company_name, ikwen_service.domain) msg = XEmailMessage(subject, html_content, sender, [member.email]) if vendor != ikwen_service and not vendor_is_dara: msg.service = vendor if invoice_pdf_file: msg.attach_file(invoice_pdf_file) msg.content_subtype = "html" if getattr(settings, 'UNIT_TESTING', False): msg.send() else: Thread(target=lambda m: m.send(), args=(msg, )).start() return HttpResponse("Notification received")
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 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