def test_get_seat_count(self) -> None: realm = get_realm("zulip") initial_count = get_seat_count(realm) user1 = UserProfile.objects.create(realm=realm, email='*****@*****.**', pointer=-1) user2 = UserProfile.objects.create(realm=realm, email='*****@*****.**', pointer=-1) self.assertEqual(get_seat_count(realm), initial_count + 2) # Test that bots aren't counted user1.is_bot = True user1.save(update_fields=['is_bot']) self.assertEqual(get_seat_count(realm), initial_count + 1) # Test that inactive users aren't counted do_deactivate_user(user2) self.assertEqual(get_seat_count(realm), initial_count)
def test_get_seat_count(self) -> None: realm = get_realm("zulip") initial_count = get_seat_count(realm) user1 = UserProfile.objects.create(realm=realm, email='*****@*****.**', pointer=-1) user2 = UserProfile.objects.create(realm=realm, email='*****@*****.**', pointer=-1) self.assertEqual(get_seat_count(realm), initial_count + 2) # Test that bots aren't counted user1.is_bot = True user1.save(update_fields=['is_bot']) self.assertEqual(get_seat_count(realm), initial_count + 1) # Test that inactive users aren't counted do_deactivate_user(user2) self.assertEqual(get_seat_count(realm), initial_count)
def initial_upgrade(request: HttpRequest) -> HttpResponse: if not settings.BILLING_ENABLED: return render(request, "404.html") user = request.user customer = Customer.objects.filter(realm=user.realm).first() if customer is not None and CustomerPlan.objects.filter(customer=customer).exists(): return HttpResponseRedirect(reverse('corporate.views.billing_home')) percent_off = 0 if customer is not None and customer.default_discount is not None: percent_off = customer.default_discount seat_count = get_seat_count(user.realm) signed_seat_count, salt = sign_string(str(seat_count)) context = { 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'email': user.email, 'seat_count': seat_count, 'signed_seat_count': signed_seat_count, 'salt': salt, 'min_invoiced_licenses': max(seat_count, MIN_INVOICED_LICENSES), 'default_invoice_days_until_due': DEFAULT_INVOICE_DAYS_UNTIL_DUE, 'plan': "Zulip Standard", 'page_params': JSONEncoderForHTML().encode({ 'seat_count': seat_count, 'annual_price': 8000, 'monthly_price': 800, 'percent_off': float(percent_off), }), } # type: Dict[str, Any] response = render(request, 'corporate/upgrade.html', context=context) return response
def initial_upgrade(request: HttpRequest) -> HttpResponse: if not settings.BILLING_ENABLED: return render(request, "404.html") user = request.user customer = Customer.objects.filter(realm=user.realm).first() if customer is not None and CustomerPlan.objects.filter(customer=customer).exists(): return HttpResponseRedirect(reverse('corporate.views.billing_home')) percent_off = 0 if customer is not None and customer.default_discount is not None: percent_off = customer.default_discount seat_count = get_seat_count(user.realm) signed_seat_count, salt = sign_string(str(seat_count)) context = { 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'email': user.email, 'seat_count': seat_count, 'signed_seat_count': signed_seat_count, 'salt': salt, 'min_invoiced_licenses': max(seat_count, MIN_INVOICED_LICENSES), 'default_invoice_days_until_due': DEFAULT_INVOICE_DAYS_UNTIL_DUE, 'plan': "Zulip Standard", 'page_params': JSONEncoderForHTML().encode({ 'seat_count': seat_count, 'annual_price': 8000, 'monthly_price': 800, 'percent_off': float(percent_off), }), } # type: Dict[str, Any] response = render(request, 'corporate/upgrade.html', context=context) return response
def initial_upgrade(request: HttpRequest) -> HttpResponse: if not settings.BILLING_ENABLED: return render(request, "404.html") user = request.user error_message = "" error_description = "" # only used in tests customer = Customer.objects.filter(realm=user.realm).first() if customer is not None and customer.has_billing_relationship: return HttpResponseRedirect(reverse('corporate.views.billing_home')) if request.method == 'POST': try: plan, seat_count = unsign_and_check_upgrade_parameters( user, request.POST['plan'], request.POST['signed_seat_count'], request.POST['salt']) if 'invoiced_seat_count' in request.POST: min_required_seat_count = max(seat_count, MIN_INVOICED_SEAT_COUNT) if int(request.POST['invoiced_seat_count'] ) < min_required_seat_count: raise BillingError( 'lowball seat count', "You must invoice for at least %d users." % (min_required_seat_count, )) seat_count = int(request.POST['invoiced_seat_count']) process_initial_upgrade(user, plan, seat_count, request.POST.get('stripeToken', None)) except BillingError as e: error_message = e.message error_description = e.description except Exception as e: billing_logger.exception("Uncaught exception in billing: %s" % (e, )) error_message = BillingError.CONTACT_SUPPORT error_description = "uncaught exception during upgrade" else: return HttpResponseRedirect( reverse('corporate.views.billing_home')) seat_count = get_seat_count(user.realm) signed_seat_count, salt = sign_string(str(seat_count)) context = { 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'email': user.email, 'seat_count': seat_count, 'signed_seat_count': signed_seat_count, 'salt': salt, 'plan': "Zulip Standard", 'nickname_monthly': Plan.CLOUD_MONTHLY, 'nickname_annual': Plan.CLOUD_ANNUAL, 'error_message': error_message, 'cloud_monthly_price': 8, 'cloud_annual_price': 80, 'cloud_annual_price_per_month': 6.67, } # type: Dict[str, Any] response = render(request, 'corporate/upgrade.html', context=context) response['error_description'] = error_description return response
def billing_home(request: HttpRequest) -> HttpResponse: user = request.user customer = Customer.objects.filter(realm=user.realm).first() if customer is None: return HttpResponseRedirect(reverse('corporate.views.initial_upgrade')) if not CustomerPlan.objects.filter(customer=customer).exists(): return HttpResponseRedirect(reverse('corporate.views.initial_upgrade')) if not user.is_realm_admin and not user.is_billing_admin: context = {'admin_access': False} # type: Dict[str, Any] return render(request, 'corporate/billing.html', context=context) context = {'admin_access': True} stripe_customer = stripe_get_customer(customer.stripe_customer_id) plan = get_active_plan(customer) if plan is not None: plan_name = { CustomerPlan.STANDARD: 'Zulip Standard', CustomerPlan.PLUS: 'Zulip Plus', }[plan.tier] now = timezone_now() last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed( plan, now) licenses = last_ledger_entry.licenses licenses_used = get_seat_count(user.realm) # Should do this in javascript, using the user's timezone renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format( dt=next_renewal_date(plan, now)) renewal_cents = renewal_amount(plan, now) # TODO: this is the case where the plan doesn't automatically renew if renewal_cents is None: # nocoverage renewal_cents = 0 charge_automatically = plan.charge_automatically if charge_automatically: payment_method = payment_method_string(stripe_customer) else: payment_method = 'Billed by invoice' # Can only get here by subscribing and then downgrading. We don't support downgrading # yet, but keeping this code here since we will soon. else: # nocoverage plan_name = "Zulip Free" licenses = 0 renewal_date = '' renewal_cents = 0 payment_method = '' charge_automatically = False context.update({ 'plan_name': plan_name, 'licenses': licenses, 'licenses_used': licenses_used, 'renewal_date': renewal_date, 'renewal_amount': '{:,.2f}'.format(renewal_cents / 100.), 'payment_method': payment_method, 'charge_automatically': charge_automatically, 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'stripe_email': stripe_customer.email, }) return render(request, 'corporate/billing.html', context=context)
def billing_home(request: HttpRequest) -> HttpResponse: user = request.user customer = Customer.objects.filter(realm=user.realm).first() if customer is None: return HttpResponseRedirect(reverse('corporate.views.initial_upgrade')) if not CustomerPlan.objects.filter(customer=customer).exists(): return HttpResponseRedirect(reverse('corporate.views.initial_upgrade')) if not user.is_realm_admin and not user.is_billing_admin: context = {'admin_access': False} # type: Dict[str, Any] return render(request, 'corporate/billing.html', context=context) context = {'admin_access': True} stripe_customer = stripe_get_customer(customer.stripe_customer_id) plan = get_active_plan(customer) if plan is not None: plan_name = { CustomerPlan.STANDARD: 'Zulip Standard', CustomerPlan.PLUS: 'Zulip Plus', }[plan.tier] now = timezone_now() last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, now) licenses = last_ledger_entry.licenses licenses_used = get_seat_count(user.realm) # Should do this in javascript, using the user's timezone renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format(dt=next_renewal_date(plan, now)) renewal_cents = renewal_amount(plan, now) # TODO: this is the case where the plan doesn't automatically renew if renewal_cents is None: # nocoverage renewal_cents = 0 charge_automatically = plan.charge_automatically if charge_automatically: payment_method = payment_method_string(stripe_customer) else: payment_method = 'Billed by invoice' # Can only get here by subscribing and then downgrading. We don't support downgrading # yet, but keeping this code here since we will soon. else: # nocoverage plan_name = "Zulip Free" licenses = 0 renewal_date = '' renewal_cents = 0 payment_method = '' charge_automatically = False context.update({ 'plan_name': plan_name, 'licenses': licenses, 'licenses_used': licenses_used, 'renewal_date': renewal_date, 'renewal_amount': '{:,.2f}'.format(renewal_cents / 100.), 'payment_method': payment_method, 'charge_automatically': charge_automatically, 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'stripe_email': stripe_customer.email, }) return render(request, 'corporate/billing.html', context=context)
def billing_home(request: HttpRequest) -> HttpResponse: user = request.user customer = Customer.objects.filter(realm=user.realm).first() if customer is None: return HttpResponseRedirect(reverse('corporate.views.initial_upgrade')) if not CustomerPlan.objects.filter(customer=customer).exists(): return HttpResponseRedirect(reverse('corporate.views.initial_upgrade')) if not user.is_realm_admin and not user.is_billing_admin: context = {'admin_access': False} # type: Dict[str, Any] return render(request, 'corporate/billing.html', context=context) context = {'admin_access': True} plan_name = "Zulip Free" licenses = 0 renewal_date = '' renewal_cents = 0 payment_method = '' charge_automatically = False stripe_customer = stripe_get_customer(customer.stripe_customer_id) plan = get_current_plan(customer) if plan is not None: plan_name = { CustomerPlan.STANDARD: 'Zulip Standard', CustomerPlan.PLUS: 'Zulip Plus', }[plan.tier] now = timezone_now() last_ledger_entry = make_end_of_cycle_updates_if_needed(plan, now) if last_ledger_entry is not None: licenses = last_ledger_entry.licenses licenses_used = get_seat_count(user.realm) # Should do this in javascript, using the user's timezone renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format( dt=start_of_next_billing_cycle(plan, now)) renewal_cents = renewal_amount(plan, now) charge_automatically = plan.charge_automatically if charge_automatically: payment_method = payment_method_string(stripe_customer) else: payment_method = 'Billed by invoice' context.update({ 'plan_name': plan_name, 'licenses': licenses, 'licenses_used': licenses_used, 'renewal_date': renewal_date, 'renewal_amount': '{:,.2f}'.format(renewal_cents / 100.), 'payment_method': payment_method, 'charge_automatically': charge_automatically, 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'stripe_email': stripe_customer.email, }) return render(request, 'corporate/billing.html', context=context)
def initial_upgrade(request: HttpRequest) -> HttpResponse: if not settings.BILLING_ENABLED: return render(request, "404.html") user = request.user error_message = "" error_description = "" # only used in tests customer = Customer.objects.filter(realm=user.realm).first() if customer is not None and customer.has_billing_relationship: return HttpResponseRedirect(reverse('corporate.views.billing_home')) if request.method == 'POST': try: plan, seat_count = unsign_and_check_upgrade_parameters( user, request.POST['plan'], request.POST['signed_seat_count'], request.POST['salt']) if 'invoiced_seat_count' in request.POST: min_required_seat_count = max(seat_count, MIN_INVOICED_SEAT_COUNT) if int(request.POST['invoiced_seat_count']) < min_required_seat_count: raise BillingError( 'lowball seat count', "You must invoice for at least %d users." % (min_required_seat_count,)) seat_count = int(request.POST['invoiced_seat_count']) process_initial_upgrade(user, plan, seat_count, request.POST.get('stripeToken', None)) except BillingError as e: error_message = e.message error_description = e.description except Exception as e: billing_logger.exception("Uncaught exception in billing: %s" % (e,)) error_message = BillingError.CONTACT_SUPPORT error_description = "uncaught exception during upgrade" else: return HttpResponseRedirect(reverse('corporate.views.billing_home')) seat_count = get_seat_count(user.realm) signed_seat_count, salt = sign_string(str(seat_count)) context = { 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'email': user.email, 'seat_count': seat_count, 'signed_seat_count': signed_seat_count, 'salt': salt, 'plan': "Zulip Standard", 'nickname_monthly': Plan.CLOUD_MONTHLY, 'nickname_annual': Plan.CLOUD_ANNUAL, 'error_message': error_message, 'cloud_monthly_price': 8, 'cloud_annual_price': 80, 'cloud_annual_price_per_month': 6.67, } # type: Dict[str, Any] response = render(request, 'corporate/upgrade.html', context=context) response['error_description'] = error_description return response
def setUp(self, *mocks: Mock) -> None: # TODO # Unfortunately this test suite is likely not robust to users being # added in populate_db. A quick hack for now to ensure get_seat_count is 8 # for these tests (8, since that's what it was when the tests were written). realm = get_realm('zulip') seat_count = get_seat_count(get_realm('zulip')) assert(seat_count >= 8) if seat_count > 8: # nocoverage for user in UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False) \ .exclude(email__in=[ self.example_email('hamlet'), self.example_email('iago')])[6:]: user.is_active = False user.save(update_fields=['is_active']) self.assertEqual(get_seat_count(get_realm('zulip')), 8) self.seat_count = 8 self.signed_seat_count, self.salt = sign_string(str(self.seat_count)) # Choosing dates with corresponding timestamps below 1500000000 so that they are # not caught by our timestamp normalization regex in normalize_fixture_data self.now = datetime(2012, 1, 2, 3, 4, 5).replace(tzinfo=timezone_utc) self.next_month = datetime(2012, 2, 2, 3, 4, 5).replace(tzinfo=timezone_utc) self.next_year = datetime(2013, 1, 2, 3, 4, 5).replace(tzinfo=timezone_utc)
def initial_upgrade(request: HttpRequest) -> HttpResponse: if not settings.BILLING_ENABLED: return render(request, "404.html") user = request.user customer = Customer.objects.filter(realm=user.realm).first() if customer is not None and customer.has_billing_relationship: return HttpResponseRedirect(reverse('corporate.views.billing_home')) percent_off = 0 if customer is not None: stripe_customer = stripe_get_customer(customer.stripe_customer_id) if stripe_customer.discount is not None: percent_off = stripe_customer.discount.coupon.percent_off seat_count = get_seat_count(user.realm) signed_seat_count, salt = sign_string(str(seat_count)) context = { 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'email': user.email, 'seat_count': seat_count, 'signed_seat_count': signed_seat_count, 'salt': salt, 'min_seat_count_for_invoice': max(seat_count, MIN_INVOICED_SEAT_COUNT), 'default_invoice_days_until_due': DEFAULT_INVOICE_DAYS_UNTIL_DUE, 'plan': "Zulip Standard", 'nickname_monthly': Plan.CLOUD_MONTHLY, 'nickname_annual': Plan.CLOUD_ANNUAL, 'page_params': JSONEncoderForHTML().encode({ 'seat_count': seat_count, 'nickname_annual': Plan.CLOUD_ANNUAL, 'nickname_monthly': Plan.CLOUD_MONTHLY, 'annual_price': 8000, 'monthly_price': 800, 'percent_off': percent_off, }), } # type: Dict[str, Any] response = render(request, 'corporate/upgrade.html', context=context) return response
def initial_upgrade(request: HttpRequest) -> HttpResponse: if not settings.BILLING_ENABLED: return render(request, "404.html") user = request.user error_message = "" error_description = "" # only used in tests customer = Customer.objects.filter(realm=user.realm).first() if customer is not None and customer.has_billing_relationship: return HttpResponseRedirect(reverse('corporate.views.billing_home')) percent_off = 0 if customer is not None: stripe_customer = stripe_get_customer(customer.stripe_customer_id) if stripe_customer.discount is not None: percent_off = stripe_customer.discount.coupon.percent_off if request.method == 'POST': try: plan, seat_count = unsign_and_check_upgrade_parameters( user, request.POST['plan'], request.POST['signed_seat_count'], request.POST['salt'], request.POST['billing_modality']) if request.POST['billing_modality'] == 'send_invoice': try: invoiced_seat_count = int(request.POST['invoiced_seat_count']) except (KeyError, ValueError): invoiced_seat_count = -1 min_required_seat_count = max(seat_count, MIN_INVOICED_SEAT_COUNT) if invoiced_seat_count < min_required_seat_count: raise BillingError( 'lowball seat count', "You must invoice for at least %d users." % (min_required_seat_count,)) seat_count = invoiced_seat_count process_initial_upgrade(user, plan, seat_count, request.POST.get('stripe_token', None)) except BillingError as e: error_message = e.message error_description = e.description except Exception as e: billing_logger.exception("Uncaught exception in billing: %s" % (e,)) error_message = BillingError.CONTACT_SUPPORT error_description = "uncaught exception during upgrade" else: return HttpResponseRedirect(reverse('corporate.views.billing_home')) seat_count = get_seat_count(user.realm) signed_seat_count, salt = sign_string(str(seat_count)) context = { 'publishable_key': STRIPE_PUBLISHABLE_KEY, 'email': user.email, 'seat_count': seat_count, 'signed_seat_count': signed_seat_count, 'salt': salt, 'min_seat_count_for_invoice': max(seat_count, MIN_INVOICED_SEAT_COUNT), 'default_invoice_days_until_due': DEFAULT_INVOICE_DAYS_UNTIL_DUE, 'plan': "Zulip Standard", 'nickname_monthly': Plan.CLOUD_MONTHLY, 'nickname_annual': Plan.CLOUD_ANNUAL, 'error_message': error_message, 'page_params': JSONEncoderForHTML().encode({ 'seat_count': seat_count, 'nickname_annual': Plan.CLOUD_ANNUAL, 'nickname_monthly': Plan.CLOUD_MONTHLY, 'annual_price': 8000, 'monthly_price': 800, 'percent_off': percent_off, }), } # type: Dict[str, Any] response = render(request, 'corporate/upgrade.html', context=context) response['error_description'] = error_description return response