diff --git a/corporate/tests/test_remote_billing.py b/corporate/tests/test_remote_billing.py index 8bb37ddef8..bb70bc4a3b 100644 --- a/corporate/tests/test_remote_billing.py +++ b/corporate/tests/test_remote_billing.py @@ -106,8 +106,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase): # Go to the URL we're redirected to after authentication and assert # some basic expected content. result = self.client_get(result["Location"], subdomain="selfhosting") - self.assert_in_success_response(["Your remote user info:"], result) - self.assert_in_success_response([desdemona.delivery_email], result) + self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result) @responses.activate def test_remote_billing_authentication_flow_realm_not_registered(self) -> None: @@ -143,8 +142,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase): self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/") result = self.client_get(result["Location"], subdomain="selfhosting") - self.assert_in_success_response(["Your remote user info:"], result) - self.assert_in_success_response([desdemona.delivery_email], result) + self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result) @responses.activate def test_remote_billing_authentication_flow_tos_consent_failure(self) -> None: @@ -227,8 +225,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase): tick=False, ): result = self.client_get(final_url, subdomain="selfhosting") - self.assert_in_success_response(["Your remote user info:"], result) - self.assert_in_success_response([desdemona.delivery_email], result) + self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result) # Now go there again, simulating doing this after the session has expired. # We should be denied access and redirected to re-auth. @@ -257,8 +254,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase): ) self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/") result = self.client_get(result["Location"], subdomain="selfhosting") - self.assert_in_success_response(["Your remote user info:"], result) - self.assert_in_success_response([desdemona.delivery_email], result) + self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result) @responses.activate def test_remote_billing_unauthed_access(self) -> None: @@ -408,7 +404,7 @@ class LegacyServerLoginTest(BouncerTestCase): ) self.assertEqual(result.status_code, 302) - self.assertEqual(result["Location"], f"/server/{self.uuid}/upgrade/") + self.assertEqual(result["Location"], f"/server/{self.uuid}/plans/") # Verify the authed data that should have been stored in the session. identity_dict = LegacyServerIdentityDict( diff --git a/corporate/urls.py b/corporate/urls.py index 9edf182634..80bf939ddf 100644 --- a/corporate/urls.py +++ b/corporate/urls.py @@ -27,13 +27,13 @@ from corporate.views.portico import ( hello_view, landing_view, plans_view, + remote_realm_plans_page, + remote_server_plans_page, team_view, ) from corporate.views.remote_billing_page import ( remote_billing_legacy_server_login, remote_realm_billing_finalize_login, - remote_realm_plans_page, - remote_server_plans_page, ) from corporate.views.session import ( start_card_update_stripe_session, diff --git a/corporate/views/portico.py b/corporate/views/portico.py index b1c03e9b57..0cbf13e5b9 100644 --- a/corporate/views/portico.py +++ b/corporate/views/portico.py @@ -1,3 +1,4 @@ +from dataclasses import asdict, dataclass from typing import Optional from urllib.parse import urlencode @@ -6,9 +7,14 @@ from django.conf import settings from django.contrib.auth.views import redirect_to_login from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.template.response import TemplateResponse +from django.urls import reverse -from corporate.lib.stripe import is_realm_on_free_trial -from corporate.models import get_customer_by_realm +from corporate.lib.decorator import ( + authenticated_remote_realm_management_endpoint, + authenticated_remote_server_management_endpoint, +) +from corporate.lib.stripe import RemoteRealmBillingSession, RemoteServerBillingSession +from corporate.models import CustomerPlan, get_current_plan_by_customer, get_customer_by_realm from zerver.context_processors import get_realm_from_request, latest_info_context from zerver.decorator import add_google_analytics from zerver.lib.github import ( @@ -47,16 +53,43 @@ def app_download_link_redirect(request: HttpRequest, platform: str) -> HttpRespo return TemplateResponse(request, "404.html", status=404) +def is_customer_on_free_trial(customer_plan: CustomerPlan) -> bool: + return customer_plan.status in ( + CustomerPlan.FREE_TRIAL, + CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL, + ) + + +@dataclass +class PlansPageContext: + sponsorship_url: str + free_trial_days: Optional[int] + on_free_trial: bool = False + sponsorship_pending: bool = False + is_sponsored: bool = False + + is_cloud_realm: bool = False + is_self_hosted_realm: bool = False + + is_new_customer: bool = False + on_free_tier: bool = False + customer_plan: Optional[CustomerPlan] = None + + billing_base_url: str = "" + + @add_google_analytics def plans_view(request: HttpRequest) -> HttpResponse: realm = get_realm_from_request(request) - free_trial_days = settings.FREE_TRIAL_DAYS - sponsorship_pending = False - sponsorship_url = "/sponsorship/" + context = PlansPageContext( + is_cloud_realm=True, + sponsorship_url=reverse("sponsorship_request"), + free_trial_days=settings.FREE_TRIAL_DAYS, + is_sponsored=realm is not None and realm.plan_type == Realm.PLAN_TYPE_STANDARD_FREE, + ) if is_subdomain_root_or_alias(request): # If we're on the root domain, we make this link first ask you which organization. - sponsorship_url = f"/accounts/go/?{urlencode({'next': sponsorship_url})}" - realm_on_free_trial = False + context.sponsorship_url = f"/accounts/go/?{urlencode({'next': context.sponsorship_url})}" if realm is not None: if realm.plan_type == Realm.PLAN_TYPE_SELF_HOSTED and settings.PRODUCTION: @@ -65,21 +98,99 @@ def plans_view(request: HttpRequest) -> HttpResponse: return redirect_to_login(next="/plans/") if request.user.is_guest: return TemplateResponse(request, "404.html", status=404) - customer = get_customer_by_realm(realm) - if customer is not None: - sponsorship_pending = customer.sponsorship_pending - realm_on_free_trial = is_realm_on_free_trial(realm) + customer = get_customer_by_realm(realm) + context.on_free_tier = customer is None and not context.is_sponsored + if customer is not None: + context.sponsorship_pending = customer.sponsorship_pending + context.customer_plan = get_current_plan_by_customer(customer) + if context.customer_plan is None: + # Cloud realms on free tier don't have active customer plan unless they are sponsored. + context.on_free_tier = not context.is_sponsored + else: + context.on_free_trial = is_customer_on_free_trial(context.customer_plan) + + context.is_new_customer = ( + not context.on_free_tier and context.customer_plan is None and not context.is_sponsored + ) return TemplateResponse( request, "corporate/plans.html", - context={ - "realm": realm, - "free_trial_days": free_trial_days, - "realm_on_free_trial": realm_on_free_trial, - "sponsorship_pending": sponsorship_pending, - "sponsorship_url": sponsorship_url, - }, + context=asdict(context), + ) + + +@add_google_analytics +@authenticated_remote_realm_management_endpoint +def remote_realm_plans_page( + request: HttpRequest, billing_session: RemoteRealmBillingSession +) -> HttpResponse: # nocoverage + customer = billing_session.get_customer() + context = PlansPageContext( + is_self_hosted_realm=True, + sponsorship_url=reverse( + "remote_realm_sponsorship_page", args=(billing_session.remote_realm.uuid,) + ), + free_trial_days=settings.FREE_TRIAL_DAYS, + billing_base_url=billing_session.billing_base_url, + is_sponsored=billing_session.is_sponsored(), + ) + + context.on_free_tier = customer is None and not context.is_sponsored + if customer is not None: + context.sponsorship_pending = customer.sponsorship_pending + context.customer_plan = get_current_plan_by_customer(customer) + if context.customer_plan is None: + context.on_free_tier = not context.is_sponsored + else: + context.on_free_trial = is_customer_on_free_trial(context.customer_plan) + + context.is_new_customer = ( + not context.on_free_tier and context.customer_plan is None and not context.is_sponsored + ) + return TemplateResponse( + request, + "corporate/plans.html", + context=asdict(context), + ) + + +@add_google_analytics +@authenticated_remote_server_management_endpoint +def remote_server_plans_page( + request: HttpRequest, billing_session: RemoteServerBillingSession +) -> HttpResponse: # nocoverage + customer = billing_session.get_customer() + context = PlansPageContext( + is_self_hosted_realm=True, + sponsorship_url=reverse( + "remote_server_sponsorship_page", args=(billing_session.remote_server.uuid,) + ), + free_trial_days=settings.FREE_TRIAL_DAYS, + billing_base_url=billing_session.billing_base_url, + is_sponsored=billing_session.is_sponsored(), + ) + + context.on_free_tier = customer is None and not context.is_sponsored + if customer is not None: + context.sponsorship_pending = customer.sponsorship_pending + context.customer_plan = get_current_plan_by_customer(customer) + if context.customer_plan is None: + context.on_free_tier = not context.is_sponsored + else: + context.on_free_tier = context.customer_plan.tier in ( + CustomerPlan.TIER_SELF_HOSTED_LEGACY, + CustomerPlan.TIER_SELF_HOSTED_BASE, + ) + context.on_free_trial = is_customer_on_free_trial(context.customer_plan) + + context.is_new_customer = ( + not context.on_free_tier and context.customer_plan is None and not context.is_sponsored + ) + return TemplateResponse( + request, + "corporate/plans.html", + context=asdict(context), ) diff --git a/corporate/views/remote_billing_page.py b/corporate/views/remote_billing_page.py index c8e550a0f2..1063488e40 100644 --- a/corporate/views/remote_billing_page.py +++ b/corporate/views/remote_billing_page.py @@ -13,11 +13,7 @@ from django.utils.translation import gettext as _ from django.views.decorators.csrf import csrf_exempt from pydantic import Json -from corporate.lib.decorator import ( - authenticated_remote_realm_management_endpoint, - authenticated_remote_server_management_endpoint, - self_hosting_management_endpoint, -) +from corporate.lib.decorator import self_hosting_management_endpoint from corporate.lib.remote_billing_util import ( REMOTE_BILLING_SESSION_VALIDITY_SECONDS, LegacyServerIdentityDict, @@ -25,7 +21,6 @@ from corporate.lib.remote_billing_util import ( RemoteBillingUserDict, get_identity_dict_from_session, ) -from corporate.lib.stripe import RemoteRealmBillingSession, RemoteServerBillingSession from zerver.lib.exceptions import JsonableError, MissingRemoteRealmError from zerver.lib.remote_server import RealmDataForAnalytics, UserDataForRemoteBilling from zerver.lib.response import json_success @@ -293,22 +288,6 @@ def remote_billing_plans_common( return render_tmp_remote_billing_page(request, realm_uuid=realm_uuid, server_uuid=server_uuid) -@authenticated_remote_realm_management_endpoint -def remote_realm_plans_page( - request: HttpRequest, billing_session: RemoteRealmBillingSession -) -> HttpResponse: - realm_uuid = str(billing_session.remote_realm.uuid) - return remote_billing_plans_common(request, realm_uuid=realm_uuid, server_uuid=None) - - -@authenticated_remote_server_management_endpoint -def remote_server_plans_page( - request: HttpRequest, billing_session: RemoteServerBillingSession -) -> HttpResponse: - server_uuid = str(billing_session.remote_server.uuid) - return remote_billing_plans_common(request, server_uuid=server_uuid, realm_uuid=None) - - def remote_billing_page_common( request: HttpRequest, realm_uuid: Optional[str], server_uuid: Optional[str] ) -> HttpResponse: @@ -369,10 +348,7 @@ def remote_billing_legacy_server_login( reverse(f"remote_server_{next_page}_page", args=(remote_server_uuid,)) ) elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_HOSTED: - # TODO: Take user to plans page once that is available. - return HttpResponseRedirect( - reverse("remote_server_upgrade_page", args=(remote_server_uuid,)) - ) + return HttpResponseRedirect(reverse("remote_server_plans_page", args=(remote_server_uuid,))) elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_COMMUNITY: return HttpResponseRedirect( reverse("remote_server_sponsorship_page", args=(remote_server_uuid,)) diff --git a/templates/corporate/plans.html b/templates/corporate/plans.html index 64b1838da8..0cba67e7c2 100644 --- a/templates/corporate/plans.html +++ b/templates/corporate/plans.html @@ -13,7 +13,7 @@ {% include 'zerver/landing_nav.html' %} -