From a59245e932c61fb094203635409f1951a305ba99 Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Sat, 2 Dec 2023 08:09:43 +0000 Subject: [PATCH] billing: Make various buttons on billing page work. We pass billing_base_url to the template and use it to construct session specific URLs. Also, add corresponding function on server to support them. --- corporate/lib/stripe.py | 17 ++++--- corporate/urls.py | 14 ++++++ corporate/views/billing_page.py | 77 +++++++++++++++++++++++++++----- corporate/views/session.py | 49 +++++++++++--------- templates/corporate/billing.html | 12 ++--- web/src/billing/billing.ts | 74 ++++++++++++++++++------------ web/src/billing/event_status.ts | 2 +- 7 files changed, 167 insertions(+), 78 deletions(-) diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 18c1ba32b6..ca5337bc57 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -863,7 +863,7 @@ class BillingSession(ABC): ) return stripe_payment_intent.id - def get_card_update_session_data_for_upgrade( + def create_card_update_session_for_upgrade( self, manual_license_management: bool, ) -> Dict[str, Any]: @@ -892,12 +892,8 @@ class BillingSession(ABC): "stripe_session_id": stripe_session.id, } - def create_stripe_checkout_session( - self, - metadata: Dict[str, Any], - session_type: int, - ) -> stripe.checkout.Session: - # Create checkout sessions for adding and updating exiting cards. + def create_card_update_session(self) -> Dict[str, Any]: + metadata = self.get_metadata_for_stripe_update_card() customer = self.get_customer() assert customer is not None and customer.stripe_customer_id is not None stripe_session = stripe.checkout.Session.create( @@ -911,9 +907,12 @@ class BillingSession(ABC): Session.objects.create( stripe_session_id=stripe_session.id, customer=customer, - type=session_type, + type=Session.CARD_UPDATE_FROM_BILLING_PAGE, ) - return stripe_session + return { + "stripe_session_url": stripe_session.url, + "stripe_session_id": stripe_session.id, + } def attach_discount_to_customer(self, new_discount: Decimal) -> str: customer = self.get_customer() diff --git a/corporate/urls.py b/corporate/urls.py index ec28f78b15..e5757a533d 100644 --- a/corporate/urls.py +++ b/corporate/urls.py @@ -9,6 +9,8 @@ from corporate.views.billing_page import ( remote_realm_billing_page, remote_server_billing_page, update_plan, + update_plan_for_remote_realm, + update_plan_for_remote_server, ) from corporate.views.event_status import ( event_status, @@ -36,7 +38,9 @@ from corporate.views.remote_billing_page import ( from corporate.views.session import ( start_card_update_stripe_session, start_card_update_stripe_session_for_realm_upgrade, + start_card_update_stripe_session_for_remote_realm, start_card_update_stripe_session_for_remote_realm_upgrade, + start_card_update_stripe_session_for_remote_server, start_card_update_stripe_session_for_remote_server_upgrade, ) from corporate.views.sponsorship import ( @@ -231,6 +235,14 @@ urlpatterns += [ # Remote variants of above API endpoints. path("json/realm//sponsorship", remote_realm_sponsorship), path("json/server//sponsorship", remote_server_sponsorship), + path( + "json/realm//billing/session/start_card_update_session", + start_card_update_stripe_session_for_remote_realm, + ), + path( + "json/server//billing/session/start_card_update_session", + start_card_update_stripe_session_for_remote_server, + ), path( "json/realm//upgrade/session/start_card_update_session", start_card_update_stripe_session_for_remote_realm_upgrade, @@ -243,6 +255,8 @@ urlpatterns += [ path("json/server//billing/event/status", remote_server_event_status), path("json/realm//billing/upgrade", remote_realm_upgrade), path("json/server//billing/upgrade", remote_server_upgrade), + path("json/realm//billing/plan", update_plan_for_remote_realm), + path("json/server//billing/plan", update_plan_for_remote_server), ] urlpatterns += [ diff --git a/corporate/views/billing_page.py b/corporate/views/billing_page.py index a14261a6ec..9ecc0fef3a 100644 --- a/corporate/views/billing_page.py +++ b/corporate/views/billing_page.py @@ -26,6 +26,16 @@ from zilencer.models import RemoteRealm, RemoteZulipServer billing_logger = logging.getLogger("corporate.stripe") +ALLOWED_PLANS_API_STATUS_VALUES = [ + CustomerPlan.ACTIVE, + CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE, + CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE, + CustomerPlan.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE, + CustomerPlan.FREE_TRIAL, + CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL, + CustomerPlan.ENDED, +] + @zulip_login_required @typed_endpoint @@ -46,6 +56,7 @@ def billing_page( "admin_access": user.has_billing_access, "has_active_plan": False, "org_name": user.realm.name, + "billing_base_url": "", } if not user.has_billing_access: @@ -92,6 +103,7 @@ def remote_realm_billing_page( "admin_access": billing_session.has_billing_access(), "has_active_plan": False, "org_name": billing_session.remote_realm.name, + "billing_base_url": f"/realm/{billing_session.remote_realm.uuid}", } if billing_session.remote_realm.plan_type == RemoteRealm.PLAN_TYPE_COMMUNITY: @@ -135,6 +147,7 @@ def remote_server_billing_page( "admin_access": billing_session.has_billing_access(), "has_active_plan": False, "org_name": billing_session.remote_server.hostname, + "billing_base_url": f"/server/{billing_session.remote_server.uuid}", } if billing_session.remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_COMMUNITY: @@ -185,17 +198,7 @@ def update_plan( 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.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE, - CustomerPlan.FREE_TRIAL, - CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL, - CustomerPlan.ENDED, - ] - ), + json_validator=check_int_in(ALLOWED_PLANS_API_STATUS_VALUES), default=None, ), licenses: Optional[int] = REQ("licenses", json_validator=check_int, default=None), @@ -213,3 +216,55 @@ def update_plan( billing_session = RealmBillingSession(user=user) billing_session.do_update_plan(update_plan_request) return json_success(request) + + +@authenticated_remote_realm_management_endpoint +@has_request_variables +def update_plan_for_remote_realm( + request: HttpRequest, + billing_session: RemoteRealmBillingSession, + status: Optional[int] = REQ( + "status", + json_validator=check_int_in(ALLOWED_PLANS_API_STATUS_VALUES), + 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 + ), + schedule: Optional[int] = REQ("schedule", json_validator=check_int, default=None), +) -> HttpResponse: # nocoverage + update_plan_request = UpdatePlanRequest( + status=status, + licenses=licenses, + licenses_at_next_renewal=licenses_at_next_renewal, + schedule=schedule, + ) + billing_session.do_update_plan(update_plan_request) + return json_success(request) + + +@authenticated_remote_server_management_endpoint +@has_request_variables +def update_plan_for_remote_server( + request: HttpRequest, + billing_session: RemoteServerBillingSession, + status: Optional[int] = REQ( + "status", + json_validator=check_int_in(ALLOWED_PLANS_API_STATUS_VALUES), + 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 + ), + schedule: Optional[int] = REQ("schedule", json_validator=check_int, default=None), +) -> HttpResponse: # nocoverage + update_plan_request = UpdatePlanRequest( + status=status, + licenses=licenses, + licenses_at_next_renewal=licenses_at_next_renewal, + schedule=schedule, + ) + billing_session.do_update_plan(update_plan_request) + return json_success(request) diff --git a/corporate/views/session.py b/corporate/views/session.py index 3c682a2010..f5ec1dcaa9 100644 --- a/corporate/views/session.py +++ b/corporate/views/session.py @@ -12,7 +12,6 @@ from corporate.lib.stripe import ( RemoteRealmBillingSession, RemoteServerBillingSession, ) -from corporate.models import Session from zerver.decorator import require_billing_access, require_organization_member from zerver.lib.response import json_success from zerver.lib.typed_endpoint import typed_endpoint @@ -24,20 +23,32 @@ billing_logger = logging.getLogger("corporate.stripe") @require_billing_access def start_card_update_stripe_session(request: HttpRequest, user: UserProfile) -> HttpResponse: billing_session = RealmBillingSession(user) - assert billing_session.get_customer() is not None - metadata = { - "type": "card_update", - "user_id": user.id, - } - stripe_session = billing_session.create_stripe_checkout_session( - metadata, Session.CARD_UPDATE_FROM_BILLING_PAGE - ) + session_data = billing_session.create_card_update_session() return json_success( request, - data={ - "stripe_session_url": stripe_session.url, - "stripe_session_id": stripe_session.id, - }, + data=session_data, + ) + + +@authenticated_remote_realm_management_endpoint +def start_card_update_stripe_session_for_remote_realm( + request: HttpRequest, billing_session: RemoteRealmBillingSession +) -> HttpResponse: # nocoverage + session_data = billing_session.create_card_update_session() + return json_success( + request, + data=session_data, + ) + + +@authenticated_remote_server_management_endpoint +def start_card_update_stripe_session_for_remote_server( + request: HttpRequest, billing_session: RemoteServerBillingSession +) -> HttpResponse: # nocoverage + session_data = billing_session.create_card_update_session() + return json_success( + request, + data=session_data, ) @@ -50,9 +61,7 @@ def start_card_update_stripe_session_for_realm_upgrade( manual_license_management: Json[bool] = False, ) -> HttpResponse: billing_session = RealmBillingSession(user) - session_data = billing_session.get_card_update_session_data_for_upgrade( - manual_license_management - ) + session_data = billing_session.create_card_update_session_for_upgrade(manual_license_management) return json_success( request, data=session_data, @@ -67,9 +76,7 @@ def start_card_update_stripe_session_for_remote_realm_upgrade( *, manual_license_management: Json[bool] = False, ) -> HttpResponse: # nocoverage - session_data = billing_session.get_card_update_session_data_for_upgrade( - manual_license_management - ) + session_data = billing_session.create_card_update_session_for_upgrade(manual_license_management) return json_success( request, data=session_data, @@ -84,9 +91,7 @@ def start_card_update_stripe_session_for_remote_server_upgrade( *, manual_license_management: Json[bool] = False, ) -> HttpResponse: # nocoverage - session_data = billing_session.get_card_update_session_data_for_upgrade( - manual_license_management - ) + session_data = billing_session.create_card_update_session_for_upgrade(manual_license_management) return json_success( request, data=session_data, diff --git a/templates/corporate/billing.html b/templates/corporate/billing.html index 8a3c9465b3..ff478b66cf 100644 --- a/templates/corporate/billing.html +++ b/templates/corporate/billing.html @@ -6,12 +6,12 @@ {% endblock %} {% block portico_content %} -