mirror of https://github.com/zulip/zulip.git
remote-support: Add ability to configure temporary courtesy plan.
Expands section for scheduling plans in the remote support view to have a form to create a temporary courtesy plan (aka our legacy plan for remote servers and realms). Form is not shown if there is a current plan for the remote billing entity, and would raise a SupportRequestError in that case as well.
This commit is contained in:
parent
f27cee21e3
commit
3d58a7ec04
|
@ -579,6 +579,7 @@ class SupportType(Enum):
|
|||
update_required_plan_tier = 8
|
||||
configure_fixed_price_plan = 9
|
||||
delete_fixed_price_next_plan = 10
|
||||
configure_temporary_courtesy_plan = 11
|
||||
|
||||
|
||||
class SupportViewRequest(TypedDict, total=False):
|
||||
|
@ -1411,6 +1412,28 @@ class BillingSession(ABC):
|
|||
plan_tier_name = CustomerPlan.name_from_tier(new_plan_tier)
|
||||
return f"Required plan tier for {self.billing_entity_display_name} set to {plan_tier_name}."
|
||||
|
||||
def configure_temporary_courtesy_plan(self, end_date_string: str) -> str:
|
||||
plan_end_date = datetime.strptime(end_date_string, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
if plan_end_date.date() <= timezone_now().date():
|
||||
raise SupportRequestError(
|
||||
f"Cannot configure a courtesy plan for {self.billing_entity_display_name} to end on {end_date_string}."
|
||||
)
|
||||
customer = self.get_customer()
|
||||
if customer is not None:
|
||||
plan = get_current_plan_by_customer(customer)
|
||||
if plan is not None:
|
||||
raise SupportRequestError(
|
||||
f"Cannot configure a courtesy plan for {self.billing_entity_display_name} because of current plan."
|
||||
)
|
||||
plan_anchor_date = timezone_now()
|
||||
if isinstance(self, RealmBillingSession):
|
||||
raise SupportRequestError(
|
||||
f"Cannot currently configure a courtesy plan for {self.billing_entity_display_name}."
|
||||
) # nocoverage
|
||||
|
||||
self.migrate_customer_to_legacy_plan(plan_anchor_date, plan_end_date)
|
||||
return f"Temporary courtesy plan for {self.billing_entity_display_name} configured to end on {end_date_string}."
|
||||
|
||||
def configure_fixed_price_plan(self, fixed_price: int, sent_invoice_id: str | None) -> str:
|
||||
customer = self.get_customer()
|
||||
if customer is None:
|
||||
|
@ -3384,6 +3407,10 @@ class BillingSession(ABC):
|
|||
new_fixed_price = support_request["fixed_price"]
|
||||
sent_invoice_id = support_request["sent_invoice_id"]
|
||||
success_message = self.configure_fixed_price_plan(new_fixed_price, sent_invoice_id)
|
||||
elif support_type == SupportType.configure_temporary_courtesy_plan:
|
||||
assert support_request["plan_end_date"] is not None
|
||||
temporary_plan_end_date = support_request["plan_end_date"]
|
||||
success_message = self.configure_temporary_courtesy_plan(temporary_plan_end_date)
|
||||
elif support_type == SupportType.update_billing_modality:
|
||||
assert support_request["billing_modality"] is not None
|
||||
assert support_request["billing_modality"] in VALID_BILLING_MODALITY_VALUES
|
||||
|
|
|
@ -7352,7 +7352,9 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
|
|||
self.assertEqual(success_message, "Fixed price offer deleted")
|
||||
result = self.client_get("/activity/remote/support", {"q": "example.com"})
|
||||
self.assert_not_in_success_response(["Next plan information:"], result)
|
||||
self.assert_in_success_response(["Fixed price", "Annual amount in dollars"], result)
|
||||
self.assert_in_success_response(
|
||||
["Configure fixed price plan", "Annual amount in dollars"], result
|
||||
)
|
||||
|
||||
@responses.activate
|
||||
@mock_stripe()
|
||||
|
|
|
@ -503,6 +503,50 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
|
|||
["Cannot update current plan for realm-name-5 to end on 2020-01-01."], result
|
||||
)
|
||||
|
||||
def test_configure_temporary_courtesy_plan(self) -> None:
|
||||
iago = self.example_user("iago")
|
||||
self.login_user(iago)
|
||||
remote_realm = RemoteRealm.objects.get(name="realm-name-4")
|
||||
# Cannot configure courtesy plan to end in the past.
|
||||
result = self.client_post(
|
||||
"/activity/remote/support",
|
||||
{
|
||||
"remote_realm_id": f"{remote_realm.id}",
|
||||
"temporary_courtesy_plan": "2010-03-01",
|
||||
},
|
||||
)
|
||||
self.assert_in_success_response(
|
||||
["Cannot configure a courtesy plan for realm-name-4 to end on 2010-03-01."],
|
||||
result,
|
||||
)
|
||||
# Cannot configure courtesy plan if there is a current plan for billing entity.
|
||||
result = self.client_post(
|
||||
"/activity/remote/support",
|
||||
{
|
||||
"remote_realm_id": f"{remote_realm.id}",
|
||||
"temporary_courtesy_plan": "2050-03-01",
|
||||
},
|
||||
)
|
||||
self.assert_in_success_response(
|
||||
["Cannot configure a courtesy plan for realm-name-4 because of current plan."],
|
||||
result,
|
||||
)
|
||||
remote_realm = RemoteRealm.objects.get(name="realm-name-2")
|
||||
assert remote_realm.plan_type == RemoteRealm.PLAN_TYPE_SELF_MANAGED
|
||||
result = self.client_post(
|
||||
"/activity/remote/support",
|
||||
{
|
||||
"remote_realm_id": f"{remote_realm.id}",
|
||||
"temporary_courtesy_plan": "2050-03-01",
|
||||
},
|
||||
)
|
||||
self.assert_in_success_response(
|
||||
["Temporary courtesy plan for realm-name-2 configured to end on 2050-03-01."],
|
||||
result,
|
||||
)
|
||||
remote_realm.refresh_from_db()
|
||||
assert remote_realm.plan_type == RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY
|
||||
|
||||
def test_discount_support_actions_when_upgrade_scheduled(self) -> None:
|
||||
remote_realm = RemoteRealm.objects.get(name="realm-name-4")
|
||||
billing_session = RemoteRealmBillingSession(remote_realm=remote_realm)
|
||||
|
|
|
@ -651,6 +651,10 @@ def remote_servers_support(
|
|||
modify_plan: VALID_MODIFY_PLAN_METHODS | None = None,
|
||||
delete_fixed_price_next_plan: Json[bool] = False,
|
||||
remote_server_status: VALID_STATUS_VALUES | None = None,
|
||||
temporary_courtesy_plan: Annotated[
|
||||
str, AfterValidator(lambda x: check_date("temporary_courtesy_plan", x))
|
||||
]
|
||||
| None = None,
|
||||
) -> HttpResponse:
|
||||
context: dict[str, Any] = {}
|
||||
|
||||
|
@ -706,6 +710,11 @@ def remote_servers_support(
|
|||
fixed_price=fixed_price,
|
||||
sent_invoice_id=sent_invoice_id,
|
||||
)
|
||||
elif temporary_courtesy_plan is not None:
|
||||
support_view_request = SupportViewRequest(
|
||||
support_type=SupportType.configure_temporary_courtesy_plan,
|
||||
plan_end_date=temporary_courtesy_plan,
|
||||
)
|
||||
elif billing_modality is not None:
|
||||
support_view_request = SupportViewRequest(
|
||||
support_type=SupportType.update_billing_modality,
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<p class="support-section-header">⏱️ Schedule fixed price plan:</p>
|
||||
<p class="support-section-header">⏱️ Schedule plan:</p>
|
||||
<form method="POST" class="remote-form">
|
||||
<b>Fixed price</b><br />
|
||||
{% if not is_current_plan_billable %}
|
||||
<b>Configure fixed price plan:</b><br />
|
||||
{% if not plan_data.is_current_plan_billable %}
|
||||
<i>Enter Invoice ID only if the customer chose to pay by invoice.</i><br />
|
||||
{% endif %}
|
||||
{{ csrf_input }}
|
||||
<input type="hidden" name="{{ remote_type }}" value="{{ remote_id }}" />
|
||||
<input type="number" name="fixed_price" placeholder="Annual amount in dollars" required />
|
||||
{% if not is_current_plan_billable %}
|
||||
{% if not plan_data.is_current_plan_billable %}
|
||||
<input type="text" name="sent_invoice_id" placeholder="Sent invoice ID" />
|
||||
{% endif %}
|
||||
<button type="submit" class="support-submit-button">Schedule</button>
|
||||
</form>
|
||||
{% if not plan_data.current_plan %}
|
||||
<form method="POST" class="remote-form">
|
||||
<b>Configure temporary courtesy plan:</b><br />
|
||||
<i>Once created, the end date for the temporary courtesy plan can be extended.</i><br />
|
||||
{{ csrf_input }}
|
||||
<input type="hidden" name="{{ remote_type }}" value="{{ remote_id }}" />
|
||||
<input type="date" name="temporary_courtesy_plan" required />
|
||||
<button type="submit" class="support-submit-button">Create</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
{% elif not remote_server_deactivated %}
|
||||
<div class="next-plan-container">
|
||||
{% with %}
|
||||
{% set is_current_plan_billable = support_data[remote_realm.id].plan_data.is_current_plan_billable %}
|
||||
{% set plan_data = support_data[remote_realm.id].plan_data %}
|
||||
{% set remote_id = remote_realm.id %}
|
||||
{% set remote_type = "remote_realm_id" %}
|
||||
{% include 'corporate/support/next_plan_forms_support.html' %}
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
{% elif not remote_server.deactivated %}
|
||||
<div class="next-plan-container">
|
||||
{% with %}
|
||||
{% set is_current_plan_billable = remote_servers_support_data[remote_server.id].plan_data.is_current_plan_billable %}
|
||||
{% set plan_data = remote_servers_support_data[remote_server.id].plan_data %}
|
||||
{% set remote_id = remote_server.id %}
|
||||
{% set remote_type = "remote_server_id" %}
|
||||
{% include 'corporate/support/next_plan_forms_support.html' %}
|
||||
|
|
Loading…
Reference in New Issue