def test_setup(self): call_command('loaddata', 'kc_partners.yaml') call_command('loaddata', 'kc_partner_app_retail_config.yaml') call_command('loaddata', 'kc_partners.yaml', database=UMBRELLA) call_command('loaddata', 'kc_partner_app_retail_config.yaml', database=UMBRELLA) call_command('loaddata', 'themes.yaml', database=UMBRELLA) app = Application.objects.using(UMBRELLA).get( pk='56eb6d04b37b3379b531a001') theme = Theme.objects.using(UMBRELLA).get( pk='588fc0c224123d63bb4fc4e1') billing_cycle = 'Quarterly' billing_plan = CloudBillingPlan.objects.using(UMBRELLA).get( pk='55e7b9b531a003371b6d0cb1') member = Member.objects.using(UMBRELLA).get( pk='56eb6d04b37b3379b531e014') project_name = 'Le Vignon' domain = 'levignon.cm' business_type = OperatorProfile.PROVIDER setup_cost = 45000 monthly_cost = 13500 partner = Service.objects.using(UMBRELLA).get( pk='56eb6d04b9b531b10537b331') item1 = IkwenInvoiceItem(label='Domain name') item2 = IkwenInvoiceItem(label='Website cloud_setup', price=billing_plan.setup_cost, amount=setup_cost) item3 = IkwenInvoiceItem(label='Shooting of 50 products', amount=50000) item4 = IkwenInvoiceItem(label='Publication of 100 products', amount=25000) entries = [ InvoiceEntry(item=item1, short_description=domain), InvoiceEntry(item=item2, total=setup_cost), InvoiceEntry(item=item3, total=50000), InvoiceEntry(item=item4, total=25000) ] from ikwen_kakocase.kakocase.cloud_setup import deploy deploy(app, member, business_type, project_name, billing_plan, theme, monthly_cost, entries, billing_cycle, domain, partner_retailer=partner) service_partner = Service.objects.get(domain='levignon.cm') service_umbrella = Service.objects.using(UMBRELLA).get( domain='levignon.cm') service_original = Service.objects.using('levignon').get( domain='levignon.cm') self.assertIsNotNone(service_umbrella.config) self.assertIsNotNone(service_partner.config) self.assertIsNotNone(service_original.config) self.assertIsNotNone( InvoicingConfig.objects.using('levignon').all()[0])
def post(self, request, *args, **kwargs): form = DeploymentForm(request.POST) if form.is_valid(): app_id = form.cleaned_data.get('app_id') project_name = form.cleaned_data.get('project_name') billing_cycle = form.cleaned_data.get('billing_cycle') billing_plan_id = form.cleaned_data.get('billing_plan_id') app = Application.objects.using(UMBRELLA).get(pk=app_id) billing_plan = CloudBillingPlan.objects.using(UMBRELLA).get(pk=billing_plan_id) customer_id = form.cleaned_data.get('customer_id') customer = Member.objects.using(UMBRELLA).get(pk=customer_id) setup_cost = form.cleaned_data.get('setup_cost') monthly_cost = form.cleaned_data.get('monthly_cost') if setup_cost < billing_plan.setup_cost: return HttpResponseForbidden("Attempt to set a Setup cost lower than allowed.") if monthly_cost < billing_plan.monthly_cost: return HttpResponseForbidden("Attempt to set a monthly cost lower than allowed.") invoice_entries = [] website_setup = IkwenInvoiceItem(label='Platform setup', price=billing_plan.setup_cost, amount=setup_cost) short_description = "N/A" website_setup_entry = InvoiceEntry(item=website_setup, short_description=short_description, total=setup_cost) invoice_entries.append(website_setup_entry) i = 0 while True: try: label = request.POST['item%d' % i] amount = float(request.POST['amount%d' % i]) if not (label and amount): break item = IkwenInvoiceItem(label=label, amount=amount) entry = InvoiceEntry(item=item, total=amount) invoice_entries.append(entry) i += 1 except: break theme = Theme.objects.using(UMBRELLA).get(slug='dreamer') theme.display = Theme.COZY if getattr(settings, 'DEBUG', False): service = deploy(app, customer, project_name, billing_plan, monthly_cost, theme, billing_cycle, invoice_entries) else: try: service = deploy(app, customer, project_name, billing_plan, monthly_cost, theme, billing_cycle, invoice_entries) except Exception as e: context = self.get_context_data(**kwargs) context['error'] = e.message return render(request, 'core/cloud_setup/deploy.html', context) next_url = reverse('partnership:change_service', args=(service.id, )) return HttpResponseRedirect(next_url) else: context = self.get_context_data(**kwargs) context['form'] = form return render(request, 'core/cloud_setup/deploy.html', context)
def test_setup(self): app = Application.objects.using(UMBRELLA).get( pk='56eb6d04b37b3379b531a001') billing_cycle = 'Quarterly' billing_plan = CloudBillingPlan.objects.using(UMBRELLA).get( pk='57e7b9b5371b6d0cb131a001') member = Member.objects.using(UMBRELLA).get( pk='56eb6d04b37b3379b531e014') project_name = 'IT Pro' setup_cost = 45000 monthly_cost = 13500 item1 = IkwenInvoiceItem(label='Website Cloud Setup', price=billing_plan.setup_cost, amount=setup_cost) entries = [ InvoiceEntry(item=item1), ] from ikwen.partnership.cloud_setup import deploy deploy(app, member, project_name, billing_plan, monthly_cost, billing_cycle, entries) service_umbrella = Service.objects.get(domain='itpro.ikwen.com') service_original = Service.objects.using('itpro').get( domain='itpro.ikwen.com') self.assertIsNotNone( PartnerProfile.objects.get(service=service_umbrella)) self.assertIsNotNone( PartnerProfile.objects.using('itpro').get( service=service_original)) self.assertIsNotNone(InvoicingConfig.objects.using('itpro').all()[0])
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 place_invoice(self, request, *args, **kwargs): school_name = kwargs['school_name'] weblet = Service.objects.get(project_name_slug=school_name) try: db = weblet.database add_database(db) school = SchoolConfig.objects.using(db).get(service=weblet) now = datetime.now() due_date = now + timedelta(days=7) number = get_next_invoice_number() from ikwen.billing.utils import Invoice app = Application.objects.using(UMBRELLA).get(slug='foulassi') cost = 12000 item = IkwenInvoiceItem(label='School website', price=cost, amount=cost) entry = InvoiceEntry(item=item, total=cost) invoice_entries = [entry] try: Invoice.objects.using(UMBRELLA).get(subscription=weblet, months_count=SCHOOL_WEBSITE_MONTH_COUNT) except: invoice = Invoice(subscription=weblet, member=weblet.member, amount=cost, months_count=SCHOOL_WEBSITE_MONTH_COUNT, number=number, due_date=due_date, last_reminder=now, entries=invoice_entries, is_one_off=True) invoice.save() school.has_subscribed_website_service = True school.save() return HttpResponse(json.dumps({'success': True}, 'content-type: text/json')) except: return HttpResponse(json.dumps({'error': True}, 'content-type: text/json'))
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 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 post(self, request, *args, **kwargs): form = DeploymentForm(request.POST) if form.is_valid(): app_id = form.cleaned_data.get('app_id') project_name = form.cleaned_data.get('project_name') business_type = form.cleaned_data.get('business_type') billing_cycle = form.cleaned_data.get('billing_cycle') billing_plan_id = form.cleaned_data.get('billing_plan_id') domain = form.cleaned_data.get('domain') theme_id = form.cleaned_data.get('theme_id') partner_id = form.cleaned_data.get('partner_id') app = Application.objects.using(UMBRELLA).get(pk=app_id) theme = Theme.objects.using(UMBRELLA).get(pk=theme_id) billing_plan = CloudBillingPlan.objects.using(UMBRELLA).get( pk=billing_plan_id) is_ikwen = getattr(settings, 'IS_IKWEN', False) if not is_ikwen or (is_ikwen and request.user.is_staff): customer_id = form.cleaned_data.get('customer_id') customer = Member.objects.using(UMBRELLA).get(pk=customer_id) setup_cost = form.cleaned_data.get('setup_cost') monthly_cost = form.cleaned_data.get('monthly_cost') if setup_cost < billing_plan.setup_cost: return HttpResponseForbidden( "Attempt to set a Setup cost lower than allowed.") if monthly_cost < billing_plan.monthly_cost: return HttpResponseForbidden( "Attempt to set a monthly cost lower than allowed.") else: # User self-deploying his website customer = Member.objects.using(UMBRELLA).get( pk=request.user.id) setup_cost = billing_plan.setup_cost monthly_cost = billing_plan.monthly_cost partner = Service.objects.using(UMBRELLA).get( pk=partner_id) if partner_id else None invoice_entries = [] domain_name = IkwenInvoiceItem(label='Domain name') domain_name_entry = InvoiceEntry(item=domain_name, short_description=domain) invoice_entries.append(domain_name_entry) website_setup = IkwenInvoiceItem(label='Website setup', price=billing_plan.setup_cost, amount=setup_cost) short_description = "%d products" % billing_plan.max_objects website_setup_entry = InvoiceEntry( item=website_setup, short_description=short_description, total=setup_cost) invoice_entries.append(website_setup_entry) i = 0 while True: try: label = request.POST['item%d' % i] amount = float(request.POST['amount%d' % i]) if not (label and amount): break item = IkwenInvoiceItem(label=label, amount=amount) entry = InvoiceEntry(item=item, total=amount) invoice_entries.append(entry) i += 1 except: break if getattr(settings, 'DEBUG', False): service = deploy(app, customer, business_type, project_name, billing_plan, theme, monthly_cost, invoice_entries, billing_cycle, domain, partner_retailer=partner) else: try: service = deploy(app, customer, business_type, project_name, billing_plan, theme, monthly_cost, invoice_entries, billing_cycle, domain, partner_retailer=partner) except Exception as e: context = self.get_context_data(**kwargs) context['error'] = e.message return render(request, 'shavida/cloud_setup/deploy.html', context) if is_ikwen: if request.user.is_staff: next_url = reverse('partnership:change_service', args=(service.id, )) else: next_url = reverse('ikwen:console') else: next_url = reverse('change_service', args=(service.id, )) return HttpResponseRedirect(next_url) else: context = self.get_context_data(**kwargs) context['form'] = form return render(request, 'shavida/cloud_setup/deploy.html', context)
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 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 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_invoices(): """ This cron task simply sends the Invoice *invoicing_gap* days before Subscription *expiry* """ vendor, config, invoicing_config, connection = _init_base_vars() now = timezone.now() count, total_amount = 0, 0 reminder_date_time = now + timedelta(days=invoicing_config.gap) subscription_qs = Subscription.objects.filter(status=Subscription.ACTIVE, monthly_cost__gt=0, expiry__lt=reminder_date_time.date()) logger.debug("%d Service candidate for invoice issuance." % subscription_qs.count()) for subscription in subscription_qs: if getattr(settings, 'IS_IKWEN', False): if subscription.version == Service.FREE: continue try: pending_invoice = Invoice.objects.get(subscription=subscription, status=Invoice.PENDING) logger.debug("%s found for %s. Skipping" % (pending_invoice, subscription)) continue # Continue if a Pending invoice for this Subscription is found except Invoice.DoesNotExist: pass member = subscription.member number = get_next_invoice_number() months_count = None if config.__dict__.get('separate_billing_cycle', True): months_count = get_billing_cycle_months_count(subscription.billing_cycle) amount = subscription.monthly_cost * months_count else: amount = subscription.product.cost 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 entries = [] if type(subscription) is Service: from daraja.models import DARAJA partner = subscription.retailer if partner and partner.app.slug != DARAJA: retail_config = ApplicationRetailConfig.objects.get(partner=partner, app=subscription.app) ikwen_price = retail_config.ikwen_monthly_cost else: ikwen_price = subscription.monthly_cost hosting = IkwenInvoiceItem(label=_('Website hosting'), price=ikwen_price, amount=subscription.monthly_cost) short_description = _("Project %s" % subscription.domain) entry = InvoiceEntry(item=hosting, short_description=short_description, quantity=months_count, total=amount) entries = [entry] try: cr_op_profile = CROperatorProfile.objects.get(service=subscription, is_active=True) if cr_op_profile.monthly_cost > 0: plan = cr_op_profile.plan cr_monthly_cost = cr_op_profile.monthly_cost cr_item = IkwenInvoiceItem(label=_('Continuous Rewarding'), price=cr_monthly_cost, amount=cr_monthly_cost) short_description = plan.name cr_amount = months_count * cr_monthly_cost amount += cr_amount entry = InvoiceEntry(item=cr_item, short_description=short_description, quantity=months_count, total=cr_amount) entries.append(entry) except CROperatorProfile.DoesNotExist: pass invoice = Invoice.objects.create(subscription=subscription, member=subscription.member, amount=amount, number=number, due_date=subscription.expiry, months_count=months_count, entries=entries, last_reminder=now) count += 1 total_amount += amount add_event(vendor, NEW_INVOICE_EVENT, member=member, object_id=invoice.id) paid_by_wallet_debit = False if getattr(settings, 'IS_IKWEN', False) and subscription.balance >= invoice.amount: pay_with_wallet_balance(invoice) paid_by_wallet_debit = True logger.debug("Invoice for %s paid by wallet debit" % subscription.domain) subject, message, sms_text = get_invoice_generated_message(invoice) if member.email: activate(member.language) invoice_url = 'http://ikwen.com' + reverse('billing:invoice_detail', args=(invoice.id,)) if paid_by_wallet_debit: subject = _("Thanks for your payment") invoice_url = 'http://ikwen.com' + reverse('billing:invoice_detail', args=(invoice.id,)) context = {'wallet_debit': True, 'invoice': invoice, 'config': config, 'member_name': member.first_name, 'invoice_url': invoice_url, 'cta': _("View invoice")} html_content = get_mail_content(subject, '', template_name='billing/mails/wallet_debit_notice.html', extra_context=context) else: context = {'invoice_url': invoice_url, 'cta': _("Pay now"), 'currency': config.currency_symbol, 'service': vendor, 'config': config, 'logo': config.logo, 'project_name': vendor.project_name, 'company_name': config.company_name, 'member_name': member.first_name, 'invoice': invoice} html_content = get_mail_content(subject, message, template_name='billing/mails/notice.html', extra_context=context) # 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, vendor.domain) msg = EmailMessage(subject, html_content, sender, [member.email]) try: invoice_pdf_file = generate_pdf_invoice(invoicing_config, invoice) msg.attach_file(invoice_pdf_file) except: pass if paid_by_wallet_debit: msg.bcc = ['*****@*****.**'] msg.content_subtype = "html" invoice.last_reminder = timezone.now() try: if msg.send(): logger.debug("1st Invoice reminder for %s sent to %s" % (subscription.domain, member.email)) if not paid_by_wallet_debit: invoice.reminders_sent = 1 invoice.save() 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: 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) path_after = getattr(settings, 'BILLING_AFTER_NEW_INVOICE', None) if path_after: after_new_invoice = import_by_path(path_after) after_new_invoice(invoice) try: connection.close() finally: if count > 0: report = SendingReport.objects.create(count=count, total_amount=total_amount) sudo_group = Group.objects.get(name=SUDO) add_event(vendor, INVOICES_SENT_EVENT, group_id=sudo_group.id, object_id=report.id)
def send_invoices(): """ This cron task simply sends the Invoice *invoicing_gap* days before Subscription *expiry* """ service = get_service_instance() config = service.config 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) count, total_amount = 0, 0 reminder_date_time = now + timedelta(days=invoicing_config.gap) subscription_qs = Subscription.objects.filter(monthly_cost__gt=0, expiry=reminder_date_time.date()) logger.debug("%d CR Operators candidate for invoice issuance." % subscription_qs.count()) for subscription in subscription_qs: if subscription.plan.raw_monthly_cost == 0: continue cr_service = subscription.service member = cr_service.member number = get_next_invoice_number() months_count = get_billing_cycle_months_count(subscription.billing_cycle) amount = subscription.monthly_cost * months_count ikwen_price = subscription.monthly_cost hosting = IkwenInvoiceItem(label=_('Website hosting'), price=ikwen_price, amount=subscription.monthly_cost) short_description = _("Continuous Rewarding Program for %s" % cr_service.domain) entry = InvoiceEntry(item=hosting, short_description=short_description, quantity=months_count, total=amount) entries = [entry] invoice = Invoice.objects.create(subscription=subscription, amount=amount, number=number, due_date=subscription.expiry, months_count=months_count, entries=entries) count += 1 total_amount += amount add_event(service, NEW_INVOICE_EVENT, member=member, object_id=invoice.id) paid_by_wallet_debit = False if cr_service.balance >= invoice.amount: pay_with_wallet_balance(invoice) paid_by_wallet_debit = True logger.debug("CR Invoice for %s paid by wallet debit" % cr_service.domain) subject, message, sms_text = get_invoice_generated_message(invoice) if member.email: invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id,)) if paid_by_wallet_debit: subject = _("Thanks for your payment") invoice_url = service.url + reverse('billing:invoice_detail', args=(invoice.id,)) context = {'wallet_debit': True, 'invoice': invoice, 'config': config, 'invoice_url': invoice_url, 'cta': _("View invoice")} html_content = get_mail_content(subject, '', template_name='billing/mails/notice.html', extra_context=context) else: html_content = get_mail_content(subject, message, template_name='billing/mails/notice.html', extra_context={'invoice_url': invoice_url, 'cta': _("Pay now")}) # 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 = EmailMessage(subject, html_content, sender, [member.email]) msg.content_subtype = "html" invoice.last_reminder = timezone.now() try: if msg.send(): logger.debug("1st Invoice reminder for %s sent to %s" % (cr_service.domain, member.email)) if not paid_by_wallet_debit: invoice.reminders_sent = 1 invoice.save() 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) path_after = getattr(settings, 'BILLING_AFTER_NEW_INVOICE', None) if path_after: after_new_invoice = import_by_path(path_after) after_new_invoice(invoice) try: connection.close() finally: pass
def post(self, request, *args, **kwargs): form = DeploymentForm(request.POST) if form.is_valid(): project_name = form.cleaned_data.get('project_name') billing_plan_id = form.cleaned_data.get('billing_plan_id') partner_id = request.COOKIES.get('referrer') webnode = Application.objects.get(slug='webnode') template_list = list(Template.objects.filter(app=webnode)) try: theme = Theme.objects.using(UMBRELLA).get(template__in=template_list, slug=self.DEFAULT_THEME) except: theme = None logger.error("Foulassi deployment: %s webnode theme not found" % self.DEFAULT_THEME) billing_plan = CloudBillingPlan.objects.using(UMBRELLA).get(pk=billing_plan_id) is_ikwen = getattr(settings, 'IS_IKWEN', False) if not is_ikwen or (is_ikwen and request.user.is_staff): customer_id = form.cleaned_data.get('customer_id') if not customer_id: customer_id = request.user.id customer = Member.objects.using(UMBRELLA).get(pk=customer_id) setup_cost = form.cleaned_data.get('setup_cost') monthly_cost = form.cleaned_data.get('monthly_cost') if setup_cost < billing_plan.setup_cost: return HttpResponseForbidden("Attempt to set a Setup cost lower than allowed.") if monthly_cost < billing_plan.monthly_cost: return HttpResponseForbidden("Attempt to set a monthly cost lower than allowed.") else: # User self-deploying his website customer = Member.objects.using(UMBRELLA).get(pk=request.user.id) setup_cost = billing_plan.setup_cost monthly_cost = billing_plan.monthly_cost try: partner = Service.objects.using(UMBRELLA).get(pk=partner_id) if partner_id else None except: partner = None invoice_entries = [] website_setup = IkwenInvoiceItem(label=_('ScolarFleet deployment'), price=billing_plan.setup_cost, amount=setup_cost) website_setup_entry = InvoiceEntry(item=website_setup, short_description=project_name, quantity=12, total=setup_cost) invoice_entries.append(website_setup_entry) if theme and theme.cost > 0: theme_item = IkwenInvoiceItem(label=_('Website theme'), price=theme.cost, amount=theme.cost) theme_entry = InvoiceEntry(item=theme_item, short_description=theme.name, total=theme.cost) invoice_entries.append(theme_entry) if getattr(settings, 'DEBUG', False): service = deploy(customer, project_name, billing_plan, theme, monthly_cost, invoice_entries, partner) else: try: service = deploy(customer, project_name, billing_plan, theme, monthly_cost, invoice_entries, partner) except Exception as e: logger.error("Foulassi deployment failed for %s" % project_name, exc_info=True) context = self.get_context_data(**kwargs) context['errors'] = e.message return render(request, 'foulassi/cloud_setup/deploy.html', context) if is_ikwen: if request.user.is_staff: next_url = reverse('partnership:change_service', args=(service.id,)) else: next_url = reverse('foulassi:successful_deployment', args=(service.ikwen_name,)) else: next_url = reverse('change_service', args=(service.id,)) return HttpResponseRedirect(next_url) else: context = self.get_context_data(**kwargs) context['errors'] = form.errors return render(request, 'foulassi/cloud_setup/deploy.html', context)
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 post(self, request, *args, **kwargs): form = DeploymentForm(request.POST) if form.is_valid(): project_name = form.cleaned_data.get('project_name') # try: # service = Service.objects.using(UMBRELLA).get(project_name=project_name) # except: # pass app_id = form.cleaned_data.get('app_id') business_type = form.cleaned_data.get('business_type') business_category_id = form.cleaned_data.get( 'business_category_id') bundle_id = form.cleaned_data.get('bundle_id') domain = form.cleaned_data.get('domain') theme_id = form.cleaned_data.get('theme_id') partner_id = request.COOKIES.get('referrer') app = Application.objects.using(UMBRELLA).get(pk=app_id) theme = Theme.objects.using(UMBRELLA).get(pk=theme_id) business_category = BusinessCategory.objects.using(UMBRELLA).get( pk=business_category_id) bundle = TsunamiBundle.objects.using(UMBRELLA).get(pk=bundle_id) billing_plan = bundle.billing_plan billing_cycle = get_months_count_billing_cycle( billing_plan.setup_months_count) customer = Member.objects.using(UMBRELLA).get(pk=request.user.id) setup_cost = billing_plan.setup_cost monthly_cost = billing_plan.monthly_cost try: partner = Service.objects.using(UMBRELLA).get( pk=partner_id) if partner_id else None except: partner = None invoice_entries = [] domain_name = IkwenInvoiceItem(label='Domain name') domain_name_entry = InvoiceEntry(item=domain_name, short_description=domain) invoice_entries.append(domain_name_entry) website_setup = IkwenInvoiceItem(label='Website setup', price=billing_plan.setup_cost, amount=setup_cost) short_description = "%d products" % billing_plan.max_objects website_setup_entry = InvoiceEntry( item=website_setup, short_description=short_description, total=setup_cost) invoice_entries.append(website_setup_entry) if theme.cost > 0: theme_item = IkwenInvoiceItem(label='Website theme', price=theme.cost, amount=theme.cost) theme_entry = InvoiceEntry(item=theme_item, short_description=theme.name, total=theme.cost) invoice_entries.append(theme_entry) i = 0 while True: try: label = request.POST['item%d' % i] amount = float(request.POST['amount%d' % i]) if not (label and amount): break item = IkwenInvoiceItem(label=label, amount=amount) entry = InvoiceEntry(item=item, total=amount) invoice_entries.append(entry) i += 1 except: break if getattr(settings, 'DEBUG', False): service = deploy(app, customer, business_type, project_name, billing_plan, theme, monthly_cost, invoice_entries, billing_cycle, domain, business_category, bundle, partner_retailer=partner) else: try: service = deploy(app, customer, business_type, project_name, billing_plan, theme, monthly_cost, invoice_entries, billing_cycle, domain, business_category, bundle, partner_retailer=partner) except Exception as e: context = self.get_context_data(**kwargs) context['error'] = e.message return render(request, 'kakocase/tsunami/go.html', context) next_url = reverse('kakocase:successful_deployment', args=(service.id, )) return HttpResponseRedirect(next_url) else: context = self.get_context_data(**kwargs) context['form'] = form return render(request, 'kakocase/tsunami/go.html', context)
def test_pay_one_off_invoice_with_service_having_retailer(self): """ Walks all through the payment from choosing the payment mean, setting checkout, confirm payment and having its Service expiry extended. If the Service is deployed through a partner. Share earnings accordingly """ call_command('loaddata', 'billing_invoices.yaml') call_command('loaddata', 'partners.yaml') call_command('loaddata', 'ikwen_members.yaml', database='test_kc_partner_jumbo') call_command('loaddata', 'setup_data.yaml', database='test_kc_partner_jumbo') call_command('loaddata', 'partners.yaml', database='test_kc_partner_jumbo') call_command('loaddata', 'partner_app_retail_config.yaml') call_command('loaddata', 'partner_app_retail_config.yaml', database='test_kc_partner_jumbo') now = datetime.now() partner = Service.objects.get(pk='56eb6d04b9b531b10537b331') Service.objects.filter(pk='56eb6d04b37b3379b531b102').update( expiry=now.date(), retailer=partner) item1 = IkwenInvoiceItem(label='item1', amount=10000, price=7000) item2 = IkwenInvoiceItem(label='item2', amount=4000, price=0) entries = [ InvoiceEntry(item=item1), InvoiceEntry(item=item2, quantity=2) ] Invoice.objects.filter(pk='56eb6d04b37b3379d531e012').update( is_one_off=True, amount=18000, entries=entries) self.client.login(username='******', password='******') response = self.client.post(reverse('billing:momo_set_checkout'), {'product_id': '56eb6d04b37b3379d531e012'}) json_resp = json.loads(response.content) notification_url = json_resp['notification_url'] response = self.client.get(notification_url, data={ 'status': 'Success', 'phone': '655003321', 'message': 'OK', 'operator_tx_id': 'OP_TX_1' }) self.assertEqual(response.status_code, 200) s = Service.objects.get(pk='56eb6d04b37b3379b531b102') new_expiry = now + timedelta(days=30) self.assertEqual(s.expiry, new_expiry.date()) cache.clear() service = Service.objects.get(pk='56eb6d04b37b3379b531b102') self.assertEqual(service.turnover_history, [18000]) self.assertEqual(service.invoice_earnings_history, [7000]) self.assertEqual(service.earnings_history, [7000]) self.assertEqual(service.invoice_count_history, [1]) app = service.app self.assertEqual(app.turnover_history, [18000]) self.assertEqual(app.invoice_earnings_history, [7000]) self.assertEqual(app.earnings_history, [7000]) self.assertEqual(app.invoice_count_history, [1]) partner = Service.objects.get(pk='56eb6d04b9b531b10537b331') self.assertEqual(partner.turnover_history, [18000]) self.assertEqual(partner.invoice_earnings_history, [7000]) self.assertEqual(partner.earnings_history, [7000]) self.assertEqual(partner.invoice_count_history, [1]) partner_app = partner.app self.assertEqual(partner_app.turnover_history, [18000]) self.assertEqual(partner_app.invoice_earnings_history, [7000]) self.assertEqual(partner_app.earnings_history, [7000]) self.assertEqual(partner_app.invoice_count_history, [1]) service_mirror = Service.objects.using('test_kc_partner_jumbo').get( pk='56eb6d04b37b3379b531b102') self.assertEqual(service_mirror.invoice_earnings_history, [11000]) self.assertEqual(service_mirror.earnings_history, [11000]) self.assertEqual(service_mirror.invoice_count_history, [1]) app_mirror = service_mirror.app self.assertEqual(app_mirror.invoice_earnings_history, [11000]) self.assertEqual(app_mirror.earnings_history, [11000]) self.assertEqual(app_mirror.invoice_count_history, [1]) partner_wallet = OperatorWallet.objects.using('wallets').get( nonrel_id='56eb6d04b9b531b10537b331') self.assertEqual(partner_wallet.balance, 11000)