2018-09-25 12:24:11 +02:00
|
|
|
import logging
|
2023-11-20 08:40:09 +01:00
|
|
|
from typing import Optional
|
2018-09-25 12:24:11 +02:00
|
|
|
|
2021-07-09 19:56:55 +02:00
|
|
|
from django import forms
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.conf import settings
|
2021-07-09 19:56:55 +02:00
|
|
|
from django.db import transaction
|
2018-09-25 12:24:11 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
2019-02-02 23:53:22 +01:00
|
|
|
from django.shortcuts import render
|
2023-11-24 09:12:17 +01:00
|
|
|
from pydantic import Json
|
2018-09-25 12:24:11 +02:00
|
|
|
|
2023-11-26 18:34:33 +01:00
|
|
|
from corporate.lib.decorator import authenticated_remote_realm_management_endpoint
|
2020-06-11 00:54:34 +02:00
|
|
|
from corporate.lib.stripe import (
|
2023-11-14 11:59:48 +01:00
|
|
|
VALID_BILLING_MODALITY_VALUES,
|
|
|
|
VALID_BILLING_SCHEDULE_VALUES,
|
|
|
|
VALID_LICENSE_MANAGEMENT_VALUES,
|
2020-06-11 00:54:34 +02:00
|
|
|
BillingError,
|
2023-11-20 08:40:09 +01:00
|
|
|
InitialUpgradeRequest,
|
2023-10-26 14:11:43 +02:00
|
|
|
RealmBillingSession,
|
2023-11-24 09:12:17 +01:00
|
|
|
RemoteRealmBillingSession,
|
2023-11-14 11:59:48 +01:00
|
|
|
UpgradeRequest,
|
2020-06-11 00:54:34 +02:00
|
|
|
)
|
2021-09-29 19:51:55 +02:00
|
|
|
from corporate.lib.support import get_support_url
|
2023-11-22 07:36:24 +01:00
|
|
|
from corporate.models import CustomerPlan, ZulipSponsorshipRequest
|
2023-11-15 22:30:08 +01:00
|
|
|
from zerver.actions.users import do_change_is_billing_admin
|
2023-11-26 18:29:51 +01:00
|
|
|
from zerver.decorator import require_organization_member, zulip_login_required
|
2018-09-25 12:24:11 +02:00
|
|
|
from zerver.lib.request import REQ, has_request_variables
|
2021-07-04 08:19:18 +02:00
|
|
|
from zerver.lib.response import json_success
|
2020-06-09 12:24:32 +02:00
|
|
|
from zerver.lib.send_email import FromAddress, send_email
|
2023-11-24 09:12:17 +01:00
|
|
|
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
|
2021-07-29 19:01:59 +02:00
|
|
|
from zerver.lib.validator import check_bool, check_int, check_string_in
|
2023-02-13 20:40:51 +01:00
|
|
|
from zerver.models import UserProfile, get_org_type_display_name
|
2023-11-26 18:34:33 +01:00
|
|
|
from zilencer.models import RemoteRealm
|
2018-09-25 12:24:11 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
billing_logger = logging.getLogger("corporate.stripe")
|
2018-09-25 12:24:11 +02:00
|
|
|
|
2018-12-22 05:29:25 +01:00
|
|
|
|
2020-07-15 22:18:32 +02:00
|
|
|
@require_organization_member
|
2018-12-07 18:43:22 +01:00
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def upgrade(
|
|
|
|
request: HttpRequest,
|
|
|
|
user: UserProfile,
|
2021-04-09 12:43:44 +02:00
|
|
|
billing_modality: str = REQ(str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)),
|
|
|
|
schedule: str = REQ(str_validator=check_string_in(VALID_BILLING_SCHEDULE_VALUES)),
|
|
|
|
signed_seat_count: str = REQ(),
|
|
|
|
salt: str = REQ(),
|
|
|
|
license_management: Optional[str] = REQ(
|
|
|
|
default=None, str_validator=check_string_in(VALID_LICENSE_MANAGEMENT_VALUES)
|
|
|
|
),
|
2021-04-07 22:00:44 +02:00
|
|
|
licenses: Optional[int] = REQ(json_validator=check_int, default=None),
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> HttpResponse:
|
2018-12-07 18:43:22 +01:00
|
|
|
try:
|
2023-11-14 11:59:48 +01:00
|
|
|
upgrade_request = UpgradeRequest(
|
|
|
|
billing_modality=billing_modality,
|
|
|
|
schedule=schedule,
|
|
|
|
signed_seat_count=signed_seat_count,
|
|
|
|
salt=salt,
|
|
|
|
license_management=license_management,
|
|
|
|
licenses=licenses,
|
2023-04-10 21:48:52 +02:00
|
|
|
)
|
2023-11-13 07:55:57 +01:00
|
|
|
billing_session = RealmBillingSession(user)
|
2023-11-14 11:59:48 +01:00
|
|
|
data = billing_session.do_upgrade(upgrade_request)
|
|
|
|
return json_success(request, data)
|
2021-08-29 15:33:29 +02:00
|
|
|
except BillingError as e:
|
|
|
|
billing_logger.warning(
|
|
|
|
"BillingError during upgrade: %s. user=%s, realm=%s (%s), billing_modality=%s, "
|
|
|
|
"schedule=%s, license_management=%s, licenses=%s",
|
|
|
|
e.error_description,
|
|
|
|
user.id,
|
|
|
|
user.realm.id,
|
|
|
|
user.realm.string_id,
|
|
|
|
billing_modality,
|
|
|
|
schedule,
|
|
|
|
license_management,
|
|
|
|
licenses,
|
|
|
|
)
|
|
|
|
raise e
|
2020-06-12 01:35:37 +02:00
|
|
|
except Exception:
|
2020-08-11 03:19:00 +02:00
|
|
|
billing_logger.exception("Uncaught exception in billing:", stack_info=True)
|
2020-10-17 03:42:50 +02:00
|
|
|
error_message = BillingError.CONTACT_SUPPORT.format(email=settings.ZULIP_ADMINISTRATOR)
|
2018-12-07 18:43:22 +01:00
|
|
|
error_description = "uncaught exception during upgrade"
|
2021-07-04 08:19:18 +02:00
|
|
|
raise BillingError(error_description, error_message)
|
2018-12-07 18:43:22 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-09-25 12:24:11 +02:00
|
|
|
@zulip_login_required
|
2021-07-29 19:01:59 +02:00
|
|
|
@has_request_variables
|
2023-11-23 09:31:46 +01:00
|
|
|
def upgrade_page(
|
2023-11-10 10:59:28 +01:00
|
|
|
request: HttpRequest,
|
|
|
|
manual_license_management: bool = REQ(default=False, json_validator=check_bool),
|
2021-07-29 19:01:59 +02:00
|
|
|
) -> HttpResponse:
|
2018-09-25 12:24:11 +02:00
|
|
|
user = request.user
|
2021-07-24 20:37:35 +02:00
|
|
|
assert user.is_authenticated
|
2020-06-09 12:24:32 +02:00
|
|
|
|
2020-07-15 22:18:32 +02:00
|
|
|
if not settings.BILLING_ENABLED or user.is_guest:
|
|
|
|
return render(request, "404.html", status=404)
|
|
|
|
|
2023-11-20 08:40:09 +01:00
|
|
|
initial_upgrade_request = InitialUpgradeRequest(
|
|
|
|
manual_license_management=manual_license_management,
|
2023-11-22 07:36:24 +01:00
|
|
|
tier=CustomerPlan.STANDARD,
|
2023-04-10 21:48:52 +02:00
|
|
|
)
|
2023-11-20 08:40:09 +01:00
|
|
|
billing_session = RealmBillingSession(user)
|
2023-11-20 20:32:29 +01:00
|
|
|
redirect_url, context = billing_session.get_initial_upgrade_context(initial_upgrade_request)
|
2023-04-10 21:48:52 +02:00
|
|
|
|
2023-11-20 08:40:09 +01:00
|
|
|
if redirect_url:
|
|
|
|
return HttpResponseRedirect(redirect_url)
|
2023-02-13 20:40:51 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
response = render(request, "corporate/upgrade.html", context=context)
|
2018-09-25 12:24:11 +02:00
|
|
|
return response
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2023-11-26 18:34:33 +01:00
|
|
|
@authenticated_remote_realm_management_endpoint
|
2023-11-24 09:12:17 +01:00
|
|
|
@typed_endpoint
|
|
|
|
def remote_realm_upgrade_page(
|
|
|
|
request: HttpRequest,
|
2023-11-26 18:34:33 +01:00
|
|
|
remote_realm: RemoteRealm,
|
2023-11-24 09:12:17 +01:00
|
|
|
*,
|
|
|
|
realm_uuid: PathOnly[str],
|
|
|
|
manual_license_management: Json[bool] = False,
|
|
|
|
) -> HttpResponse: # nocoverage
|
|
|
|
initial_upgrade_request = InitialUpgradeRequest(
|
|
|
|
manual_license_management=manual_license_management,
|
|
|
|
tier=CustomerPlan.STANDARD,
|
|
|
|
)
|
|
|
|
billing_session = RemoteRealmBillingSession(remote_realm)
|
|
|
|
redirect_url, context = billing_session.get_initial_upgrade_context(initial_upgrade_request)
|
|
|
|
|
|
|
|
if redirect_url:
|
|
|
|
return HttpResponseRedirect(redirect_url)
|
|
|
|
|
|
|
|
response = render(request, "corporate/upgrade.html", context=context)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2021-07-09 19:56:55 +02:00
|
|
|
class SponsorshipRequestForm(forms.Form):
|
2021-08-10 20:52:01 +02:00
|
|
|
website = forms.URLField(max_length=ZulipSponsorshipRequest.MAX_ORG_URL_LENGTH, required=False)
|
2021-07-09 19:56:55 +02:00
|
|
|
organization_type = forms.IntegerField()
|
|
|
|
description = forms.CharField(widget=forms.Textarea)
|
2023-11-11 16:15:33 +01:00
|
|
|
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)
|
2021-07-09 19:56:55 +02:00
|
|
|
|
|
|
|
|
2020-07-15 22:18:32 +02:00
|
|
|
@require_organization_member
|
2020-06-09 12:24:32 +02:00
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def sponsorship(
|
|
|
|
request: HttpRequest,
|
|
|
|
user: UserProfile,
|
2021-04-09 12:43:44 +02:00
|
|
|
organization_type: str = REQ("organization-type"),
|
|
|
|
website: str = REQ(),
|
|
|
|
description: str = REQ(),
|
2023-11-11 16:15:33 +01:00
|
|
|
expected_total_users: str = REQ(),
|
|
|
|
paid_users_count: str = REQ(),
|
|
|
|
paid_users_description: str = REQ(),
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> HttpResponse:
|
2020-06-09 12:24:32 +02:00
|
|
|
realm = user.realm
|
2023-11-02 18:17:08 +01:00
|
|
|
billing_session = RealmBillingSession(user)
|
2020-06-09 12:24:32 +02:00
|
|
|
|
|
|
|
requested_by = user.full_name
|
2020-07-21 02:25:28 +02:00
|
|
|
user_role = user.get_role_name()
|
2021-09-29 19:51:55 +02:00
|
|
|
support_url = get_support_url(realm)
|
2020-06-09 12:24:32 +02:00
|
|
|
|
2021-07-09 19:56:55 +02:00
|
|
|
post_data = request.POST.copy()
|
|
|
|
# We need to do this because the field name in the template
|
|
|
|
# for organization type contains a hyphen and the form expects
|
|
|
|
# an underscore.
|
|
|
|
post_data.update(organization_type=organization_type)
|
|
|
|
form = SponsorshipRequestForm(post_data)
|
|
|
|
|
2021-08-10 20:52:01 +02:00
|
|
|
if form.is_valid():
|
|
|
|
with transaction.atomic():
|
2023-11-27 06:16:25 +01:00
|
|
|
# Ensures customer is created first before updating sponsorship status.
|
|
|
|
billing_session.update_customer_sponsorship_status(True)
|
2021-07-09 19:56:55 +02:00
|
|
|
sponsorship_request = ZulipSponsorshipRequest(
|
2023-11-27 06:16:25 +01:00
|
|
|
customer=billing_session.get_customer(),
|
2021-07-09 19:56:55 +02:00
|
|
|
requested_by=user,
|
|
|
|
org_website=form.cleaned_data["website"],
|
|
|
|
org_description=form.cleaned_data["description"],
|
|
|
|
org_type=form.cleaned_data["organization_type"],
|
2023-11-11 16:15:33 +01:00
|
|
|
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"],
|
2021-07-09 19:56:55 +02:00
|
|
|
)
|
|
|
|
sponsorship_request.save()
|
|
|
|
|
2021-07-19 03:59:40 +02:00
|
|
|
org_type = form.cleaned_data["organization_type"]
|
|
|
|
if realm.org_type != org_type:
|
|
|
|
realm.org_type = org_type
|
|
|
|
realm.save(update_fields=["org_type"])
|
|
|
|
|
2023-11-15 22:30:08 +01:00
|
|
|
do_change_is_billing_admin(user, True)
|
2021-08-10 20:52:01 +02:00
|
|
|
|
|
|
|
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": website,
|
|
|
|
"description": description,
|
2023-11-11 16:15:33 +01:00
|
|
|
"expected_total_users": expected_total_users,
|
|
|
|
"paid_users_count": paid_users_count,
|
|
|
|
"paid_users_description": paid_users_description,
|
2021-08-10 20:52:01 +02:00
|
|
|
}
|
|
|
|
send_email(
|
|
|
|
"zerver/emails/sponsorship_request",
|
|
|
|
to_emails=[FromAddress.SUPPORT],
|
|
|
|
from_name="Zulip sponsorship",
|
|
|
|
from_address=FromAddress.tokenized_no_reply_address(),
|
|
|
|
reply_to_email=user.delivery_email,
|
|
|
|
context=context,
|
|
|
|
)
|
2020-06-09 12:24:32 +02:00
|
|
|
|
2022-01-31 13:44:02 +01:00
|
|
|
return json_success(request)
|
2021-08-10 20:52:01 +02:00
|
|
|
else:
|
2023-07-31 22:52:35 +02:00
|
|
|
message = " ".join(
|
|
|
|
error["message"]
|
|
|
|
for error_list in form.errors.get_json_data().values()
|
|
|
|
for error in error_list
|
|
|
|
)
|
2021-08-10 20:52:01 +02:00
|
|
|
raise BillingError("Form validation error", message=message)
|