def save_formset(self, request, form, formset, change): instances = formset.save(commit=False) total_amount = 0 instance = None for instance in instances: if isinstance(instance, Payment): total_amount += instance.amount if instance.cashier: # Instances with non null cashier are those that previously existed. # setting them to None allows to ignore them at the end of the loop # since we want to undertake action only for newly added Payment instance = None continue instance.cashier = request.user instance.save() if instance: # TODO: Check if the last payment is newly added # Notice email is sent only for the last saved Payment, # this is why this code is not run within the "for" loop above member = instance.invoice.subscription.member service = get_service_instance() config = service.config invoice = instance.invoice s = invoice.service days = get_days_count(invoice.months_count) if s.status == Service.SUSPENDED: invoicing_config = get_invoicing_config_instance() days -= invoicing_config.tolerance # Catch-up days that were offered before service suspension expiry = datetime.now() + timedelta(days=days) expiry = expiry.date() elif s.expiry: expiry = s.expiry + timedelta(days=days) else: expiry = datetime.now() + timedelta(days=days) expiry = expiry.date() s.expiry = expiry s.status = Service.ACTIVE if invoice.is_one_off: s.version = Service.FULL s.save() share_payment_and_set_stats(invoice, invoice.months_count) if s.retailer: db = s.retailer.database Service.objects.using(db).filter(pk=s.id).update( expiry=s.expiry, status=s.status, version=s.version) sudo_group = Group.objects.using(UMBRELLA).get(name=SUDO) add_event(service, PAYMENT_CONFIRMATION, group_id=sudo_group.id, object_id=invoice.id) add_event(service, PAYMENT_CONFIRMATION, member=member, object_id=invoice.id) subject, message, sms_text = get_payment_confirmation_message( instance, member) if member.email: try: currency = Currency.active.default().symbol except: currency = config.currency_code invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id, )) subject, message, sms_text = get_payment_confirmation_message( instance, member) html_content = get_mail_content( subject, message, template_name='billing/mails/notice.html', extra_context={ 'member_name': member.first_name, 'invoice': invoice, 'cta': _("View invoice"), 'invoice_url': invoice_url, 'currency': currency }) sender = '%s <no-reply@%s>' % (config.company_name, service.domain) msg = EmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" Thread(target=lambda m: m.send(), args=(msg, )).start() if sms_text: if member.phone: if config.sms_sending_method == Config.HTTP_API: send_sms(member.phone, sms_text) else: QueuedSMS.objects.create(recipient=member.phone, text=sms_text) if total_amount >= invoice.amount: invoice.status = AbstractInvoice.PAID invoice.save()
def 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 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")