mirror of
https://github.com/zulip/zulip.git
synced 2025-11-12 18:06:44 +00:00
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.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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/<realm_uuid>/sponsorship", remote_realm_sponsorship),
|
||||
path("json/server/<server_uuid>/sponsorship", remote_server_sponsorship),
|
||||
path(
|
||||
"json/realm/<realm_uuid>/billing/session/start_card_update_session",
|
||||
start_card_update_stripe_session_for_remote_realm,
|
||||
),
|
||||
path(
|
||||
"json/server/<server_uuid>/billing/session/start_card_update_session",
|
||||
start_card_update_stripe_session_for_remote_server,
|
||||
),
|
||||
path(
|
||||
"json/realm/<realm_uuid>/upgrade/session/start_card_update_session",
|
||||
start_card_update_stripe_session_for_remote_realm_upgrade,
|
||||
@@ -243,6 +255,8 @@ urlpatterns += [
|
||||
path("json/server/<server_uuid>/billing/event/status", remote_server_event_status),
|
||||
path("json/realm/<realm_uuid>/billing/upgrade", remote_realm_upgrade),
|
||||
path("json/server/<server_uuid>/billing/upgrade", remote_server_upgrade),
|
||||
path("json/realm/<realm_uuid>/billing/plan", update_plan_for_remote_realm),
|
||||
path("json/server/<server_uuid>/billing/plan", update_plan_for_remote_server),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block portico_content %}
|
||||
<div id="billing-page" class="register-account flex full-page">
|
||||
<div id="billing-page" class="register-account flex full-page" data-billing-base-url="{{ billing_base_url }}">
|
||||
<div class="center-block new-style">
|
||||
{% if admin_access and has_active_plan %}
|
||||
{% if is_sponsorship_pending %}
|
||||
<div class="alert alert-success billing-page-success" id="billing-sponsorship-pending-message-top">
|
||||
This organization has requested sponsorship for a <a href="/plans/">{{ plan_name }}</a> plan. <a href="mailto:support@zulip.com">Contact Zulip support</a> with any questions or updates.
|
||||
This organization has requested sponsorship for a <a href="{{ billing_base_url }}/plans/">{{ plan_name }}</a> plan. <a href="mailto:support@zulip.com">Contact Zulip support</a> with any questions or updates.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if success_message %}
|
||||
@@ -157,11 +157,11 @@
|
||||
{% if downgrade_at_end_of_cycle %}
|
||||
Your organization will be downgraded to <strong>Zulip Cloud Free</strong> at the end of the current billing
|
||||
period (<strong>{{ renewal_date }}</strong>). You will lose access to unlimited search history and
|
||||
<a href="/plans/">other features</a> of your current plan.
|
||||
<a href="{{ billing_base_url }}/plans/">other features</a> of your current plan.
|
||||
{% elif downgrade_at_end_of_free_trial %}
|
||||
Your organization will be downgraded to <strong>Zulip Cloud Free</strong> at the end of the free trial
|
||||
(<strong>{{ renewal_date }}</strong>). You will lose access to unlimited search history and
|
||||
<a href="/plans/">other features</a> of your current plan.
|
||||
<a href="{{ billing_base_url }}/plans/">other features</a> of your current plan.
|
||||
{% else %}
|
||||
{% if charge_automatically %}
|
||||
Your plan will automatically renew on <strong>{{ renewal_date }}</strong>.
|
||||
@@ -276,7 +276,7 @@
|
||||
<p>
|
||||
Your organization will be downgraded to <strong>Zulip Cloud Free</strong> at the end of your free trial
|
||||
({{ renewal_date }}). You will lose access to unlimited search history and
|
||||
<a href="/plans/">other features</a>
|
||||
<a href="{{ billing_base_url }}/plans/">other features</a>
|
||||
of your current plan. Are you sure you want to continue?
|
||||
</p>
|
||||
</main>
|
||||
@@ -360,7 +360,7 @@
|
||||
<p>
|
||||
Your organization will be downgraded to <strong>Zulip Cloud Free</strong> at the end of the current billing
|
||||
period ({{ renewal_date }}). You will lose access to unlimited search history and
|
||||
<a href="/plans/">other features</a>
|
||||
<a href="{{ billing_base_url }}/plans/">other features</a>
|
||||
of your current plan. Are you sure you want to continue?
|
||||
</p>
|
||||
</main>
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as portico_modals from "../portico/portico_modals";
|
||||
import * as helpers from "./helpers";
|
||||
|
||||
const billing_frequency_schema = z.enum(["Monthly", "Annual"]);
|
||||
const billing_base_url = $("#billing-page").attr("data-billing-base-url")!;
|
||||
|
||||
// Matches the CustomerPlan model in the backend.
|
||||
enum BillingFrequency {
|
||||
@@ -25,13 +26,13 @@ export function create_update_current_cycle_license_request(): void {
|
||||
$("#current-manual-license-count-update-button .billing-button-text").text("");
|
||||
$("#current-manual-license-count-update-button .loader").show();
|
||||
helpers.create_ajax_request(
|
||||
"/json/billing/plan",
|
||||
`/json${billing_base_url}/billing/plan`,
|
||||
"current-license-change",
|
||||
[],
|
||||
"PATCH",
|
||||
"post",
|
||||
() => {
|
||||
window.location.replace(
|
||||
"/billing/?success_message=" +
|
||||
`${billing_base_url}/billing/?success_message=` +
|
||||
encodeURIComponent(
|
||||
"Updated number of licenses for the current billing period.",
|
||||
),
|
||||
@@ -50,13 +51,13 @@ export function create_update_next_cycle_license_request(): void {
|
||||
$("#next-manual-license-count-update-button .loader").show();
|
||||
$("#next-manual-license-count-update-button .billing-button-text").text("");
|
||||
helpers.create_ajax_request(
|
||||
"/json/billing/plan",
|
||||
`/json${billing_base_url}/billing/plan`,
|
||||
"next-license-change",
|
||||
[],
|
||||
"PATCH",
|
||||
"post",
|
||||
() => {
|
||||
window.location.replace(
|
||||
"/billing/?success_message=" +
|
||||
`${billing_base_url}/billing/?success_message=` +
|
||||
encodeURIComponent("Updated number of licenses for the next billing period."),
|
||||
);
|
||||
$("#next-manual-license-count-update-button .loader").hide();
|
||||
@@ -74,7 +75,7 @@ export function initialize(): void {
|
||||
$("#update-card-button .billing-button-text").text("");
|
||||
$("#update-card-button .loader").show();
|
||||
helpers.create_ajax_request(
|
||||
"/json/billing/session/start_card_update_session",
|
||||
`/json${billing_base_url}/billing/session/start_card_update_session`,
|
||||
"cardchange",
|
||||
[],
|
||||
"POST",
|
||||
@@ -188,9 +189,14 @@ export function initialize(): void {
|
||||
);
|
||||
|
||||
$("#confirm-cancel-subscription-modal .dialog_submit_button").on("click", (e) => {
|
||||
helpers.create_ajax_request("/json/billing/plan", "planchange", [], "PATCH", () =>
|
||||
helpers.create_ajax_request(
|
||||
`/json${billing_base_url}/billing/plan`,
|
||||
"planchange",
|
||||
[],
|
||||
"post",
|
||||
() =>
|
||||
window.location.replace(
|
||||
"/billing/?success_message=" +
|
||||
`${billing_base_url}/billing/?success_message=` +
|
||||
encodeURIComponent("Your plan has been canceled and will not renew."),
|
||||
),
|
||||
);
|
||||
@@ -198,9 +204,14 @@ export function initialize(): void {
|
||||
});
|
||||
|
||||
$("#reactivate-subscription .reactivate-current-plan-button").on("click", (e) => {
|
||||
helpers.create_ajax_request("/json/billing/plan", "planchange", [], "PATCH", () =>
|
||||
helpers.create_ajax_request(
|
||||
`/json${billing_base_url}/billing/plan`,
|
||||
"planchange",
|
||||
[],
|
||||
"post",
|
||||
() =>
|
||||
window.location.replace(
|
||||
"/billing/?success_message=" +
|
||||
`${billing_base_url}/billing/?success_message=` +
|
||||
encodeURIComponent(
|
||||
"Your plan has been reactivated and will renew automatically.",
|
||||
),
|
||||
@@ -210,9 +221,14 @@ export function initialize(): void {
|
||||
});
|
||||
|
||||
$("#confirm-end-free-trial .dialog_submit_button").on("click", (e) => {
|
||||
helpers.create_ajax_request("/json/billing/plan", "planchange", [], "PATCH", () =>
|
||||
helpers.create_ajax_request(
|
||||
`/json${billing_base_url}/billing/plan`,
|
||||
"planchange",
|
||||
[],
|
||||
"post",
|
||||
() =>
|
||||
window.location.replace(
|
||||
"/billing/?success_message=" +
|
||||
`${billing_base_url}/billing/?success_message=` +
|
||||
encodeURIComponent(
|
||||
"Your plan will be canceled at the end of the trial. Your card will not be charged.",
|
||||
),
|
||||
@@ -326,12 +342,12 @@ export function initialize(): void {
|
||||
};
|
||||
e.preventDefault();
|
||||
void $.ajax({
|
||||
type: "patch",
|
||||
url: "/json/billing/plan",
|
||||
type: "post",
|
||||
url: `/json${billing_base_url}/billing/plan`,
|
||||
data,
|
||||
success() {
|
||||
window.location.replace(
|
||||
"/billing/?success_message=" +
|
||||
`${billing_base_url}/billing/?success_message=` +
|
||||
encodeURIComponent("Billing frequency has been updated."),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ function handle_session_complete_event(session: StripeSession): void {
|
||||
let redirect_to = "";
|
||||
switch (session.type) {
|
||||
case "card_update_from_billing_page":
|
||||
redirect_to = "/billing/";
|
||||
redirect_to = billing_base_url + "/billing/";
|
||||
break;
|
||||
case "card_update_from_upgrade_page":
|
||||
redirect_to = helpers.get_upgrade_page_url(
|
||||
|
||||
Reference in New Issue
Block a user