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", "visa": "Visa",
} }
PAID_PLANS = [
Realm.PLAN_TYPE_STANDARD,
Realm.PLAN_TYPE_PLUS,
]
# The version of Stripe API the billing system supports. # The version of Stripe API the billing system supports.
STRIPE_API_VERSION = "2020-08-27" 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 # Be extremely careful changing this function. Historical billing periods
# are not stored anywhere, and are just computed on the fly using this # 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 # 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: def has_billing_access(self) -> bool:
pass 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 @catch_stripe_errors
def create_stripe_customer(self) -> Customer: def create_stripe_customer(self) -> Customer:
stripe_customer_data = self.get_data_for_stripe_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")) 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): class RealmBillingSession(BillingSession):
def __init__( def __init__(
@@ -1814,6 +1859,27 @@ class RealmBillingSession(BillingSession):
assert self.user is not None assert self.user is not None
return self.user.has_billing_access 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 class RemoteRealmBillingSession(BillingSession): # nocoverage
def __init__( def __init__(
@@ -2015,6 +2081,16 @@ class RemoteRealmBillingSession(BillingSession): # nocoverage
# session that isn't authorized for billing access. # session that isn't authorized for billing access.
return True 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 class RemoteServerBillingSession(BillingSession): # nocoverage
"""Billing session for pre-8.0 servers that do not yet support """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. # session that isn't authorized for billing access.
return True 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( def stripe_customer_has_credit_card_as_default_payment_method(
stripe_customer: stripe.Customer, stripe_customer: stripe.Customer,

View File

@@ -5,72 +5,28 @@ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from corporate.lib.stripe import RealmBillingSession, UpdatePlanRequest from corporate.lib.stripe import RealmBillingSession, UpdatePlanRequest, is_realm_on_paid_plan
from corporate.models import CustomerPlan, get_current_plan_by_customer, get_customer_by_realm from corporate.models import CustomerPlan, get_customer_by_realm
from zerver.decorator import require_billing_access, zulip_login_required from zerver.decorator import require_billing_access, zulip_login_required
from zerver.lib.request import REQ, has_request_variables from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success from zerver.lib.response import json_success
from zerver.lib.validator import check_int, check_int_in, check_string 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") 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 @zulip_login_required
@has_request_variables @has_request_variables
def sponsorship_request(request: HttpRequest) -> HttpResponse: def sponsorship_request(request: HttpRequest) -> HttpResponse:
user = request.user user = request.user
assert user.is_authenticated assert user.is_authenticated
context: Dict[str, Any] = {}
customer = get_customer_by_realm(user.realm) billing_session = RealmBillingSession(user)
if customer is not None and customer.sponsorship_pending: context = billing_session.get_sponsorship_request_context()
if is_realm_on_paid_plan(user.realm): if context is None:
return HttpResponseRedirect(reverse("billing_home")) 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) return render(request, "corporate/sponsorship.html", context=context)