Example #1
0
def initial_upgrade(request: HttpRequest) -> HttpResponse:
    if not settings.BILLING_ENABLED:
        return render(request, "404.html")

    user = request.user
    customer = get_customer_by_realm(user.realm)
    if customer is not None and get_current_plan_by_customer(
            customer) is not None:
        return HttpResponseRedirect(reverse('corporate.views.billing_home'))

    percent_off = Decimal(0)
    if customer is not None and customer.default_discount is not None:
        percent_off = customer.default_discount

    seat_count = get_latest_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context = {
        'publishable_key': STRIPE_PUBLISHABLE_KEY,
        'email': user.delivery_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': {
            '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
Example #2
0
    def test_notify_realm_of_new_user_in_manual_license_management(
            self) -> None:
        realm = get_realm("zulip")

        user_count = get_latest_seat_count(realm)
        self.subscribe_realm_to_monthly_plan_on_manual_license_management(
            realm, user_count + 5, user_count + 5)

        def create_new_user_and_verify_strings_in_notification_message(
                strings_present: Sequence[str] = [],
                strings_absent: Sequence[str] = []) -> None:
            user_no = random.randrange(100000)
            new_user = UserProfile.objects.create(
                realm=realm,
                full_name=f"new user {user_no}",
                email=f"user-{user_no}[email protected]",
                delivery_email=f"user-{user_no}[email protected]",
            )
            notify_new_user(new_user)

            message = self.get_last_message()
            actual_stream = Stream.objects.get(id=message.recipient.type_id)
            self.assertEqual(actual_stream, realm.signup_notifications_stream)
            self.assertIn(
                f"@_**new user {user_no}|{new_user.id}** just signed up for Zulip.",
                message.content,
            )
            for string_present in strings_present:
                self.assertIn(
                    string_present,
                    message.content,
                )
            for string_absent in strings_absent:
                self.assertNotIn(string_absent, message.content)

        create_new_user_and_verify_strings_in_notification_message(
            strings_absent=["Your organization has"])
        create_new_user_and_verify_strings_in_notification_message(
            strings_present=[
                "Your organization has only three Zulip licenses remaining",
                "to allow more than three users to",
            ], )
        create_new_user_and_verify_strings_in_notification_message(
            strings_present=[
                "Your organization has only two Zulip licenses remaining",
                "to allow more than two users to",
            ], )

        create_new_user_and_verify_strings_in_notification_message(
            strings_present=[
                "Your organization has only one Zulip license remaining",
                "to allow more than one user to",
            ], )
        create_new_user_and_verify_strings_in_notification_message(
            strings_present=[
                "Your organization has no Zulip licenses remaining",
                "to allow new users to",
            ])
Example #3
0
def billing_home(request: HttpRequest) -> HttpResponse:
    user = request.user
    customer = get_customer_by_realm(user.realm)
    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: Dict[str, Any] = {'admin_access': False}
        return render(request, 'corporate/billing.html', context=context)

    context = {
        'admin_access': True,
        'has_active_plan': False,
    }

    plan = get_current_plan_by_customer(customer)
    if plan is not None:
        now = timezone_now()
        last_ledger_entry = make_end_of_cycle_updates_if_needed(plan, now)
        if last_ledger_entry is not None:
            plan_name = {
                CustomerPlan.STANDARD: 'Zulip Standard',
                CustomerPlan.PLUS: 'Zulip Plus',
            }[plan.tier]
            free_trial = plan.status == CustomerPlan.FREE_TRIAL
            downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
            licenses = last_ledger_entry.licenses
            licenses_used = get_latest_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
            stripe_customer = stripe_get_customer(customer.stripe_customer_id)
            if charge_automatically:
                payment_method = payment_method_string(stripe_customer)
            else:
                payment_method = 'Billed by invoice'

            context.update({
                'plan_name': plan_name,
                'has_active_plan': True,
                'free_trial': free_trial,
                'downgrade_at_end_of_cycle': downgrade_at_end_of_cycle,
                '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,
                'CustomerPlan': CustomerPlan,
                'onboarding': request.GET.get("onboarding") is not None,
            })

    return render(request, 'corporate/billing.html', context=context)
Example #4
0
def check_spare_licenses_available_for_adding_new_users(
        realm: Realm, number_of_users_to_add: int) -> None:
    plan = get_current_plan_by_realm(realm)
    if (plan is None or plan.automanage_licenses
            or plan.customer.exempt_from_from_license_number_check):
        return

    if plan.licenses() < get_latest_seat_count(realm) + number_of_users_to_add:
        raise LicenseLimitError()
Example #5
0
def initial_upgrade(
    request: HttpRequest, onboarding: bool = REQ(default=False, json_validator=check_bool)
) -> HttpResponse:
    user = request.user
    assert user.is_authenticated

    if not settings.BILLING_ENABLED or user.is_guest:
        return render(request, "404.html", status=404)

    billing_page_url = reverse(billing_home)

    customer = get_customer_by_realm(user.realm)
    if customer is not None and (
        get_current_plan_by_customer(customer) is not None or customer.sponsorship_pending
    ):
        if onboarding:
            billing_page_url = f"{billing_page_url}?onboarding=true"
        return HttpResponseRedirect(billing_page_url)

    if is_sponsored_realm(user.realm):
        return HttpResponseRedirect(billing_page_url)

    percent_off = Decimal(0)
    if customer is not None and customer.default_discount is not None:
        percent_off = customer.default_discount

    seat_count = get_latest_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context: Dict[str, Any] = {
        "realm": user.realm,
        "email": user.delivery_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",
        "free_trial_days": settings.FREE_TRIAL_DAYS,
        "onboarding": onboarding,
        "page_params": {
            "seat_count": seat_count,
            "annual_price": 8000,
            "monthly_price": 800,
            "percent_off": float(percent_off),
        },
        "realm_org_type": user.realm.org_type,
        "sorted_org_types": sorted(
            (
                [org_type_name, org_type]
                for (org_type_name, org_type) in Realm.ORG_TYPES.items()
                if not org_type.get("hidden")
            ),
            key=lambda d: d[1]["display_order"],
        ),
    }
    response = render(request, "corporate/upgrade.html", context=context)
    return response
Example #6
0
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_latest_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)
Example #7
0
def initial_upgrade(request: HttpRequest) -> HttpResponse:
    user = request.user

    if not settings.BILLING_ENABLED or user.is_guest:
        return render(request, "404.html", status=404)

    billing_page_url = reverse(billing_home)

    customer = get_customer_by_realm(user.realm)
    if customer is not None and (
        get_current_plan_by_customer(customer) is not None or customer.sponsorship_pending
    ):
        if request.GET.get("onboarding") is not None:
            billing_page_url = f"{billing_page_url}?onboarding=true"
        return HttpResponseRedirect(billing_page_url)

    if user.realm.plan_type == user.realm.STANDARD_FREE:
        return HttpResponseRedirect(billing_page_url)

    percent_off = Decimal(0)
    if customer is not None and customer.default_discount is not None:
        percent_off = customer.default_discount

    seat_count = get_latest_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context: Dict[str, Any] = {
        "realm": user.realm,
        "publishable_key": STRIPE_PUBLISHABLE_KEY,
        "email": user.delivery_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",
        "free_trial_days": settings.FREE_TRIAL_DAYS,
        "onboarding": request.GET.get("onboarding") is not None,
        "page_params": {
            "seat_count": seat_count,
            "annual_price": 8000,
            "monthly_price": 800,
            "percent_off": float(percent_off),
        },
    }
    response = render(request, "corporate/upgrade.html", context=context)
    return response
