def product_set_checkout(request, *args, **kwargs): product_id = request.POST['product_id'] product = Product.objects.get(pk=product_id) member = request.user now = datetime.now() expiry = now + timedelta(days=product.duration) subscription = Subscription.objects.create(member=member, product=product, since=now, expiry=expiry) number = get_next_invoice_number() item = InvoiceItem(label=product.name) entry = InvoiceEntry(item=item, short_description=product.short_description, total=product.cost) months_count = product.duration / 30 invoice = Invoice.objects.create(subscription=subscription, amount=product.cost, number=number, due_date=now, last_reminder=now, is_one_off=product.is_one_off, entries=[entry], months_count=months_count) amount = invoice.amount notification_url = reverse('billing:product_do_checkout', args=(invoice.id, )) cancel_url = request.META['HTTP_REFERER'] return_url = reverse('billing:invoice_detail', args=(invoice.id, )) return invoice, amount, notification_url, return_url, cancel_url
def product_set_checkout(request, *args, **kwargs): service = get_service_instance() product_id = request.POST['product_id'] product = Product.objects.get(pk=product_id) member = request.user now = datetime.now() expiry = now + timedelta(days=product.duration) subscription = Subscription.objects.create(member=member, product=product, since=now, expiry=expiry) number = get_next_invoice_number() item = InvoiceItem(label=product.name) entry = InvoiceEntry(item=item, short_description=product.short_description, total=product.cost) months_count = product.duration / 30 invoice = Invoice.objects.create(subscription=subscription, amount=product.cost, number=number, due_date=now, last_reminder=now, is_one_off=True, entries=[entry], months_count=months_count) request.session['amount'] = product.cost request.session['model_name'] = 'billing.Invoice' request.session['object_id'] = invoice.id mean = request.GET.get('mean', MTN_MOMO) request.session['mean'] = mean request.session['notif_url'] = service.url # Orange Money only request.session['cancel_url'] = service.url + reverse('billing:pricing') # Orange Money only request.session['return_url'] = reverse('billing:invoice_detail', args=(invoice.id, ))
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 set_my_kids_payment(request, *args, **kwargs): school_id = request.POST['school_id'] student_id = request.POST['student_id'] cycle = request.POST['my_kids_cycle'] school = Service.objects.get(pk=school_id) student = Student.objects.get(pk=student_id) school_config = SchoolConfig.objects.get(service=school) Invoice.objects.filter(student=student, is_my_kids=True, status=Invoice.PENDING).delete() max_expiry = datetime(day=31, month=8, year=get_school_year() + 1) if cycle == Service.YEARLY: amount = school_config.my_kids_fees elif cycle == Service.QUARTERLY: amount = school_config.my_kids_fees_term else: amount = school_config.my_kids_fees_month cycle = Service.MONTHLY item = InvoiceItem(label=_("MyKids fees"), amount=amount) days = get_billing_cycle_days_count(cycle) now = datetime.now() expiry = now + timedelta(days=days) expiry = min(expiry, max_expiry) short_description = now.strftime("%Y/%m/%d") + ' - ' + expiry.strftime( "%Y/%m/%d") entry = InvoiceEntry(item=item, short_description=short_description, total=amount, quantity_unit='') number = get_next_invoice_number() member = request.user invoice = Invoice.objects.create(number=number, member=member, student=student, school=school, is_one_off=True, amount=amount, my_kids_cycle=cycle, due_date=now, entries=[entry], is_my_kids=True) foulassi_weblet = get_service_instance() # This is Foulassi service itself # Transaction is hidden from school if ikwen collects 100%. # This is achieved by changing the service_id of transaction tx_service_id = school.id if school_config.my_kids_share_rate < 100 else foulassi_weblet.id model_name = 'billing.Invoice' mean = request.GET.get('mean', MTN_MOMO) signature = ''.join([ random.SystemRandom().choice(string.ascii_letters + string.digits) for i in range(16) ]) MoMoTransaction.objects.using(WALLETS_DB_ALIAS).filter( object_id=invoice.id).delete() tx = MoMoTransaction.objects.using(WALLETS_DB_ALIAS)\ .create(service_id=tx_service_id, type=MoMoTransaction.CASH_OUT, amount=amount, phone='N/A', model=model_name, object_id=invoice.id, task_id=signature, wallet=mean, username=request.user.username, is_running=True) notification_url = foulassi_weblet.url + reverse( 'foulassi:confirm_my_kids_payment', args=(tx.id, signature)) cancel_url = request.META['HTTP_REFERER'] return_url = request.META['HTTP_REFERER'] return invoice, amount, notification_url, return_url, cancel_url
def confirm_reservation_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 amount = tx.amount now = datetime.now() # Update reservation status --- Confirm reservation reservation_id = tx.object_id reservation = Reservation.objects.get(pk=reservation_id) reservation.processor_tx_id = tx.processor_tx_id reservation.method = Reservation.MOBILE_MONEY reservation.status = CONFIRMED item = InvoiceItem(label=_("Reservation of %s " % reservation.post), amount=reservation.amount) short_description = reservation.start_on.strftime( "%Y/%m/%d") + ' - ' + reservation.end_on.strftime("%Y/%m/%d") entry = InvoiceEntry(item=item, short_description=short_description, total=reservation.amount, quantity_unit='') reservation.entries = [entry] reservation.save() # Share earnings weblet = get_service_instance(check_cache=False) config = weblet.config ikwen_charges = tx.amount * config.ikwen_share_rate / 100 ikwen_share_rate = config.ikwen_share_fixed tx.fees = ikwen_charges tx.save() amount = (100 - ikwen_share_rate) * amount / 100 weblet.raise_balance(amount, provider=tx.wallet) set_counters(weblet) increment_history_field(weblet, 'turnover_history', amount) increment_history_field(weblet, 'earnings_history', amount) increment_history_field(weblet, 'transaction_count_history') member = reservation.member # Notify customer and staff payer_email = member.email email = config.contact_email if not email: email = weblet.member.email if email or payer_email: subject = _("New reservation of %s done" % reservation.post) try: html_content = get_mail_content( subject, template_name='enfinchezmoi/mails/payment_notice.html', extra_context={ 'currency_symbol': config.currency_symbol, 'post': reservation, 'payer': member, 'tx_date': tx.updated_on.strftime('%Y-%m-%d'), 'tx_time': tx.updated_on.strftime('%H:%M:%S') }) sender = '%s <no-reply@%s>' % (weblet.project_name, weblet.domain) msg = EmailMessage(subject, html_content, sender, [payer_email]) msg.bcc = [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("%s - Failed to send notice mail to %s." % (weblet, email), exc_info=True) # 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 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