def product_do_checkout(request, *args, **kwargs): from echo.models import Balance from echo.utils import LOW_MAIL_LIMIT, notify_for_low_messaging_credit, notify_for_empty_messaging_credit tx = kwargs['tx'] invoice = Invoice.objects.get(pk=tx.object_id) member = invoice.member subscription = invoice.subscription subscription.status = Subscription.ACTIVE subscription.save() invoice.status = Invoice.PAID invoice.save() payment = Payment.objects.create(invoice=invoice, method=Payment.MOBILE_MONEY, amount=invoice.amount, processor_tx_id=tx.processor_tx_id) share_payment_and_set_stats(invoice, payment_mean_slug=tx.wallet) service = get_service_instance() config = service.config balance, update = Balance.objects.using(WALLETS_DB_ALIAS).get_or_create( service_id=service.id) if member and 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, )) subject, message, sms_text = get_payment_confirmation_message( payment, 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 }) 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() messages.success(request, _("Successful payment. Your subscription is now active.")) return HttpResponseRedirect(request.session['return_url'])
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_invoice_payment(request, *args, **kwargs): """ This function has no URL associated with it. It serves as ikwen setting "MOMO_AFTER_CHECKOUT" """ from echo.models import Balance from echo.utils import LOW_MAIL_LIMIT, notify_for_low_messaging_credit, notify_for_empty_messaging_credit tx = kwargs.get('transaction') now = datetime.now() service = get_service_instance() config = service.config invoicing_config = get_invoicing_config_instance() invoice_id = request.session['object_id'] amount = request.session['amount'] invoice = Invoice.objects.select_related('subscription').get(pk=invoice_id) invoice.paid += amount invoice.status = Invoice.PAID if invoicing_config.processing_fees_on_customer: invoice.processing_fees = config.ikwen_share_fixed invoice.save() payment = Payment.objects.create(invoice=invoice, method=Payment.MOBILE_MONEY, amount=amount, processor_tx_id=tx.processor_tx_id) subscription = invoice.subscription if invoicing_config.separate_billing_cycle: extra_months = request.session['extra_months'] total_months = invoice.months_count + extra_months days = get_days_count(total_months) else: extra_months = 0 days = invoice.subscription.product.duration total_months = None if subscription.status == Service.SUSPENDED: invoicing_config = get_invoicing_config_instance() days -= invoicing_config.tolerance # Catch-up days that were offered before service suspension expiry = now + timedelta(days=days) expiry = expiry.date() elif subscription.expiry: expiry = subscription.expiry + timedelta(days=days) else: expiry = now + timedelta(days=days) expiry = expiry.date() subscription.expiry = expiry subscription.status = Service.ACTIVE subscription.save() mean = request.session['mean'] share_payment_and_set_stats(invoice, total_months, mean) member = request.user sudo_group = Group.objects.using(UMBRELLA).get(name=SUDO) add_event(service, PAYMENT_CONFIRMATION, member=member, object_id=invoice.id) add_event(service, PAYMENT_CONFIRMATION, group_id=sudo_group.id, object_id=invoice.id) if invoicing_config.return_url: params = {'reference_id': subscription.reference_id, 'invoice_number': invoice.number, 'amount_paid': amount, 'processor_tx_id': tx.processor_tx_id, 'extra_months': extra_months} Thread(target=notify_event, args=(service, invoicing_config.return_url, params)).start() try: invoice_pdf_file = generate_pdf_invoice(invoicing_config, invoice) except: invoice_pdf_file = None 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: notify_for_empty_messaging_credit(service, balance) else: 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(payment, 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 = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" if invoice_pdf_file: msg.attach_file(invoice_pdf_file) balance.mail_count -= 1 balance.save() Thread(target=lambda m: m.send(), args=(msg,)).start() return HttpResponseRedirect(request.session['return_url'])
def cash_in(self, invoice, request): if not request.user.has_perm('billing.ik_cash_in'): return HttpResponse( json.dumps({'error': "You're not allowed here"})) service = get_service_instance() config = service.config invoicing_config = get_invoicing_config_instance() if invoice.status == Invoice.PAID: return HttpResponse(json.dumps({'error': "Invoice already paid"})) amount = request.GET.get('amount', invoice.amount) try: amount = float(amount) if amount <= 0: raise ValueError() except ValueError: return HttpResponse(json.dumps({'error': "Invalid amount"})) member = invoice.member payment = Payment.objects.create(invoice=invoice, amount=amount, method=Payment.CASH, cashier=request.user) response = {'success': True, 'payment': payment.to_dict()} try: aggr = Payment.objects.filter(invoice=invoice).aggregate( Sum('amount')) amount_paid = aggr['amount__sum'] except IndexError: amount_paid = 0 if invoicing_config.return_url: try: subscription = invoice.subscription extra_months = request.GET.get('extra_months', 0) params = { 'reference_id': subscription.reference_id, 'invoice_number': invoice.number, 'amount_paid': amount, 'extra_months': extra_months } Thread(target=notify_event, args=(service, invoicing_config.return_url, params)).start() except: notice = "%s: Could not notify endpoint %s after cash in" % ( service, invoicing_config.return_url) logger.error(notice, exc_info=True) total = amount + amount_paid invoice.paid += amount if total >= invoice.amount: invoice.status = Invoice.PAID try: subscription = invoice.subscription subscription.status = Subscription.ACTIVE days = get_days_count(invoice.months_count) subscription.expiry += timedelta(days=days) subscription.save() except AttributeError: pass invoice.save() set_counters(service) increment_history_field(service, 'turnover_history', amount) increment_history_field(service, 'earnings_history', amount) increment_history_field(service, 'transaction_earnings_history', amount) increment_history_field(service, 'invoice_earnings_history', amount) increment_history_field(service, 'transaction_count_history') increment_history_field(service, 'invoice_count_history') if member.email: from echo.models import Balance from echo.utils import notify_for_low_messaging_credit, LOW_MAIL_LIMIT, notify_for_empty_messaging_credit balance = Balance.objects.using(WALLETS_DB_ALIAS).get( service_id=service.id) 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) return HttpResponse(json.dumps(response)) subject, message, sms = get_payment_confirmation_message( payment, member) html_content = get_mail_content( subject, message, template_name='billing/mails/notice.html') sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" try: with transaction.atomic(using='wallets'): balance.mail_count -= 1 balance.save() Thread(target=lambda m: m.send(), args=(msg, )).start() except: pass return HttpResponse(json.dumps(response))
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 invite_member(service, member): sender = '%s <no-reply@%s>' % (service.project_name, service.domain) config = service.config try: invitation_message = config.__getattribute__('invitation_message') except AttributeError: return template_name = 'revival/mails/suggest_create_account.html' kwargs = get_join_reward_pack_list(service=service) join_reward_pack_list = kwargs['reward_pack_list'] if join_reward_pack_list: subject = _("Join us on ikwen and earn free coupons." % service.project_name) email_type = XEmailObject.REWARDING else: subject = _("Join our community on ikwen.") email_type = XEmailObject.REVIVAL if invitation_message or join_reward_pack_list: with transaction.atomic(using=WALLETS_DB_ALIAS): from echo.models import Balance from echo.utils import LOW_MAIL_LIMIT, notify_for_low_messaging_credit, 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 invitation_message = invitation_message.replace( '$client', member.first_name) extra_context = { 'member_name': member.first_name, 'join_reward_pack_list': join_reward_pack_list, 'invitation_message': invitation_message } try: html_content = get_mail_content(subject, service=service, template_name=template_name, extra_context=extra_context) msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" msg.type = email_type if not getattr(settings, 'UNIT_TESTING', False): balance.mail_count -= 1 balance.save() Thread(target=lambda m: m.send(), args=(msg, )).start() notice = "%s: Invitation sent message to member after ghost registration attempt" % service.project_name_slug logger.error(notice, exc_info=True) except: notice = "%s: Failed to send invite message to member after ghost registration attempt" % service.project_name_slug logger.error(notice, exc_info=True)
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 pull_invoice(request, *args, **kwargs): api_signature = request.POST.get('api_signature') try: service = Service.objects.get(api_signature=api_signature) except: notice = "Invalid API Signature." response = {'error': notice} return HttpResponse(json.dumps(response)) db = service.database add_database(db) invoicing_config, update = InvoicingConfig.objects.using(db).get_or_create( service=service) if not invoicing_config.pull_invoice: notice = "Cannot import when not explicitly configured to do so. You must activate " \ "'pull_invoice' in your platform configuration for import to work." response = {'error': notice} return HttpResponse(json.dumps(response)) lang = request.POST.get('lang', "en") activate(lang) missing = [] errors = [] do_pull = True try: number = request.POST['invoice_number'].strip() try: Invoice.objects.using(db).get(number=number) errors.append( "Invoice with number '%s' already exists. Invoice numbers must be unique." % number) do_pull = False except Invoice.DoesNotExist: pass except KeyError: missing.append('invoice_number') do_pull = False try: reference_id = request.POST['reference_id'] except KeyError: reference_id = None missing.append('reference_id') do_pull = False try: amount = request.POST['amount'] amount = float(amount) except KeyError: missing.append('amount') do_pull = False except ValueError: errors.append("Invalid amount '%s'. Expected valid float or int.") do_pull = False try: due_date = request.POST['due_date'] time.strptime(due_date, '%Y-%m-%d') except KeyError: missing.append('due_date') do_pull = False except ValueError: errors.append( "Invalid due_date '%s'. Expected valid date in the format 'YYYY-mm-dd'." ) do_pull = False try: quantity = request.POST['quantity'] except KeyError: missing.append('quantity') do_pull = False except ValueError: errors.append("Invalid quantity '%s'. Expected valid int.") do_pull = False quantity_unit = request.POST.get('quantity_unit', _("Month(s)")) currency_code = request.POST.get('currency_code', 'XAF') if reference_id: try: subscription = Subscription.objects.using(db).select_related( 'member', 'product').get(reference_id=reference_id) except Subscription.DoesNotExist: do_pull = False notice = "reference_id '%s' not found." % reference_id errors.append(notice) if not do_pull: response = {'error': '\n'.join(errors)} if missing: response[ 'missing'] = 'Following parameters are missing: ' + ', '.join( missing) return HttpResponse(json.dumps(response)) product = subscription.product if product: short_description = product.name else: short_description = request.POST.get('short_description', '---') invoice_entries = [] item = InvoiceItem(label=_('Subscription'), amount=amount) entry = InvoiceEntry(item=item, short_description=short_description, quantity=quantity, quantity_unit=quantity_unit, total=amount) invoice_entries.append(entry) invoice = Invoice.objects.using(db).create(number=number, member=subscription.member, subscription=subscription, amount=amount, months_count=quantity, due_date=due_date, entries=invoice_entries) config = service.config member = subscription.member if member.email: with transaction.atomic(using=WALLETS_DB_ALIAS): from echo.models import Balance from echo.utils import notify_for_low_messaging_credit, LOW_MAIL_LIMIT, notify_for_empty_messaging_credit balance, update = Balance.objects.using( WALLETS_DB_ALIAS).get_or_create(service_id=service.id) if 0 < balance.mail_count < LOW_MAIL_LIMIT: notify_for_low_messaging_credit(service, balance) if balance.mail_count <= 0: notify_for_empty_messaging_credit(service, balance) response = { 'success': True, 'warning': "Email not set due to empty mail credit" } return HttpResponse(json.dumps(response)) subject, message, sms_text = get_invoice_generated_message(invoice) try: currency = Currency.objects.using(db).get( code=currency_code).symbol except: try: currency = Currency.active.default().symbol except: currency = currency_code invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id, )) html_content = get_mail_content( subject, template_name='billing/mails/notice.html', service=service, extra_context={ 'invoice': invoice, 'member_name': member.first_name, 'invoice_url': invoice_url, 'cta': _("Pay now"), 'currency': currency }) sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = XEmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" balance.mail_count -= 1 balance.save() if getattr(settings, 'UNIT_TESTING', False): msg.send() else: Thread(target=lambda m: m.send(), args=(msg, )).start() response = {'success': True, 'invoice_id': invoice.id} return HttpResponse(json.dumps(response))
def send_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_order_confirmation_email(request, subject, buyer_name, buyer_email, order, message=None, reward_pack_list=None): service = get_service_instance() coupon_count = 0 if reward_pack_list: template_name = 'shopping/mails/order_notice_with_reward.html' for pack in reward_pack_list: coupon_count += pack.count else: template_name = 'shopping/mails/order_notice.html' with transaction.atomic(using=WALLETS_DB_ALIAS): 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 try: crcy = currencies(request)['CURRENCY'] html_content = get_mail_content(subject, template_name=template_name, extra_context={ 'buyer_name': buyer_name, 'order': order, 'message': message, 'IS_BANK': getattr( settings, 'IS_BANK', False), 'coupon_count': coupon_count, 'crcy': crcy }) sender = '%s <no-reply@%s>' % (service.project_name, service.domain) msg = XEmailMessage(subject, html_content, sender, [buyer_email]) bcc = [ email.strip() for email in service.config.notification_email.split(',') if email.strip() ] delcom = order.delivery_option.company if service != delcom: db = delcom.database add_database(db) try: delcom_config = OperatorProfile.objects.using(db).get( service=delcom) bcc += [ email.strip() for email in delcom_config.notification_email.split(',') if email.strip() ] bcc.append(delcom.member.email) except: pass bcc.append(service.member.email) msg.bcc = list(set(bcc)) msg.content_subtype = "html" if not getattr(settings, 'UNIT_TESTING', False): balance.mail_count -= len(msg.bcc) + 1 balance.save() Thread(target=lambda m: m.send(), args=(msg, )).start() except: logger.error("%s - Failed to send order confirmation email." % service, exc_info=True)
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