Пример #1
0
def approve_sponsorship(realm: Realm, *, acting_user: Optional[UserProfile]) -> None:
    from zerver.lib.actions import do_change_realm_plan_type, internal_send_private_message

    do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=acting_user)
    customer = get_customer_by_realm(realm)
    if customer is not None and customer.sponsorship_pending:
        customer.sponsorship_pending = False
        customer.save(update_fields=["sponsorship_pending"])
        RealmAuditLog.objects.create(
            realm=realm,
            acting_user=acting_user,
            event_type=RealmAuditLog.REALM_SPONSORSHIP_APPROVED,
            event_time=timezone_now(),
        )
    notification_bot = get_system_bot(settings.NOTIFICATION_BOT, realm.id)
    for user in realm.get_human_billing_admin_and_realm_owner_users():
        with override_language(user.default_language):
            # Using variable to make life easier for translators if these details change.
            plan_name = "Zulip Cloud Standard"
            emoji = ":tada:"
            message = _(
                f"Your organization's request for sponsored hosting has been approved! {emoji}.\n"
                f"You have been upgraded to {plan_name}, free of charge."
            )
            internal_send_private_message(notification_bot, user, message)
Пример #2
0
def process_downgrade(plan: CustomerPlan) -> None:
    from zerver.lib.actions import do_change_realm_plan_type

    assert plan.customer.realm is not None
    do_change_realm_plan_type(plan.customer.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None)
    plan.status = CustomerPlan.ENDED
    plan.save(update_fields=["status"])
Пример #3
0
    def test_promote_sponsoring_zulip_in_realm(self) -> None:
        realm = get_realm("zulip")

        do_change_realm_plan_type(realm,
                                  Realm.PLAN_TYPE_STANDARD_FREE,
                                  acting_user=None)
        promote_zulip = promote_sponsoring_zulip_in_realm(realm)
        self.assertTrue(promote_zulip)

        with self.settings(PROMOTE_SPONSORING_ZULIP=False):
            promote_zulip = promote_sponsoring_zulip_in_realm(realm)
        self.assertFalse(promote_zulip)

        do_change_realm_plan_type(realm,
                                  Realm.PLAN_TYPE_STANDARD_FREE,
                                  acting_user=None)
        promote_zulip = promote_sponsoring_zulip_in_realm(realm)
        self.assertTrue(promote_zulip)

        do_change_realm_plan_type(realm,
                                  Realm.PLAN_TYPE_LIMITED,
                                  acting_user=None)
        promote_zulip = promote_sponsoring_zulip_in_realm(realm)
        self.assertFalse(promote_zulip)

        do_change_realm_plan_type(realm,
                                  Realm.PLAN_TYPE_STANDARD,
                                  acting_user=None)
        promote_zulip = promote_sponsoring_zulip_in_realm(realm)
        self.assertFalse(promote_zulip)
Пример #4
0
def process_initial_upgrade(
    user: UserProfile,
    licenses: int,
    automanage_licenses: bool,
    billing_schedule: int,
    charge_automatically: bool,
    free_trial: bool,
) -> None:
    realm = user.realm
    customer = update_or_create_stripe_customer(user)
    assert customer.stripe_customer_id is not None  # for mypy
    assert customer.realm is not None
    ensure_realm_does_not_have_active_plan(customer.realm)
    (
        billing_cycle_anchor,
        next_invoice_date,
        period_end,
        price_per_license,
    ) = compute_plan_parameters(
        CustomerPlan.STANDARD,
        automanage_licenses,
        billing_schedule,
        customer.default_discount,
        free_trial,
    )

    # TODO: The correctness of this relies on user creation, deactivation, etc being
    # in a transaction.atomic() with the relevant RealmAuditLog entries
    with transaction.atomic():
        # billed_licenses can greater than licenses if users are added between the start of
        # this function (process_initial_upgrade) and now
        billed_licenses = max(get_latest_seat_count(realm), licenses)
        plan_params = {
            "automanage_licenses": automanage_licenses,
            "charge_automatically": charge_automatically,
            "price_per_license": price_per_license,
            "discount": customer.default_discount,
            "billing_cycle_anchor": billing_cycle_anchor,
            "billing_schedule": billing_schedule,
            "tier": CustomerPlan.STANDARD,
        }
        if free_trial:
            plan_params["status"] = CustomerPlan.FREE_TRIAL
        plan = CustomerPlan.objects.create(
            customer=customer, next_invoice_date=next_invoice_date, **plan_params
        )
        ledger_entry = LicenseLedger.objects.create(
            plan=plan,
            is_renewal=True,
            event_time=billing_cycle_anchor,
            licenses=billed_licenses,
            licenses_at_next_renewal=billed_licenses,
        )
        plan.invoiced_through = ledger_entry
        plan.save(update_fields=["invoiced_through"])
        RealmAuditLog.objects.create(
            realm=realm,
            acting_user=user,
            event_time=billing_cycle_anchor,
            event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED,
            extra_data=orjson.dumps(plan_params, default=decimal_to_float).decode(),
        )

    if not free_trial:
        stripe.InvoiceItem.create(
            currency="usd",
            customer=customer.stripe_customer_id,
            description="Zulip Cloud Standard",
            discountable=False,
            period={
                "start": datetime_to_timestamp(billing_cycle_anchor),
                "end": datetime_to_timestamp(period_end),
            },
            quantity=billed_licenses,
            unit_amount=price_per_license,
        )

        if charge_automatically:
            collection_method = "charge_automatically"
            days_until_due = None
        else:
            collection_method = "send_invoice"
            days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE

        stripe_invoice = stripe.Invoice.create(
            auto_advance=True,
            collection_method=collection_method,
            customer=customer.stripe_customer_id,
            days_until_due=days_until_due,
            statement_descriptor="Zulip Cloud Standard",
        )
        stripe.Invoice.finalize_invoice(stripe_invoice)

    from zerver.lib.actions import do_change_realm_plan_type

    do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=user)