Example #8
0
def initial_upgrade(request: HttpRequest) -> HttpResponse:
    user = request.user

    if not settings.BILLING_ENABLED or user.is_guest:
        return render(request, "404.html", status=404)

    customer = get_customer_by_realm(user.realm)
    if customer is not None and (get_current_plan_by_customer(customer)
                                 is not None or customer.sponsorship_pending):
        billing_page_url = reverse('corporate.views.billing_home')
        if request.GET.get("onboarding") is not None:
            billing_page_url = f"{billing_page_url}?onboarding=true"
        return HttpResponseRedirect(billing_page_url)

    percent_off = Decimal(0)
    if customer is not None and customer.default_discount is not None:
        percent_off = customer.default_discount

    seat_count = get_latest_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context: Dict[str, Any] = {
        'realm': user.realm,
        'publishable_key': STRIPE_PUBLISHABLE_KEY,
        'email': user.delivery_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",
        "free_trial_days": settings.FREE_TRIAL_DAYS,
        "onboarding": request.GET.get("onboarding") is not None,
        'page_params': {
            'seat_count': seat_count,
            'annual_price': 8000,
            'monthly_price': 800,
            'percent_off': float(percent_off),
        },
    }
    response = render(request, 'corporate/upgrade.html', context=context)
    return response
