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 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 notify_cashout_and_reset_counters(request, transaction, *args, **kwargs): """ Notifies IAO that request to cashout completed successfully and resets wallet balance accordingly :param request: :param transaction: MoMoTransaction object used to process this operation :return: """ cashout_request = CashOutRequest.objects.using('wallets').get( pk=transaction.object_id) cashout_request.status = CashOutRequest.PAID cashout_request.reference = transaction.processor_tx_id charges = cashout_request.amount * cashout_request.rate / 100 cashout_request.amount_paid = cashout_request.amount * ( 100 - cashout_request.rate) / 100 cashout_request.save() weblet = Service.objects.using(UMBRELLA).get(pk=transaction.service_id) wallet = OperatorWallet.objects.using('wallets').get( nonrel_id=weblet.id, provider=transaction.wallet) method = CashOutMethod.objects.using(UMBRELLA).get(slug=transaction.wallet) address = CashOutAddress.objects.using(UMBRELLA).get(service=weblet, method=method) with db_transaction.atomic(using='wallets'): queryset = MoMoTransaction.objects.using('wallets') \ .filter(created_on__gt=cashout_request.paid_on, type=MoMoTransaction.CASH_OUT, status=MoMoTransaction.SUCCESS, wallet=cashout_request.provider) iao = weblet.member if weblet.app.slug == DARAJA: dara = Dara.objects.get(member=iao) queryset = queryset.filter(dara_id=dara.id) if queryset.count() > 0: aggr = queryset.aggregate(Sum('dara_fees')) amount_successful = aggr['dara_fees__sum'] else: amount_successful = 0 else: queryset = queryset.filter(service_id=weblet.id) if queryset.count() > 0: aggr = queryset.aggregate(Sum('amount')) aggr_fees = queryset.aggregate(Sum('fees')) aggr_dara_fees = queryset.aggregate(Sum('dara_fees')) amount_successful = aggr['amount__sum'] - aggr_fees[ 'fees__sum'] - aggr_dara_fees['dara_fees__sum'] else: amount_successful = 0 wallet.balance = amount_successful wallet.save(using='wallets') if getattr(settings, 'TESTING', False): IKWEN_SERVICE_ID = getattr(settings, 'IKWEN_ID') ikwen_service = Service.objects.using(UMBRELLA).get( pk=IKWEN_SERVICE_ID) else: from ikwen.conf.settings import IKWEN_SERVICE_ID ikwen_service = Service.objects.using(UMBRELLA).get( pk=IKWEN_SERVICE_ID) sender = 'ikwen <*****@*****.**>' event_originator = ikwen_service add_event(event_originator, CASH_OUT_REQUEST_PAID, member=iao, object_id=cashout_request.id) subject = _("Money transfer confirmation") html_content = get_mail_content( subject, '', template_name='cashout/mails/payment_notice.html', extra_context={ 'cash_out_request': cashout_request, 'charges': charges, 'weblet': weblet, 'address': address, 'service': event_originator }) msg = XEmailMessage(subject, html_content, sender, [iao.email]) msg.service = ikwen_service msg.bcc = ['*****@*****.**', '*****@*****.**'] msg.content_subtype = "html" Thread(target=lambda m: m.send(), args=(msg, )).start() set_counters(ikwen_service) increment_history_field(ikwen_service, 'cash_out_history', cashout_request.amount) increment_history_field(ikwen_service, 'cash_out_count_history')
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