diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py
index a5a504d6c3..d8d227a456 100644
--- a/corporate/lib/stripe.py
+++ b/corporate/lib/stripe.py
@@ -30,6 +30,7 @@ from corporate.models import (
LicenseLedger,
PaymentIntent,
Session,
+ SponsoredPlanTypes,
ZulipSponsorshipRequest,
get_current_plan_by_customer,
get_current_plan_by_realm,
@@ -645,6 +646,9 @@ class SponsorshipRequestForm(forms.Form):
expected_total_users = forms.CharField(widget=forms.Textarea)
paid_users_count = forms.CharField(widget=forms.Textarea)
paid_users_description = forms.CharField(widget=forms.Textarea, required=False)
+ requested_plan = forms.ChoiceField(
+ choices=[(plan.value, plan.name) for plan in SponsoredPlanTypes], required=False
+ )
class BillingSession(ABC):
@@ -1654,6 +1658,7 @@ class BillingSession(ABC):
customer, status=CustomerPlan.SWITCH_PLAN_TIER_AT_PLAN_END
)
legacy_remote_server_new_plan_name = self.get_legacy_remote_server_new_plan_name(customer)
+ is_self_hosted_billing = not isinstance(self, RealmBillingSession)
context = {
"plan_name": plan.name,
"has_active_plan": True,
@@ -1676,8 +1681,11 @@ class BillingSession(ABC):
"fixed_price": fixed_price,
"price_per_license": price_per_license,
"is_sponsorship_pending": customer.sponsorship_pending,
+ "sponsorship_plan_name": self.get_sponsorship_plan_name(
+ customer, is_self_hosted_billing
+ ),
"discount_percent": format_discount_percentage(customer.default_discount),
- "is_self_hosted_billing": not isinstance(self, RealmBillingSession),
+ "is_self_hosted_billing": is_self_hosted_billing,
"is_server_on_legacy_plan": remote_server_legacy_plan_end_date is not None,
"remote_server_legacy_plan_end_date": remote_server_legacy_plan_end_date,
"legacy_remote_server_new_plan_name": legacy_remote_server_new_plan_name,
@@ -2218,11 +2226,22 @@ class BillingSession(ABC):
raise JsonableError(_("Pass stripe_session_id or stripe_payment_intent_id"))
- def get_sponsorship_request_context(self) -> Optional[Dict[str, Any]]:
- customer = self.get_customer()
- is_remotely_hosted = isinstance(
- self, (RemoteRealmBillingSession, RemoteServerBillingSession)
- )
+ def get_sponsorship_plan_name(
+ self, customer: Optional[Customer], is_remotely_hosted: bool
+ ) -> str:
+ if customer is not None and customer.sponsorship_pending:
+ # For sponsorship pending requests, we also show the type of sponsorship requested.
+ # In other cases, we just show the plan user is currently on.
+ sponsorship_request = (
+ ZulipSponsorshipRequest.objects.filter(customer=customer).order_by("-id").first()
+ )
+ # It's possible that we marked `customer.sponsorship_pending` via support page
+ # without user submitting a sponsorship request.
+ if sponsorship_request is not None and sponsorship_request.requested_plan not in (
+ None,
+ SponsoredPlanTypes.UNSPECIFIED.value,
+ ): # nocoverage
+ return sponsorship_request.requested_plan
# We only support sponsorships for these plans.
sponsored_plan_name = CustomerPlan.name_from_tier(CustomerPlan.TIER_CLOUD_STANDARD)
@@ -2231,6 +2250,14 @@ class BillingSession(ABC):
CustomerPlan.TIER_SELF_HOSTED_COMMUNITY
)
+ return sponsored_plan_name
+
+ def get_sponsorship_request_context(self) -> Optional[Dict[str, Any]]:
+ customer = self.get_customer()
+ is_remotely_hosted = isinstance(
+ self, (RemoteRealmBillingSession, RemoteServerBillingSession)
+ )
+
plan_name = "Zulip Cloud Free"
if is_remotely_hosted:
plan_name = "Self-managed"
@@ -2238,7 +2265,7 @@ class BillingSession(ABC):
context: Dict[str, Any] = {
"billing_base_url": self.billing_base_url,
"is_remotely_hosted": is_remotely_hosted,
- "sponsored_plan_name": sponsored_plan_name,
+ "sponsorship_plan_name": self.get_sponsorship_plan_name(customer, is_remotely_hosted),
"plan_name": plan_name,
}
@@ -2250,7 +2277,6 @@ class BillingSession(ABC):
if self.is_sponsored():
context["is_sponsored"] = True
- context["plan_name"] = sponsored_plan_name
if customer is not None:
plan = get_current_plan_by_customer(customer)
@@ -2283,6 +2309,7 @@ class BillingSession(ABC):
expected_total_users=form.cleaned_data["expected_total_users"],
paid_users_count=form.cleaned_data["paid_users_count"],
paid_users_description=form.cleaned_data["paid_users_description"],
+ requested_plan=form.cleaned_data["requested_plan"],
)
sponsorship_request.save()
@@ -2310,6 +2337,7 @@ class BillingSession(ABC):
"expected_total_users": sponsorship_request.expected_total_users,
"paid_users_count": sponsorship_request.paid_users_count,
"paid_users_description": sponsorship_request.paid_users_description,
+ "requested_plan": sponsorship_request.requested_plan,
}
send_email(
"zerver/emails/sponsorship_request",
diff --git a/corporate/migrations/0028_zulipsponsorshiprequest_requested_plan.py b/corporate/migrations/0028_zulipsponsorshiprequest_requested_plan.py
new file mode 100644
index 0000000000..a0c6fd68b2
--- /dev/null
+++ b/corporate/migrations/0028_zulipsponsorshiprequest_requested_plan.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.8 on 2023-12-08 18:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("corporate", "0027_alter_zulipsponsorshiprequest_requested_by"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="zulipsponsorshiprequest",
+ name="requested_plan",
+ field=models.CharField(
+ choices=[("", "UNSPECIFIED"), ("Community", "COMMUNITY"), ("Business", "BUSINESS")],
+ default="",
+ max_length=50,
+ ),
+ ),
+ ]
diff --git a/corporate/models.py b/corporate/models.py
index 7a11f0cc56..6d3483ef54 100644
--- a/corporate/models.py
+++ b/corporate/models.py
@@ -1,3 +1,4 @@
+from enum import Enum
from typing import Any, Dict, Optional, Union
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -384,6 +385,13 @@ class LicenseLedger(models.Model):
licenses_at_next_renewal = models.IntegerField(null=True)
+class SponsoredPlanTypes(Enum):
+ # unspecified used for cloud sponsorship requests
+ UNSPECIFIED = ""
+ COMMUNITY = "Community"
+ BUSINESS = "Business"
+
+
class ZulipSponsorshipRequest(models.Model):
customer = models.ForeignKey(Customer, on_delete=CASCADE)
requested_by = models.ForeignKey(UserProfile, on_delete=CASCADE, null=True, blank=True)
@@ -400,3 +408,9 @@ class ZulipSponsorshipRequest(models.Model):
expected_total_users = models.TextField(default="")
paid_users_count = models.TextField(default="")
paid_users_description = models.TextField(default="")
+
+ requested_plan = models.CharField(
+ max_length=50,
+ choices=[(plan.value, plan.name) for plan in SponsoredPlanTypes],
+ default=SponsoredPlanTypes.UNSPECIFIED.value,
+ )
diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py
index 9935410cfb..aa9832a431 100644
--- a/corporate/tests/test_stripe.py
+++ b/corporate/tests/test_stripe.py
@@ -1865,7 +1865,9 @@ class StripeTest(StripeTestCase):
response = self.client_get("/sponsorship/")
self.assert_in_success_response(
[
- 'This organization has requested sponsorship for a Zulip Cloud Standard plan. Contact Zulip support with any questions or updates.'
+ "This organization has requested sponsorship for a",
+ 'Zulip Cloud Standard',
+ 'plan.
Contact Zulip support with any questions or updates.',
],
response,
)
diff --git a/templates/corporate/billing.html b/templates/corporate/billing.html
index ea2447f6e6..e78dbe7c9a 100644
--- a/templates/corporate/billing.html
+++ b/templates/corporate/billing.html
@@ -11,7 +11,11 @@
{% if admin_access and has_active_plan %}
{% if is_sponsorship_pending %}