Example #9
0
def generate_licenses_low_warning_message_if_required(realm: Realm) -> Optional[str]:
    plan = get_current_plan_by_realm(realm)
    if plan is None or plan.automanage_licenses:
        return None

    licenses_remaining = plan.licenses() - get_latest_seat_count(realm)
    if licenses_remaining > 3:
        return None

    format_kwargs = {
        "billing_page_link": "/billing/#settings",
        "deactivate_user_help_page_link": "/help/deactivate-or-reactivate-a-user",
    }

    if licenses_remaining <= 0:
        return _(
            "Your organization has no Zulip licenses remaining and can no longer accept new users. "
            "Please [increase the number of licenses]({billing_page_link}) or "
            "[deactivate inactive users]({deactivate_user_help_page_link}) to allow new users to join."
        ).format(**format_kwargs)

    return {
        1: _(
            "Your organization has only one Zulip license remaining. You can "
            "[increase the number of licenses]({billing_page_link}) or [deactivate inactive users]({deactivate_user_help_page_link}) "
            "to allow more than one user to join."
        ),
        2: _(
            "Your organization has only two Zulip licenses remaining. You can "
            "[increase the number of licenses]({billing_page_link}) or [deactivate inactive users]({deactivate_user_help_page_link}) "
            "to allow more than two users to join."
        ),
        3: _(
            "Your organization has only three Zulip licenses remaining. You can "
            "[increase the number of licenses]({billing_page_link}) or [deactivate inactive users]({deactivate_user_help_page_link}) "
            "to allow more than three users to join."
        ),
    }[licenses_remaining].format(**format_kwargs)
