stripe: Add get_sponsorship_request_context method to BillingSession.

This commit refactors 'sponsorship_request' view and adds
'BillingSession.get_sponsorship_request' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_Server customers.
This commit is contained in:
Prakhar Pratyush
2023-11-27 17:38:43 +05:30
committed by Tim Abbott
parent 1b17626327
commit f8b0e16ff2
2 changed files with 93 additions and 51 deletions

View File

@@ -79,6 +79,11 @@ CARD_CAPITALIZATION = {
"visa": "Visa",
}
PAID_PLANS = [
Realm.PLAN_TYPE_STANDARD,
Realm.PLAN_TYPE_PLUS,
]
# The version of Stripe API the billing system supports.
STRIPE_API_VERSION = "2020-08-27"
@@ -207,6 +212,10 @@ def check_upgrade_parameters(
)
def is_realm_on_paid_plan(realm: Realm) -> bool:
return realm.plan_type in PAID_PLANS
# Be extremely careful changing this function. Historical billing periods
# are not stored anywhere, and are just computed on the fly using this
# function. Any change you make here should return the same value (or be
@@ -634,6 +643,14 @@ class BillingSession(ABC):
def has_billing_access(self) -> bool:
pass
@abstractmethod
def on_paid_plan(self) -> bool:
pass
@abstractmethod
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
pass
@catch_stripe_errors
def create_stripe_customer(self) -> Customer:
stripe_customer_data = self.get_data_for_stripe_customer()
@@ -1567,6 +1584,34 @@ class BillingSession(ABC):
raise JsonableError(_("Pass stripe_session_id or stripe_payment_intent_id"))
def get_sponsorship_request_context(self) -> Optional[Dict[str, Any]]:
context: Dict[str, Any] = {}
customer = self.get_customer()
if customer is not None and customer.sponsorship_pending:
if self.on_paid_plan():
return None
context["is_sponsorship_pending"] = True
if self.is_sponsored():
context["is_sponsored"] = True
if customer is not None:
plan = get_current_plan_by_customer(customer)
if plan is not None:
context["plan_name"] = plan.name
context["free_trial"] = plan.is_free_trial()
elif self.is_sponsored():
# We don't create CustomerPlan objects for fully sponsored realms via support page.
context["plan_name"] = "Zulip Cloud Standard"
else:
# TODO: Don't hardcode this plan name.
context["plan_name"] = "Zulip Cloud Free"
self.add_sponsorship_info_to_context(context)
return context
class RealmBillingSession(BillingSession):
def __init__(
@@ -1814,6 +1859,27 @@ class RealmBillingSession(BillingSession):
assert self.user is not None
return self.user.has_billing_access
@override
def on_paid_plan(self) -> bool:
return is_realm_on_paid_plan(self.realm)
@override
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
def key_helper(d: Any) -> int:
return d[1]["display_order"]
context.update(
realm_org_type=self.realm.org_type,
sorted_org_types=sorted(
(
[org_type_name, org_type]
for (org_type_name, org_type) in Realm.ORG_TYPES.items()
if not org_type.get("hidden")
),
key=key_helper,
),
)
class RemoteRealmBillingSession(BillingSession): # nocoverage
def __init__(
@@ -2015,6 +2081,16 @@ class RemoteRealmBillingSession(BillingSession): # nocoverage
# session that isn't authorized for billing access.
return True
@override
def on_paid_plan(self) -> bool:
# TBD
return False
@override
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
# TBD
pass
class RemoteServerBillingSession(BillingSession): # nocoverage
"""Billing session for pre-8.0 servers that do not yet support
@@ -2211,6 +2287,16 @@ class RemoteServerBillingSession(BillingSession): # nocoverage
# session that isn't authorized for billing access.
return True
@override
def on_paid_plan(self) -> bool:
# TBD
return False
@override
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
# TBD
pass
def stripe_customer_has_credit_card_as_default_payment_method(
stripe_customer: stripe.Customer,

View File

@@ -5,72 +5,28 @@ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from corporate.lib.stripe import RealmBillingSession, UpdatePlanRequest
from corporate.models import CustomerPlan, get_current_plan_by_customer, get_customer_by_realm
from corporate.lib.stripe import RealmBillingSession, UpdatePlanRequest, is_realm_on_paid_plan
from corporate.models import CustomerPlan, get_customer_by_realm
from zerver.decorator import require_billing_access, zulip_login_required
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success
from zerver.lib.validator import check_int, check_int_in, check_string
from zerver.models import Realm, UserProfile
from zerver.models import UserProfile
billing_logger = logging.getLogger("corporate.stripe")
PAID_PLANS = [
Realm.PLAN_TYPE_STANDARD,
Realm.PLAN_TYPE_PLUS,
]
def is_realm_on_paid_plan(realm: Realm) -> bool:
return realm.plan_type in PAID_PLANS
def add_sponsorship_info_to_context(context: Dict[str, Any], user_profile: UserProfile) -> None:
def key_helper(d: Any) -> int:
return d[1]["display_order"]
context.update(
realm_org_type=user_profile.realm.org_type,
sorted_org_types=sorted(
(
[org_type_name, org_type]
for (org_type_name, org_type) in Realm.ORG_TYPES.items()
if not org_type.get("hidden")
),
key=key_helper,
),
)
@zulip_login_required
@has_request_variables
def sponsorship_request(request: HttpRequest) -> HttpResponse:
user = request.user
assert user.is_authenticated
context: Dict[str, Any] = {}
customer = get_customer_by_realm(user.realm)
if customer is not None and customer.sponsorship_pending:
if is_realm_on_paid_plan(user.realm):
return HttpResponseRedirect(reverse("billing_home"))
billing_session = RealmBillingSession(user)
context = billing_session.get_sponsorship_request_context()
if context is None:
return HttpResponseRedirect(reverse("billing_home"))
context["is_sponsorship_pending"] = True
if user.realm.plan_type == user.realm.PLAN_TYPE_STANDARD_FREE:
context["is_sponsored"] = True
if customer is not None:
plan = get_current_plan_by_customer(customer)
if plan is not None:
context["plan_name"] = plan.name
context["free_trial"] = plan.is_free_trial()
# We don't create CustomerPlan objects for fully sponsored realms via support page.
elif user.realm.plan_type == Realm.PLAN_TYPE_STANDARD_FREE:
context["plan_name"] = "Zulip Cloud Standard"
else:
context["plan_name"] = "Zulip Cloud Free"
add_sponsorship_info_to_context(context, user)
return render(request, "corporate/sponsorship.html", context=context)