support: Add option to change billing method.

This commit is contained in:
Vishnu KS 2020-08-18 11:48:11 +00:00 committed by Tim Abbott
parent 367c792968
commit 510efbc1a8
6 changed files with 76 additions and 4 deletions

View File

@ -461,8 +461,9 @@ class TestSupportEndpoint(ZulipTestCase):
'<b>Billing schedule</b>: Annual',
'<b>Licenses</b>: 2/10 (Manual)',
'<b>Price per license</b>: $80.0',
'<b>Payment method</b>: Send invoice',
'<b>Next invoice date</b>: 02 January 2017',
'<option value="send_invoice" selected>',
'<option value="charge_automatically" >'
], result)
def check_preregistration_user_query_result(result: HttpResponse, email: str, invite: bool=False) -> None:
@ -572,6 +573,28 @@ class TestSupportEndpoint(ZulipTestCase):
check_realm_reactivation_link_query_result(result)
check_zulip_realm_query_result(result)
@mock.patch("analytics.views.update_billing_method_of_current_plan")
def test_change_billing_method(self, m: mock.Mock) -> None:
cordelia = self.example_user('cordelia')
self.login_user(cordelia)
result = self.client_post("/activity/support", {"realm_id": f"{cordelia.realm_id}", "plan_type": "2"})
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
iago = self.example_user("iago")
self.login_user(iago)
result = self.client_post("/activity/support", {"realm_id": f"{iago.realm_id}", "billing_method": "charge_automatically"})
m.assert_called_once_with(get_realm("zulip"), charge_automatically=True)
self.assert_in_success_response(["Billing method of Zulip Dev updated to charge automatically"], result)
m.reset_mock()
result = self.client_post("/activity/support", {"realm_id": f"{iago.realm_id}", "billing_method": "send_invoice"})
m.assert_called_once_with(get_realm("zulip"), charge_automatically=False)
self.assert_in_success_response(["Billing method of Zulip Dev updated to pay by invoice"], result)
def test_change_plan_type(self) -> None:
cordelia = self.example_user('cordelia')
self.login_user(cordelia)

View File

@ -82,6 +82,7 @@ if settings.BILLING_ENABLED:
get_discount_for_realm,
get_latest_seat_count,
make_end_of_cycle_updates_if_needed,
update_billing_method_of_current_plan,
update_sponsorship_status,
void_all_open_invoices,
)
@ -1174,6 +1175,14 @@ def support(request: HttpRequest) -> HttpResponse:
elif status == "deactivated":
do_deactivate_realm(realm, request.user)
context["message"] = f"{realm.name} deactivated."
elif request.POST.get("billing_method", None) is not None:
billing_method = request.POST.get("billing_method")
if billing_method == "send_invoice":
update_billing_method_of_current_plan(realm, charge_automatically=False)
context["message"] = f"Billing method of {realm.name} updated to pay by invoice."
elif billing_method == "charge_automatically":
update_billing_method_of_current_plan(realm, charge_automatically=True)
context["message"] = f"Billing method of {realm.name} updated to charge automatically."
elif request.POST.get("sponsorship_pending", None) is not None:
sponsorship_pending = request.POST.get("sponsorship_pending")
if sponsorship_pending == "true":

View File

@ -655,3 +655,9 @@ def void_all_open_invoices(realm: Realm) -> int:
)
voided_invoices_count += 1
return voided_invoices_count
def update_billing_method_of_current_plan(realm: Realm, charge_automatically: bool) -> None:
plan = get_current_plan_by_realm(realm)
if plan is not None:
plan.charge_automatically = charge_automatically
plan.save(update_fields=["charge_automatically"])

View File

@ -37,6 +37,7 @@ from corporate.lib.stripe import (
sign_string,
stripe_get_customer,
unsign_string,
update_billing_method_of_current_plan,
update_license_ledger_for_automanaged_plan,
update_license_ledger_if_needed,
update_or_create_stripe_customer,
@ -1846,6 +1847,23 @@ class StripeTest(StripeTestCase):
for invoice in invoices:
self.assertEqual(invoice.status, "void")
def test_update_billing_method_of_current_plan(self) -> None:
realm = get_realm("zulip")
customer = Customer.objects.create(realm=realm, stripe_customer_id='cus_12345')
plan = CustomerPlan.objects.create(customer=customer, status=CustomerPlan.ACTIVE,
billing_cycle_anchor=timezone_now(),
billing_schedule=CustomerPlan.ANNUAL,
tier=CustomerPlan.STANDARD)
self.assertEqual(plan.charge_automatically, False)
update_billing_method_of_current_plan(realm, True)
plan.refresh_from_db()
self.assertEqual(plan.charge_automatically, True)
update_billing_method_of_current_plan(realm, False)
plan.refresh_from_db()
self.assertEqual(plan.charge_automatically, False)
class RequiresBillingAccessTest(ZulipTestCase):
def setUp(self) -> None:
super().setUp()

View File

@ -94,18 +94,23 @@ tr.admin td:first-child {
top: -40px;
}
.downgrade-plan-form {
.billing-method-form {
position: relative;
top: -70px;
}
.downgrade-plan-form {
position: relative;
top: -115px;
}
.downgrade-plan-method-select {
width: auto;
}
.scrub-realm-form {
position: relative;
top: -40px;
top: -70px;
}
.support-search-button {

View File

@ -72,10 +72,21 @@
{% else %}
<b>Fixed price</b>: ${{ realm.current_plan.fixed_price/100 }}<br>
{% endif %}
<b>Payment method</b>: {% if realm.current_plan.charge_automatically %}Card{% else %}Send invoice{% endif %}<br>
<b>Next invoice date</b>: {{ realm.current_plan.next_invoice_date.strftime('%d %B %Y') }}<br>
</div>
<form method="POST" class="billing-method-form">
<br>
<b>Billing method</b><br>
{{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" />
<select name="billing_method" class="billing-method-select" required>
<option value="charge_automatically" {% if realm.current_plan.charge_automatically %}selected{% endif %}>Charge automatically</option>
<option value="send_invoice" {% if not realm.current_plan.charge_automatically %}selected{% endif %}>Pay by invoice</option>
</select>
<button type="submit" class="button rounded small support-submit-button">Update</button>
</form>
<form method="POST" class="downgrade-plan-form">
<br>
<b>Downgrade plan</b><br>