diff --git a/corporate/lib/remote_billing_util.py b/corporate/lib/remote_billing_util.py index 7fb9f26100..42f02d7241 100644 --- a/corporate/lib/remote_billing_util.py +++ b/corporate/lib/remote_billing_util.py @@ -4,6 +4,9 @@ from typing import Optional, TypedDict from django.http import HttpRequest from django.utils.translation import gettext as _ +from zerver.lib.exceptions import JsonableError +from zilencer.models import RemoteRealm + billing_logger = logging.getLogger("corporate.stripe") @@ -28,3 +31,35 @@ def get_identity_dict_from_session( return identity_dicts.get(authed_uuid) return None + + +def get_remote_realm_from_session( + request: HttpRequest, + realm_uuid: Optional[str], + server_uuid: Optional[str] = None, +) -> RemoteRealm: + identity_dict = get_identity_dict_from_session(request, realm_uuid, server_uuid) + + if identity_dict is None: + raise JsonableError(_("User not authenticated")) + + remote_server_uuid = identity_dict["remote_server_uuid"] + remote_realm_uuid = identity_dict["remote_realm_uuid"] + + try: + remote_realm = RemoteRealm.objects.get( + uuid=remote_realm_uuid, server__uuid=remote_server_uuid + ) + except RemoteRealm.DoesNotExist: + raise AssertionError( + "The remote realm is missing despite being in the RemoteBillingIdentityDict" + ) + + if ( + remote_realm.registration_deactivated + or remote_realm.realm_deactivated + or remote_realm.server.deactivated + ): + raise JsonableError(_("Registration is deactivated")) + + return remote_realm diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 6f6296ce0b..38db591fad 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -1846,6 +1846,14 @@ class RemoteRealmBillingSession(BillingSession): # nocoverage # TBD pass + @override + def is_sponsored_or_pending(self, customer: Optional[Customer]) -> bool: + if ( + customer is not None and customer.sponsorship_pending + ) or self.remote_realm.plan_type == self.remote_realm.PLAN_TYPE_COMMUNITY: + return True + return False + @override def get_upgrade_page_session_type_specific_context( self, diff --git a/corporate/urls.py b/corporate/urls.py index 5ba8cc366e..6dcd153bed 100644 --- a/corporate/urls.py +++ b/corporate/urls.py @@ -27,7 +27,7 @@ from corporate.views.session import ( start_card_update_stripe_session_for_realm_upgrade, ) from corporate.views.support import support_request -from corporate.views.upgrade import sponsorship, upgrade, upgrade_page +from corporate.views.upgrade import remote_realm_upgrade_page, sponsorship, upgrade, upgrade_page from corporate.views.webhook import stripe_webhook from zerver.lib.rest import rest_path from zerver.lib.url_redirects import LANDING_PAGE_REDIRECTS @@ -168,4 +168,5 @@ urlpatterns += [ ), path("realm//billing", remote_billing_page_realm, name="remote_billing_page_realm"), path("server//", remote_billing_page_server, name="remote_billing_page_server"), + path("realm//upgrade", remote_realm_upgrade_page, name="remote_realm_upgrade_page"), ] diff --git a/corporate/views/upgrade.py b/corporate/views/upgrade.py index 53a6dba4bd..e82e659dc0 100644 --- a/corporate/views/upgrade.py +++ b/corporate/views/upgrade.py @@ -6,7 +6,9 @@ from django.conf import settings from django.db import transaction from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.shortcuts import render +from pydantic import Json +from corporate.lib.remote_billing_util import get_remote_realm_from_session from corporate.lib.stripe import ( VALID_BILLING_MODALITY_VALUES, VALID_BILLING_SCHEDULE_VALUES, @@ -14,15 +16,21 @@ from corporate.lib.stripe import ( BillingError, InitialUpgradeRequest, RealmBillingSession, + RemoteRealmBillingSession, UpgradeRequest, ) from corporate.lib.support import get_support_url from corporate.models import CustomerPlan, ZulipSponsorshipRequest from zerver.actions.users import do_change_is_billing_admin -from zerver.decorator import require_organization_member, zulip_login_required +from zerver.decorator import ( + require_organization_member, + self_hosting_management_endpoint, + zulip_login_required, +) from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success from zerver.lib.send_email import FromAddress, send_email +from zerver.lib.typed_endpoint import PathOnly, typed_endpoint from zerver.lib.validator import check_bool, check_int, check_string_in from zerver.models import UserProfile, get_org_type_display_name @@ -102,6 +110,29 @@ def upgrade_page( return response +@self_hosting_management_endpoint +@typed_endpoint +def remote_realm_upgrade_page( + request: HttpRequest, + *, + realm_uuid: PathOnly[str], + manual_license_management: Json[bool] = False, +) -> HttpResponse: # nocoverage + remote_realm = get_remote_realm_from_session(request, realm_uuid) + initial_upgrade_request = InitialUpgradeRequest( + manual_license_management=manual_license_management, + tier=CustomerPlan.STANDARD, + ) + billing_session = RemoteRealmBillingSession(remote_realm) + redirect_url, context = billing_session.get_initial_upgrade_context(initial_upgrade_request) + + if redirect_url: + return HttpResponseRedirect(redirect_url) + + response = render(request, "corporate/upgrade.html", context=context) + return response + + class SponsorshipRequestForm(forms.Form): website = forms.URLField(max_length=ZulipSponsorshipRequest.MAX_ORG_URL_LENGTH, required=False) organization_type = forms.IntegerField() diff --git a/tools/test-backend b/tools/test-backend index 9704d0ccd1..9756e3f184 100755 --- a/tools/test-backend +++ b/tools/test-backend @@ -57,6 +57,7 @@ not_yet_fully_covered = [ # TODO: This is a work in progress and therefore without # tests yet. "corporate/views/remote_billing_page.py", + "corporate/lib/remote_billing_util.py", # Major lib files should have 100% coverage "zerver/actions/presence.py", "zerver/lib/addressee.py",