Пример #5
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_realm_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)
Пример #6
0
    def test_get_billing_info(self) -> None:
        user = self.example_user("desdemona")
        user.role = UserProfile.ROLE_REALM_OWNER
        user.save(update_fields=["role"])
        # realm owner, but no CustomerPlan and realm plan_type SELF_HOSTED -> neither billing link or plans
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # realm owner, with inactive CustomerPlan and realm plan_type SELF_HOSTED -> show only billing link
        customer = Customer.objects.create(realm=get_realm("zulip"),
                                           stripe_customer_id="cus_id")
        CustomerPlan.objects.create(
            customer=customer,
            billing_cycle_anchor=timezone_now(),
            billing_schedule=CustomerPlan.ANNUAL,
            next_invoice_date=timezone_now(),
            tier=CustomerPlan.STANDARD,
            status=CustomerPlan.ENDED,
        )
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertTrue(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # realm owner, with inactive CustomerPlan and realm plan_type LIMITED -> show billing link and plans
        do_change_realm_plan_type(user.realm,
                                  Realm.PLAN_TYPE_LIMITED,
                                  acting_user=None)
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertTrue(billing_info.show_billing)
        self.assertTrue(billing_info.show_plans)

        # Always false without CORPORATE_ENABLED
        with self.settings(CORPORATE_ENABLED=False):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # Always false without a UserProfile
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(None)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # realm admin, with CustomerPlan and realm plan_type LIMITED -> show only billing plans
        user.role = UserProfile.ROLE_REALM_ADMINISTRATOR
        user.save(update_fields=["role"])
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertTrue(billing_info.show_plans)

        # billing admin, with CustomerPlan and realm plan_type STANDARD -> show only billing link
        user.role = UserProfile.ROLE_MEMBER
        user.is_billing_admin = True
        do_change_realm_plan_type(user.realm,
                                  Realm.PLAN_TYPE_STANDARD,
                                  acting_user=None)
        user.save(update_fields=["role", "is_billing_admin"])
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertTrue(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # billing admin, with CustomerPlan and realm plan_type PLUS -> show only billing link
        do_change_realm_plan_type(user.realm,
                                  Realm.PLAN_TYPE_PLUS,
                                  acting_user=None)
        user.save(update_fields=["role", "is_billing_admin"])
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertTrue(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # member, with CustomerPlan and realm plan_type STANDARD -> neither billing link or plans
        do_change_realm_plan_type(user.realm,
                                  Realm.PLAN_TYPE_STANDARD,
                                  acting_user=None)
        user.is_billing_admin = False
        user.save(update_fields=["is_billing_admin"])
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # guest, with CustomerPlan and realm plan_type SELF_HOSTED -> neither billing link or plans
        user.role = UserProfile.ROLE_GUEST
        user.save(update_fields=["role"])
        do_change_realm_plan_type(user.realm,
                                  Realm.PLAN_TYPE_SELF_HOSTED,
                                  acting_user=None)
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # billing admin, but no CustomerPlan and realm plan_type SELF_HOSTED -> neither billing link or plans
        user.role = UserProfile.ROLE_MEMBER
        user.is_billing_admin = True
        user.save(update_fields=["role", "is_billing_admin"])
        CustomerPlan.objects.all().delete()
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # billing admin, with sponsorship pending and relam plan_type SELF_HOSTED -> show only billing link
        customer.sponsorship_pending = True
        customer.save(update_fields=["sponsorship_pending"])
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertTrue(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)

        # billing admin, no customer object and relam plan_type SELF_HOSTED -> neither billing link or plans
        customer.delete()
        with self.settings(CORPORATE_ENABLED=True):
            billing_info = get_billing_info(user)
        self.assertFalse(billing_info.show_billing)
        self.assertFalse(billing_info.show_plans)