Example #10
0
def billing_home(request: HttpRequest) -> HttpResponse:
    user = request.user
    assert user.is_authenticated

    customer = get_customer_by_realm(user.realm)
    context: Dict[str, Any] = {
        "admin_access": user.has_billing_access,
        "has_active_plan": False,
    }

    if user.realm.plan_type == user.realm.STANDARD_FREE:
        context["is_sponsored"] = True
        return render(request, "corporate/billing.html", context=context)

    if customer is None:
        from corporate.views.upgrade import initial_upgrade

        return HttpResponseRedirect(reverse(initial_upgrade))

    if customer.sponsorship_pending:
        context["sponsorship_pending"] = True
        return render(request, "corporate/billing.html", context=context)

    if not CustomerPlan.objects.filter(customer=customer).exists():
        from corporate.views.upgrade import initial_upgrade

        return HttpResponseRedirect(reverse(initial_upgrade))

    if not user.has_billing_access:
        return render(request, "corporate/billing.html", context=context)

    plan = get_current_plan_by_customer(customer)
    if plan is not None:
        now = timezone_now()
        new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
            plan, now)
        if last_ledger_entry is not None:
            if new_plan is not None:  # nocoverage
                plan = new_plan
            assert plan is not None  # for mypy
            downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
            switch_to_annual_at_end_of_cycle = (
                plan.status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE)
            licenses = last_ledger_entry.licenses
            licenses_at_next_renewal = last_ledger_entry.licenses_at_next_renewal
            seat_count = get_latest_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
            assert customer.stripe_customer_id is not None  # for mypy
            stripe_customer = stripe_get_customer(customer.stripe_customer_id)
            if charge_automatically:
                payment_method = payment_method_string(stripe_customer)
            else:
                payment_method = "Billed by invoice"

            context.update(
                plan_name=plan.name,
                has_active_plan=True,
                free_trial=plan.is_free_trial(),
                downgrade_at_end_of_cycle=downgrade_at_end_of_cycle,
                automanage_licenses=plan.automanage_licenses,
                switch_to_annual_at_end_of_cycle=
                switch_to_annual_at_end_of_cycle,
                licenses=licenses,
                licenses_at_next_renewal=licenses_at_next_renewal,
                seat_count=seat_count,
                renewal_date=renewal_date,
                renewal_amount=cents_to_dollar_string(renewal_cents),
                payment_method=payment_method,
                charge_automatically=charge_automatically,
                publishable_key=STRIPE_PUBLISHABLE_KEY,
                stripe_email=stripe_customer.email,
                CustomerPlan=CustomerPlan,
                onboarding=request.GET.get("onboarding") is not None,
            )

    return render(request, "corporate/billing.html", context=context)
Example #11
0
def update_plan(
    request: HttpRequest,
    user: UserProfile,
    status: Optional[int] = REQ(
        "status",
        json_validator=check_int_in([
            CustomerPlan.ACTIVE,
            CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE,
            CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE,
            CustomerPlan.ENDED,
        ]),
        default=None,
    ),
    licenses: Optional[int] = REQ("licenses",
                                  json_validator=check_int,
                                  default=None),
    licenses_at_next_renewal: Optional[int] = REQ("licenses_at_next_renewal",
                                                  json_validator=check_int,
                                                  default=None),
) -> HttpResponse:
    plan = get_current_plan_by_realm(user.realm)
    assert plan is not None  # for mypy

    new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
        plan, timezone_now())
    if new_plan is not None:
        raise JsonableError(
            _("Unable to update the plan. The plan has been expired and replaced with a new plan."
              ))

    if last_ledger_entry is None:
        raise JsonableError(
            _("Unable to update the plan. The plan has ended."))

    if status is not None:
        if status == CustomerPlan.ACTIVE:
            assert plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
            do_change_plan_status(plan, status)
        elif status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE:
            assert plan.status == CustomerPlan.ACTIVE
            downgrade_at_the_end_of_billing_cycle(user.realm)
        elif status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE:
            assert plan.billing_schedule == CustomerPlan.MONTHLY
            assert plan.status == CustomerPlan.ACTIVE
            assert plan.fixed_price is None
            do_change_plan_status(plan, status)
        elif status == CustomerPlan.ENDED:
            assert plan.is_free_trial()
            downgrade_now_without_creating_additional_invoices(user.realm)
        return json_success()

    if licenses is not None:
        if plan.automanage_licenses:
            raise JsonableError(
                _("Unable to update licenses manually. Your plan is on automatic license management."
                  ))
        if last_ledger_entry.licenses == licenses:
            raise JsonableError(
                _("Your plan is already on {licenses} licenses in the current billing period."
                  ).format(licenses=licenses))
        if last_ledger_entry.licenses > licenses:
            raise JsonableError(
                _("You cannot decrease the licenses in the current billing period."
                  ).format(licenses=licenses))
        validate_licenses(plan.charge_automatically, licenses,
                          get_latest_seat_count(user.realm))
        update_license_ledger_for_manual_plan(plan,
                                              timezone_now(),
                                              licenses=licenses)
        return json_success()

    if licenses_at_next_renewal is not None:
        if plan.automanage_licenses:
            raise JsonableError(
                _("Unable to update licenses manually. Your plan is on automatic license management."
                  ))
        if last_ledger_entry.licenses_at_next_renewal == licenses_at_next_renewal:
            raise JsonableError(
                _("Your plan is already scheduled to renew with {licenses_at_next_renewal} licenses."
                  ).format(licenses_at_next_renewal=licenses_at_next_renewal))
        validate_licenses(
            plan.charge_automatically,
            licenses_at_next_renewal,
            get_latest_seat_count(user.realm),
        )
        update_license_ledger_for_manual_plan(
            plan,
            timezone_now(),
            licenses_at_next_renewal=licenses_at_next_renewal)
        return json_success()

    raise JsonableError(_("Nothing to change."))
