mirror of https://github.com/zulip/zulip.git
billing: Add BillingSession support for requesting sponsorship.
This commit is contained in:
parent
1691205306
commit
a01618d633
|
@ -11,6 +11,7 @@ from functools import wraps
|
|||
from typing import Any, Callable, Dict, Generator, Optional, Tuple, TypedDict, TypeVar, Union
|
||||
|
||||
import stripe
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core import signing
|
||||
from django.core.signing import Signer
|
||||
|
@ -28,6 +29,7 @@ from corporate.models import (
|
|||
LicenseLedger,
|
||||
PaymentIntent,
|
||||
Session,
|
||||
ZulipSponsorshipRequest,
|
||||
get_current_plan_by_customer,
|
||||
get_current_plan_by_realm,
|
||||
get_customer_by_realm,
|
||||
|
@ -36,10 +38,20 @@ from corporate.models import (
|
|||
)
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.logging_util import log_to_file
|
||||
from zerver.lib.send_email import FromAddress, send_email_to_billing_admins_and_realm_owners
|
||||
from zerver.lib.send_email import (
|
||||
FromAddress,
|
||||
send_email,
|
||||
send_email_to_billing_admins_and_realm_owners,
|
||||
)
|
||||
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
|
||||
from zerver.lib.utils import assert_is_not_none
|
||||
from zerver.models import Realm, RealmAuditLog, UserProfile, get_system_bot
|
||||
from zerver.models import (
|
||||
Realm,
|
||||
RealmAuditLog,
|
||||
UserProfile,
|
||||
get_org_type_display_name,
|
||||
get_system_bot,
|
||||
)
|
||||
from zilencer.models import (
|
||||
RemoteRealm,
|
||||
RemoteRealmAuditLog,
|
||||
|
@ -533,6 +545,20 @@ class UpgradePageSessionTypeSpecificContext(TypedDict):
|
|||
is_self_hosting: bool
|
||||
|
||||
|
||||
class SponsorshipApplicantInfo(TypedDict):
|
||||
name: str
|
||||
role: str
|
||||
email: str
|
||||
|
||||
|
||||
class SponsorshipRequestSessionSpecificContext(TypedDict):
|
||||
# We don't store UserProfile for remote realms.
|
||||
realm_user: Optional[UserProfile]
|
||||
user_info: SponsorshipApplicantInfo
|
||||
# TODO: Call this what we end up calling it for /support page.
|
||||
realm_string_id: str
|
||||
|
||||
|
||||
class UpgradePageContext(TypedDict):
|
||||
customer_name: str
|
||||
default_invoice_days_until_due: int
|
||||
|
@ -552,12 +578,25 @@ class UpgradePageContext(TypedDict):
|
|||
signed_seat_count: str
|
||||
|
||||
|
||||
class SponsorshipRequestForm(forms.Form):
|
||||
website = forms.URLField(max_length=ZulipSponsorshipRequest.MAX_ORG_URL_LENGTH, required=False)
|
||||
organization_type = forms.IntegerField()
|
||||
description = forms.CharField(widget=forms.Textarea)
|
||||
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)
|
||||
|
||||
|
||||
class BillingSession(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def billing_session_url(self) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def support_url(self) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_customer(self) -> Optional[Customer]:
|
||||
pass
|
||||
|
@ -622,6 +661,16 @@ class BillingSession(ABC):
|
|||
def is_sponsored_or_pending(self, customer: Optional[Customer]) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_sponsorship_request_session_specific_context(
|
||||
self,
|
||||
) -> SponsorshipRequestSessionSpecificContext:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_org_type_from_request_sponsorship_session(self, org_type: int) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_upgrade_page_session_type_specific_context(
|
||||
self,
|
||||
|
@ -1717,6 +1766,67 @@ class BillingSession(ABC):
|
|||
self.add_sponsorship_info_to_context(context)
|
||||
return context
|
||||
|
||||
def request_sponsorship(self, form: SponsorshipRequestForm) -> None:
|
||||
if not form.is_valid():
|
||||
message = " ".join(
|
||||
error["message"]
|
||||
for error_list in form.errors.get_json_data().values()
|
||||
for error in error_list
|
||||
)
|
||||
raise BillingError("Form validation error", message=message)
|
||||
|
||||
request_context = self.get_sponsorship_request_session_specific_context()
|
||||
with transaction.atomic():
|
||||
# Ensures customer is created first before updating sponsorship status.
|
||||
self.update_customer_sponsorship_status(True)
|
||||
sponsorship_request = ZulipSponsorshipRequest(
|
||||
customer=self.get_customer(),
|
||||
requested_by=request_context["realm_user"],
|
||||
org_website=form.cleaned_data["website"],
|
||||
org_description=form.cleaned_data["description"],
|
||||
org_type=form.cleaned_data["organization_type"],
|
||||
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"],
|
||||
)
|
||||
sponsorship_request.save()
|
||||
|
||||
org_type = form.cleaned_data["organization_type"]
|
||||
self.save_org_type_from_request_sponsorship_session(org_type)
|
||||
|
||||
if request_context["realm_user"] is not None:
|
||||
# TODO: Refactor to not create an import cycle.
|
||||
from zerver.actions.users import do_change_is_billing_admin
|
||||
|
||||
do_change_is_billing_admin(request_context["realm_user"], True)
|
||||
|
||||
org_type_display_name = get_org_type_display_name(org_type)
|
||||
|
||||
user_info = request_context["user_info"]
|
||||
support_url = self.support_url()
|
||||
context = {
|
||||
"requested_by": user_info["name"],
|
||||
"user_role": user_info["role"],
|
||||
# TODO: realm_string_id needs to be replaced by something more generic.
|
||||
"string_id": request_context["realm_string_id"],
|
||||
"support_url": support_url,
|
||||
"organization_type": org_type_display_name,
|
||||
"website": sponsorship_request.org_website,
|
||||
"description": sponsorship_request.org_description,
|
||||
"expected_total_users": sponsorship_request.expected_total_users,
|
||||
"paid_users_count": sponsorship_request.paid_users_count,
|
||||
"paid_users_description": sponsorship_request.paid_users_description,
|
||||
}
|
||||
send_email(
|
||||
"zerver/emails/sponsorship_request",
|
||||
to_emails=[FromAddress.SUPPORT],
|
||||
# Sent to the server's support team, so this email is not user-facing.
|
||||
from_name="Zulip sponsorship request",
|
||||
from_address=FromAddress.tokenized_no_reply_address(),
|
||||
reply_to_email=user_info["email"],
|
||||
context=context,
|
||||
)
|
||||
|
||||
|
||||
class RealmBillingSession(BillingSession):
|
||||
def __init__(
|
||||
|
@ -1751,6 +1861,13 @@ class RealmBillingSession(BillingSession):
|
|||
def billing_session_url(self) -> str:
|
||||
return self.realm.uri
|
||||
|
||||
@override
|
||||
def support_url(self) -> str:
|
||||
# TODO: Refactor to not create an import cycle.
|
||||
from corporate.lib.support import get_support_url
|
||||
|
||||
return get_support_url(self.realm)
|
||||
|
||||
@override
|
||||
def get_customer(self) -> Optional[Customer]:
|
||||
return get_customer_by_realm(self.realm)
|
||||
|
@ -1987,10 +2104,34 @@ class RealmBillingSession(BillingSession):
|
|||
),
|
||||
)
|
||||
|
||||
@override
|
||||
def get_sponsorship_request_session_specific_context(
|
||||
self,
|
||||
) -> SponsorshipRequestSessionSpecificContext:
|
||||
assert self.user is not None
|
||||
return SponsorshipRequestSessionSpecificContext(
|
||||
realm_user=self.user,
|
||||
user_info=SponsorshipApplicantInfo(
|
||||
name=self.user.full_name,
|
||||
email=self.user.delivery_email,
|
||||
role=self.user.get_role_name(),
|
||||
),
|
||||
realm_string_id=self.realm.string_id,
|
||||
)
|
||||
|
||||
@override
|
||||
def save_org_type_from_request_sponsorship_session(self, org_type: int) -> None:
|
||||
# TODO: Use the actions.py method for this.
|
||||
if self.realm.org_type != org_type:
|
||||
self.realm.org_type = org_type
|
||||
self.realm.save(update_fields=["org_type"])
|
||||
|
||||
|
||||
class RemoteRealmBillingSession(BillingSession): # nocoverage
|
||||
def __init__(
|
||||
self, remote_realm: RemoteRealm, support_staff: Optional[UserProfile] = None
|
||||
self,
|
||||
remote_realm: RemoteRealm,
|
||||
support_staff: Optional[UserProfile] = None,
|
||||
) -> None:
|
||||
self.remote_realm = remote_realm
|
||||
if support_staff is not None:
|
||||
|
@ -2004,6 +2145,10 @@ class RemoteRealmBillingSession(BillingSession): # nocoverage
|
|||
def billing_session_url(self) -> str:
|
||||
return f"{settings.EXTERNAL_URI_SCHEME}{settings.SELF_HOSTING_MANAGEMENT_SUBDOMAIN}.{settings.EXTERNAL_HOST}/realm/{self.remote_realm.uuid}"
|
||||
|
||||
@override
|
||||
def support_url(self) -> str:
|
||||
return "TODO:not-implemented"
|
||||
|
||||
@override
|
||||
def get_customer(self) -> Optional[Customer]:
|
||||
return get_customer_by_remote_realm(self.remote_realm)
|
||||
|
@ -2195,8 +2340,40 @@ class RemoteRealmBillingSession(BillingSession): # nocoverage
|
|||
|
||||
@override
|
||||
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
|
||||
# TBD
|
||||
pass
|
||||
context.update(
|
||||
realm_org_type=self.remote_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=self.sponsorship_org_type_key_helper,
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
def get_sponsorship_request_session_specific_context(
|
||||
self,
|
||||
) -> SponsorshipRequestSessionSpecificContext:
|
||||
return SponsorshipRequestSessionSpecificContext(
|
||||
realm_user=None,
|
||||
user_info=SponsorshipApplicantInfo(
|
||||
# TODO: Plumb through the session data on the acting user.
|
||||
name="Remote realm administrator",
|
||||
email=self.remote_realm.server.contact_email,
|
||||
# TODO: Set user_role when determining which set of users can access the page.
|
||||
role="Remote realm administrator",
|
||||
),
|
||||
# TODO: Check if this works on support page.
|
||||
realm_string_id=self.remote_realm.host,
|
||||
)
|
||||
|
||||
@override
|
||||
def save_org_type_from_request_sponsorship_session(self, org_type: int) -> None:
|
||||
if self.remote_realm.org_type != org_type:
|
||||
self.remote_realm.org_type = org_type
|
||||
self.remote_realm.save(update_fields=["org_type"])
|
||||
|
||||
|
||||
class RemoteServerBillingSession(BillingSession): # nocoverage
|
||||
|
@ -2204,7 +2381,9 @@ class RemoteServerBillingSession(BillingSession): # nocoverage
|
|||
creating RemoteRealm objects."""
|
||||
|
||||
def __init__(
|
||||
self, remote_server: RemoteZulipServer, support_staff: Optional[UserProfile] = None
|
||||
self,
|
||||
remote_server: RemoteZulipServer,
|
||||
support_staff: Optional[UserProfile] = None,
|
||||
) -> None:
|
||||
self.remote_server = remote_server
|
||||
if support_staff is not None:
|
||||
|
@ -2218,6 +2397,10 @@ class RemoteServerBillingSession(BillingSession): # nocoverage
|
|||
def billing_session_url(self) -> str:
|
||||
return "TBD"
|
||||
|
||||
@override
|
||||
def support_url(self) -> str:
|
||||
return "TODO:not-implemented"
|
||||
|
||||
@override
|
||||
def get_customer(self) -> Optional[Customer]:
|
||||
return get_customer_by_remote_server(self.remote_server)
|
||||
|
@ -2401,8 +2584,42 @@ class RemoteServerBillingSession(BillingSession): # nocoverage
|
|||
|
||||
@override
|
||||
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
|
||||
# TBD
|
||||
pass
|
||||
context.update(
|
||||
realm_org_type=self.remote_server.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=self.sponsorship_org_type_key_helper,
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
def get_sponsorship_request_session_specific_context(
|
||||
self,
|
||||
) -> SponsorshipRequestSessionSpecificContext:
|
||||
return SponsorshipRequestSessionSpecificContext(
|
||||
realm_user=None,
|
||||
user_info=SponsorshipApplicantInfo(
|
||||
# TODO: Figure out a better story here. We don't
|
||||
# actually have a name or other details on the person
|
||||
# doing this flow, but could ask for it in the login
|
||||
# form if desired.
|
||||
name="Remote server administrator",
|
||||
email=self.remote_server.contact_email,
|
||||
role="Remote server administrator",
|
||||
),
|
||||
# TODO: Check if this works on support page.
|
||||
realm_string_id=self.remote_server.hostname,
|
||||
)
|
||||
|
||||
@override
|
||||
def save_org_type_from_request_sponsorship_session(self, org_type: int) -> None:
|
||||
if self.remote_server.org_type != org_type:
|
||||
self.remote_server.org_type = org_type
|
||||
self.remote_server.save(update_fields=["org_type"])
|
||||
|
||||
|
||||
def stripe_customer_has_credit_card_as_default_payment_method(
|
||||
|
|
|
@ -4134,7 +4134,7 @@ class RequiresBillingAccessTest(StripeTestCase):
|
|||
pat
|
||||
for name in reverse_dict
|
||||
for matches, pat, defaults, converters in reverse_dict.getlist(name)
|
||||
if pat.startswith(re.escape("json/"))
|
||||
if pat.startswith("json/") and not (pat.startswith(("json/realm/", "json/server/")))
|
||||
}
|
||||
self.assert_length(json_endpoints, len(tested_endpoints))
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.conf.urls import include
|
|||
from django.urls import path
|
||||
from django.views.generic import RedirectView, TemplateView
|
||||
|
||||
from corporate.views.billing_page import billing_home, sponsorship_request, update_plan
|
||||
from corporate.views.billing_page import billing_home, update_plan
|
||||
from corporate.views.event_status import event_status, event_status_page
|
||||
from corporate.views.portico import (
|
||||
app_download_link_redirect,
|
||||
|
@ -27,8 +27,16 @@ from corporate.views.session import (
|
|||
start_card_update_stripe_session,
|
||||
start_card_update_stripe_session_for_realm_upgrade,
|
||||
)
|
||||
from corporate.views.sponsorship import (
|
||||
remote_realm_sponsorship,
|
||||
remote_realm_sponsorship_page,
|
||||
remote_server_sponsorship,
|
||||
remote_server_sponsorship_page,
|
||||
sponsorship,
|
||||
sponsorship_page,
|
||||
)
|
||||
from corporate.views.support import support_request
|
||||
from corporate.views.upgrade import remote_realm_upgrade_page, sponsorship, upgrade, upgrade_page
|
||||
from corporate.views.upgrade import remote_realm_upgrade_page, upgrade, upgrade_page
|
||||
from corporate.views.webhook import stripe_webhook
|
||||
from zerver.lib.rest import rest_path
|
||||
from zerver.lib.url_redirects import LANDING_PAGE_REDIRECTS
|
||||
|
@ -40,7 +48,7 @@ i18n_urlpatterns: Any = [
|
|||
path("jobs/", TemplateView.as_view(template_name="corporate/jobs.html")),
|
||||
# Billing
|
||||
path("billing/", billing_home, name="billing_home"),
|
||||
path("sponsorship/", sponsorship_request, name="sponsorship_request"),
|
||||
path("sponsorship/", sponsorship_page, name="sponsorship_request"),
|
||||
path("upgrade/", upgrade_page, name="upgrade_page"),
|
||||
path("support/", support_request),
|
||||
path("billing/event_status/", event_status_page, name="event_status_page"),
|
||||
|
@ -151,16 +159,11 @@ i18n_urlpatterns += landing_page_urls
|
|||
# Make a copy of i18n_urlpatterns so that they appear without prefix for English
|
||||
urlpatterns = list(i18n_urlpatterns)
|
||||
|
||||
urlpatterns += [
|
||||
path("api/v1/", include(v1_api_and_json_patterns)),
|
||||
path("json/", include(v1_api_and_json_patterns)),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
path(
|
||||
"remote-billing-login/<signed_billing_access_token>", remote_server_billing_finalize_login
|
||||
),
|
||||
# Remote server billling endpoints.
|
||||
# Remote server billing endpoints.
|
||||
path("realm/<realm_uuid>/plans", remote_billing_plans_realm, name="remote_billing_plans_realm"),
|
||||
path(
|
||||
"server/<server_uuid>/plans",
|
||||
|
@ -170,9 +173,27 @@ urlpatterns += [
|
|||
path("realm/<realm_uuid>/billing", remote_billing_page_realm, name="remote_billing_page_realm"),
|
||||
path("server/<server_uuid>/", remote_billing_page_server, name="remote_billing_page_server"),
|
||||
path("realm/<realm_uuid>/upgrade", remote_realm_upgrade_page, name="remote_realm_upgrade_page"),
|
||||
path(
|
||||
"realm/<realm_uuid>/sponsorship",
|
||||
remote_realm_sponsorship_page,
|
||||
name="remote_realm_sponsorship_page",
|
||||
),
|
||||
path(
|
||||
"server/<server_uuid>/sponsorship",
|
||||
remote_server_sponsorship_page,
|
||||
name="remote_server_sponsorship_page",
|
||||
),
|
||||
path(
|
||||
"serverlogin/",
|
||||
remote_billing_legacy_server_login,
|
||||
name="remote_billing_legacy_server_login",
|
||||
),
|
||||
# Remote variants of above API endpoints.
|
||||
path("json/realm/<realm_uuid>/sponsorship", remote_realm_sponsorship),
|
||||
path("json/server/<server_uuid>/sponsorship", remote_server_sponsorship),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
path("api/v1/", include(v1_api_and_json_patterns)),
|
||||
path("json/", include(v1_api_and_json_patterns)),
|
||||
]
|
||||
|
|
|
@ -16,19 +16,6 @@ from zerver.models import UserProfile
|
|||
billing_logger = logging.getLogger("corporate.stripe")
|
||||
|
||||
|
||||
@zulip_login_required
|
||||
def sponsorship_request(request: HttpRequest) -> HttpResponse:
|
||||
user = request.user
|
||||
assert user.is_authenticated
|
||||
|
||||
billing_session = RealmBillingSession(user)
|
||||
context = billing_session.get_sponsorship_request_context()
|
||||
if context is None:
|
||||
return HttpResponseRedirect(reverse("billing_home"))
|
||||
|
||||
return render(request, "corporate/sponsorship.html", context=context)
|
||||
|
||||
|
||||
@zulip_login_required
|
||||
@has_request_variables
|
||||
def billing_home(
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
|
||||
from corporate.lib.decorator import (
|
||||
authenticated_remote_realm_management_endpoint,
|
||||
authenticated_remote_server_management_endpoint,
|
||||
)
|
||||
from corporate.lib.stripe import (
|
||||
RealmBillingSession,
|
||||
RemoteRealmBillingSession,
|
||||
RemoteServerBillingSession,
|
||||
SponsorshipRequestForm,
|
||||
)
|
||||
from zerver.decorator import require_organization_member, zulip_login_required
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.models import UserProfile
|
||||
from zilencer.models import RemoteRealm, RemoteZulipServer
|
||||
|
||||
|
||||
@zulip_login_required
|
||||
def sponsorship_page(request: HttpRequest) -> HttpResponse:
|
||||
user = request.user
|
||||
assert user.is_authenticated
|
||||
|
||||
billing_session = RealmBillingSession(user)
|
||||
context = billing_session.get_sponsorship_request_context()
|
||||
if context is None:
|
||||
return HttpResponseRedirect(reverse("billing_home"))
|
||||
|
||||
return render(request, "corporate/sponsorship.html", context=context)
|
||||
|
||||
|
||||
@authenticated_remote_realm_management_endpoint
|
||||
def remote_realm_sponsorship_page(
|
||||
request: HttpRequest,
|
||||
remote_realm: RemoteRealm,
|
||||
) -> HttpResponse: # nocoverage
|
||||
billing_session = RemoteRealmBillingSession(remote_realm)
|
||||
context = billing_session.get_sponsorship_request_context()
|
||||
if context is None:
|
||||
return HttpResponseRedirect(reverse("remote_billing_page_realm"))
|
||||
|
||||
return render(request, "corporate/sponsorship.html", context=context)
|
||||
|
||||
|
||||
@authenticated_remote_server_management_endpoint
|
||||
def remote_server_sponsorship_page(
|
||||
request: HttpRequest,
|
||||
remote_server: RemoteZulipServer,
|
||||
) -> HttpResponse: # nocoverage
|
||||
billing_session = RemoteServerBillingSession(remote_server)
|
||||
context = billing_session.get_sponsorship_request_context()
|
||||
if context is None:
|
||||
return HttpResponseRedirect(reverse("remote_billing_page_server"))
|
||||
|
||||
return render(request, "corporate/sponsorship.html", context=context)
|
||||
|
||||
|
||||
@require_organization_member
|
||||
def sponsorship(
|
||||
request: HttpRequest,
|
||||
user: UserProfile,
|
||||
) -> HttpResponse:
|
||||
billing_session = RealmBillingSession(user)
|
||||
post_data = request.POST.copy()
|
||||
form = SponsorshipRequestForm(post_data)
|
||||
billing_session.request_sponsorship(form)
|
||||
return json_success(request)
|
||||
|
||||
|
||||
@authenticated_remote_realm_management_endpoint
|
||||
def remote_realm_sponsorship(
|
||||
request: HttpRequest,
|
||||
remote_realm: RemoteRealm,
|
||||
) -> HttpResponse: # nocoverage
|
||||
billing_session = RemoteRealmBillingSession(remote_realm)
|
||||
post_data = request.POST.copy()
|
||||
form = SponsorshipRequestForm(post_data)
|
||||
billing_session.request_sponsorship(form)
|
||||
return json_success(request)
|
||||
|
||||
|
||||
@authenticated_remote_server_management_endpoint
|
||||
def remote_server_sponsorship(
|
||||
request: HttpRequest,
|
||||
remote_server: RemoteZulipServer,
|
||||
) -> HttpResponse: # nocoverage
|
||||
billing_session = RemoteServerBillingSession(remote_server)
|
||||
post_data = request.POST.copy()
|
||||
form = SponsorshipRequestForm(post_data)
|
||||
billing_session.request_sponsorship(form)
|
||||
return json_success(request)
|
|
@ -1,9 +1,7 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from pydantic import Json
|
||||
|
@ -19,16 +17,13 @@ from corporate.lib.stripe import (
|
|||
RemoteRealmBillingSession,
|
||||
UpgradeRequest,
|
||||
)
|
||||
from corporate.lib.support import get_support_url
|
||||
from corporate.models import CustomerPlan, ZulipSponsorshipRequest
|
||||
from zerver.actions.users import do_change_is_billing_admin
|
||||
from corporate.models import CustomerPlan
|
||||
from zerver.decorator import require_organization_member, zulip_login_required
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.send_email import FromAddress, send_email
|
||||
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
|
||||
from zerver.lib.validator import check_bool, check_int, check_string_in
|
||||
from zerver.models import UserProfile, get_org_type_display_name
|
||||
from zerver.models import UserProfile
|
||||
from zilencer.models import RemoteRealm
|
||||
|
||||
billing_logger = logging.getLogger("corporate.stripe")
|
||||
|
@ -128,84 +123,3 @@ def remote_realm_upgrade_page(
|
|||
|
||||
response = render(request, "corporate/upgrade.html", context=context)
|
||||
return response
|
||||
|
||||
|
||||
class SponsorshipRequestForm(forms.Form):
|
||||
website = forms.URLField(max_length=ZulipSponsorshipRequest.MAX_ORG_URL_LENGTH, required=False)
|
||||
organization_type = forms.IntegerField()
|
||||
description = forms.CharField(widget=forms.Textarea)
|
||||
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)
|
||||
|
||||
|
||||
@require_organization_member
|
||||
def sponsorship(
|
||||
request: HttpRequest,
|
||||
user: UserProfile,
|
||||
) -> HttpResponse:
|
||||
realm = user.realm
|
||||
billing_session = RealmBillingSession(user)
|
||||
|
||||
requested_by = user.full_name
|
||||
user_role = user.get_role_name()
|
||||
support_url = get_support_url(realm)
|
||||
|
||||
post_data = request.POST.copy()
|
||||
form = SponsorshipRequestForm(post_data)
|
||||
|
||||
if form.is_valid():
|
||||
with transaction.atomic():
|
||||
# Ensures customer is created first before updating sponsorship status.
|
||||
billing_session.update_customer_sponsorship_status(True)
|
||||
sponsorship_request = ZulipSponsorshipRequest(
|
||||
customer=billing_session.get_customer(),
|
||||
requested_by=user,
|
||||
org_website=form.cleaned_data["website"],
|
||||
org_description=form.cleaned_data["description"],
|
||||
org_type=form.cleaned_data["organization_type"],
|
||||
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"],
|
||||
)
|
||||
sponsorship_request.save()
|
||||
|
||||
org_type = form.cleaned_data["organization_type"]
|
||||
if realm.org_type != org_type:
|
||||
realm.org_type = org_type
|
||||
realm.save(update_fields=["org_type"])
|
||||
|
||||
do_change_is_billing_admin(user, True)
|
||||
|
||||
org_type_display_name = get_org_type_display_name(org_type)
|
||||
|
||||
context = {
|
||||
"requested_by": requested_by,
|
||||
"user_role": user_role,
|
||||
"string_id": realm.string_id,
|
||||
"support_url": support_url,
|
||||
"organization_type": org_type_display_name,
|
||||
"website": sponsorship_request.org_website,
|
||||
"description": sponsorship_request.org_description,
|
||||
"expected_total_users": sponsorship_request.expected_total_users,
|
||||
"paid_users_count": sponsorship_request.paid_users_count,
|
||||
"paid_users_description": sponsorship_request.paid_users_description,
|
||||
}
|
||||
# Sent to the server's support team, so this email is not user-facing.
|
||||
send_email(
|
||||
"zerver/emails/sponsorship_request",
|
||||
to_emails=[FromAddress.SUPPORT],
|
||||
from_name="Zulip sponsorship request",
|
||||
from_address=FromAddress.tokenized_no_reply_address(),
|
||||
reply_to_email=user.delivery_email,
|
||||
context=context,
|
||||
)
|
||||
|
||||
return json_success(request)
|
||||
else:
|
||||
message = " ".join(
|
||||
error["message"]
|
||||
for error_list in form.errors.get_json_data().values()
|
||||
for error in error_list
|
||||
)
|
||||
raise BillingError("Form validation error", message=message)
|
||||
|
|
|
@ -55,6 +55,7 @@ function create_ajax_request(): void {
|
|||
|
||||
void $.ajax({
|
||||
type: "post",
|
||||
// TODO: This needs to be conditional on billing session type
|
||||
url: "/json/billing/sponsorship",
|
||||
data,
|
||||
success() {
|
||||
|
|
Loading…
Reference in New Issue