Example #12
0
def support(
    request: HttpRequest,
    realm_id: Optional[int] = REQ(default=None, converter=to_non_negative_int),
    plan_type: Optional[int] = REQ(default=None,
                                   converter=to_non_negative_int),
    discount: Optional[Decimal] = REQ(default=None, converter=to_decimal),
    new_subdomain: Optional[str] = REQ(default=None),
    status: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_STATUS_VALUES)),
    billing_method: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_BILLING_METHODS)),
    sponsorship_pending: Optional[bool] = REQ(default=None,
                                              json_validator=check_bool),
    approve_sponsorship: Optional[bool] = REQ(default=None,
                                              json_validator=check_bool),
    downgrade_method: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_DOWNGRADE_METHODS)),
    scrub_realm: Optional[bool] = REQ(default=None, json_validator=check_bool),
    query: Optional[str] = REQ("q", default=None),
    org_type: Optional[int] = REQ(default=None, converter=to_non_negative_int),
) -> HttpResponse:
    context: Dict[str, Any] = {}

    if "success_message" in request.session:
        context["success_message"] = request.session["success_message"]
        del request.session["success_message"]

    if settings.BILLING_ENABLED and request.method == "POST":
        # We check that request.POST only has two keys in it: The
        # realm_id and a field to change.
        keys = set(request.POST.keys())
        if "csrfmiddlewaretoken" in keys:
            keys.remove("csrfmiddlewaretoken")
        if len(keys) != 2:
            raise JsonableError(_("Invalid parameters"))

        realm = Realm.objects.get(id=realm_id)

        acting_user = request.user
        assert isinstance(acting_user, UserProfile)
        if plan_type is not None:
            current_plan_type = realm.plan_type
            do_change_plan_type(realm, plan_type, acting_user=acting_user)
            msg = f"Plan type of {realm.string_id} changed from {get_plan_name(current_plan_type)} to {get_plan_name(plan_type)} "
            context["success_message"] = msg
        elif org_type is not None:
            current_realm_type = realm.org_type
            do_change_realm_org_type(realm, org_type, acting_user=acting_user)
            msg = f"Org type of {realm.string_id} changed from {get_org_type_display_name(current_realm_type)} to {get_org_type_display_name(org_type)} "
            context["success_message"] = msg
        elif discount is not None:
            current_discount = get_discount_for_realm(realm) or 0
            attach_discount_to_realm(realm, discount, acting_user=acting_user)
            context[
                "success_message"] = f"Discount of {realm.string_id} changed to {discount}% from {current_discount}%."
        elif new_subdomain is not None:
            old_subdomain = realm.string_id
            try:
                check_subdomain_available(new_subdomain)
            except ValidationError as error:
                context["error_message"] = error.message
            else:
                do_change_realm_subdomain(realm,
                                          new_subdomain,
                                          acting_user=acting_user)
                request.session[
                    "success_message"] = f"Subdomain changed from {old_subdomain} to {new_subdomain}"
                return HttpResponseRedirect(
                    reverse("support") + "?" + urlencode({"q": new_subdomain}))
        elif status is not None:
            if status == "active":
                do_send_realm_reactivation_email(realm,
                                                 acting_user=acting_user)
                context[
                    "success_message"] = f"Realm reactivation email sent to admins of {realm.string_id}."
            elif status == "deactivated":
                do_deactivate_realm(realm, acting_user=acting_user)
                context["success_message"] = f"{realm.string_id} deactivated."
        elif billing_method is not None:
            if billing_method == "send_invoice":
                update_billing_method_of_current_plan(
                    realm, charge_automatically=False, acting_user=acting_user)
                context[
                    "success_message"] = f"Billing method of {realm.string_id} updated to pay by invoice."
            elif billing_method == "charge_automatically":
                update_billing_method_of_current_plan(
                    realm, charge_automatically=True, acting_user=acting_user)
                context[
                    "success_message"] = f"Billing method of {realm.string_id} updated to charge automatically."
        elif sponsorship_pending is not None:
            if sponsorship_pending:
                update_sponsorship_status(realm, True, acting_user=acting_user)
                context[
                    "success_message"] = f"{realm.string_id} marked as pending sponsorship."
            else:
                update_sponsorship_status(realm,
                                          False,
                                          acting_user=acting_user)
                context[
                    "success_message"] = f"{realm.string_id} is no longer pending sponsorship."
        elif approve_sponsorship:
            do_approve_sponsorship(realm, acting_user=acting_user)
            context[
                "success_message"] = f"Sponsorship approved for {realm.string_id}"
        elif downgrade_method is not None:
            if downgrade_method == "downgrade_at_billing_cycle_end":
                downgrade_at_the_end_of_billing_cycle(realm)
                context[
                    "success_message"] = f"{realm.string_id} marked for downgrade at the end of billing cycle"
            elif downgrade_method == "downgrade_now_without_additional_licenses":
                downgrade_now_without_creating_additional_invoices(realm)
                context[
                    "success_message"] = f"{realm.string_id} downgraded without creating additional invoices"
            elif downgrade_method == "downgrade_now_void_open_invoices":
                downgrade_now_without_creating_additional_invoices(realm)
                voided_invoices_count = void_all_open_invoices(realm)
                context[
                    "success_message"] = f"{realm.string_id} downgraded and voided {voided_invoices_count} open invoices"
        elif scrub_realm:
            do_scrub_realm(realm, acting_user=acting_user)
            context["success_message"] = f"{realm.string_id} scrubbed."

    if query:
        key_words = get_invitee_emails_set(query)

        users = set(UserProfile.objects.filter(delivery_email__in=key_words))
        realms = set(Realm.objects.filter(string_id__in=key_words))

        for key_word in key_words:
            try:
                URLValidator()(key_word)
                parse_result = urllib.parse.urlparse(key_word)
                hostname = parse_result.hostname
                assert hostname is not None
                if parse_result.port:
                    hostname = f"{hostname}:{parse_result.port}"
                subdomain = get_subdomain_from_hostname(hostname)
                try:
                    realms.add(get_realm(subdomain))
                except Realm.DoesNotExist:
                    pass
            except ValidationError:
                users.update(
                    UserProfile.objects.filter(full_name__iexact=key_word))

        for realm in realms:
            realm.customer = get_customer_by_realm(realm)

            current_plan = get_current_plan_by_realm(realm)
            if current_plan is not None:
                new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
                    current_plan, timezone_now())
                if last_ledger_entry is not None:
                    if new_plan is not None:
                        realm.current_plan = new_plan
                    else:
                        realm.current_plan = current_plan
                    realm.current_plan.licenses = last_ledger_entry.licenses
                    realm.current_plan.licenses_used = get_latest_seat_count(
                        realm)

        # full_names can have , in them
        users.update(UserProfile.objects.filter(full_name__iexact=query))

        context["users"] = users
        context["realms"] = realms

        confirmations: List[Dict[str, Any]] = []

        preregistration_users = PreregistrationUser.objects.filter(
            email__in=key_words)
        confirmations += get_confirmations(
            [
                Confirmation.USER_REGISTRATION, Confirmation.INVITATION,
                Confirmation.REALM_CREATION
            ],
            preregistration_users,
            hostname=request.get_host(),
        )

        multiuse_invites = MultiuseInvite.objects.filter(realm__in=realms)
        confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE],
                                           multiuse_invites)

        confirmations += get_confirmations([Confirmation.REALM_REACTIVATION],
                                           [realm.id for realm in realms])

        context["confirmations"] = confirmations

    def get_realm_owner_emails_as_string(realm: Realm) -> str:
        return ", ".join(realm.get_human_owner_users().order_by(
            "delivery_email").values_list("delivery_email", flat=True))

    def get_realm_admin_emails_as_string(realm: Realm) -> str:
        return ", ".join(
            realm.get_human_admin_users(include_realm_owners=False).order_by(
                "delivery_email").values_list("delivery_email", flat=True))

    context[
        "get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string
    context[
        "get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string
    context["get_discount_for_realm"] = get_discount_for_realm
    context["get_org_type_display_name"] = get_org_type_display_name
    context["realm_icon_url"] = realm_icon_url
    context["Confirmation"] = Confirmation
    context["sorted_realm_types"] = sorted(Realm.ORG_TYPES.values(),
                                           key=lambda d: d["display_order"])

    return render(request, "analytics/support.html", context=context)
Example #13
0
def billing_home(request: HttpRequest) -> HttpResponse:
    user = request.user
    customer = get_customer_by_realm(user.realm)
    context: Dict[str, Any] = {
        "admin_access": user.has_billing_access,
        'has_active_plan': False,
    }

    if user.realm.plan_type == user.realm.STANDARD_FREE:
        context["is_sponsored"] = True
        return render(request, 'corporate/billing.html', context=context)

    if customer is None:
        return HttpResponseRedirect(reverse('corporate.views.initial_upgrade'))

    if customer.sponsorship_pending:
        context["sponsorship_pending"] = True
        return render(request, 'corporate/billing.html', context=context)

    if not CustomerPlan.objects.filter(customer=customer).exists():
        return HttpResponseRedirect(reverse('corporate.views.initial_upgrade'))

    if not user.has_billing_access:
        return render(request, 'corporate/billing.html', context=context)

    plan = get_current_plan_by_customer(customer)
    if plan is not None:
        now = timezone_now()
        new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
            plan, now)
        if last_ledger_entry is not None:
            if new_plan is not None:  # nocoverage
                plan = new_plan
            assert (plan is not None)  # for mypy
            free_trial = plan.status == CustomerPlan.FREE_TRIAL
            downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
            switch_to_annual_at_end_of_cycle = plan.status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE
            licenses = last_ledger_entry.licenses
            licenses_used = get_latest_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
            stripe_customer = stripe_get_customer(customer.stripe_customer_id)
            if charge_automatically:
                payment_method = payment_method_string(stripe_customer)
            else:
                payment_method = 'Billed by invoice'

            context.update({
                'plan_name':
                plan.name,
                'has_active_plan':
                True,
                'free_trial':
                free_trial,
                'downgrade_at_end_of_cycle':
                downgrade_at_end_of_cycle,
                'automanage_licenses':
                plan.automanage_licenses,
                'switch_to_annual_at_end_of_cycle':
                switch_to_annual_at_end_of_cycle,
                'licenses':
                licenses,
                'licenses_used':
                licenses_used,
                'renewal_date':
                renewal_date,
                'renewal_amount':
                f'{renewal_cents / 100.:,.2f}',
                'payment_method':
                payment_method,
                'charge_automatically':
                charge_automatically,
                'publishable_key':
                STRIPE_PUBLISHABLE_KEY,
                'stripe_email':
                stripe_customer.email,
                'CustomerPlan':
                CustomerPlan,
                'onboarding':
                request.GET.get("onboarding") is not None,
            })

    return render(request, 'corporate/billing.html', context=context)