billing: Support downgrading plan from /billing page.

This commit is contained in:
Vishnu KS 2020-04-24 21:08:13 +05:30 committed by Tim Abbott
parent f74e2b69f0
commit 8fb1f2af58
6 changed files with 90 additions and 2 deletions

View File

@ -185,6 +185,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
CustomerPlan.PLUS: 'Zulip Plus', CustomerPlan.PLUS: 'Zulip Plus',
}[plan.tier] }[plan.tier]
free_trial = plan.status == CustomerPlan.FREE_TRIAL free_trial = plan.status == CustomerPlan.FREE_TRIAL
downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
licenses = last_ledger_entry.licenses licenses = last_ledger_entry.licenses
licenses_used = get_latest_seat_count(user.realm) licenses_used = get_latest_seat_count(user.realm)
# Should do this in javascript, using the user's timezone # Should do this in javascript, using the user's timezone
@ -201,6 +202,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
'plan_name': plan_name, 'plan_name': plan_name,
'has_active_plan': True, 'has_active_plan': True,
'free_trial': free_trial, 'free_trial': free_trial,
'downgrade_at_end_of_cycle': downgrade_at_end_of_cycle,
'licenses': licenses, 'licenses': licenses,
'licenses_used': licenses_used, 'licenses_used': licenses_used,
'renewal_date': renewal_date, 'renewal_date': renewal_date,
@ -209,6 +211,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
'charge_automatically': charge_automatically, 'charge_automatically': charge_automatically,
'publishable_key': STRIPE_PUBLISHABLE_KEY, 'publishable_key': STRIPE_PUBLISHABLE_KEY,
'stripe_email': stripe_customer.email, 'stripe_email': stripe_customer.email,
'CustomerPlan': CustomerPlan,
}) })
return render(request, 'corporate/billing.html', context=context) return render(request, 'corporate/billing.html', context=context)

View File

@ -20,16 +20,22 @@ set_global('$', global.make_zjquery());
run_test("initialize", () => { run_test("initialize", () => {
let token_func; let token_func;
let set_tab_called = false;
helpers.set_tab = (page_name) => { helpers.set_tab = (page_name) => {
assert.equal(page_name, "billing"); assert.equal(page_name, "billing");
set_tab_called = true;
}; };
let create_ajax_request_called = false;
helpers.create_ajax_request = (url, form_name, stripe_token) => { helpers.create_ajax_request = (url, form_name, stripe_token) => {
assert.equal(url, "/json/billing/sources/change"); assert.equal(url, "/json/billing/sources/change");
assert.equal(form_name, "cardchange"); assert.equal(form_name, "cardchange");
assert.equal(stripe_token, "stripe_token"); assert.equal(stripe_token, "stripe_token");
create_ajax_request_called = true;
}; };
let open_func_called = false;
const open_func = (config_opts) => { const open_func = (config_opts) => {
assert.equal(config_opts.name, "Zulip"); assert.equal(config_opts.name, "Zulip");
assert.equal(config_opts.zipCode, true); assert.equal(config_opts.zipCode, true);
@ -40,13 +46,16 @@ run_test("initialize", () => {
assert.equal(config_opts.email, "{{stripe_email}}"); assert.equal(config_opts.email, "{{stripe_email}}");
token_func("stripe_token"); token_func("stripe_token");
open_func_called = true;
}; };
let stripe_checkout_configure_called = false;
StripeCheckout.configure = (config_opts) => { StripeCheckout.configure = (config_opts) => {
assert.equal(config_opts.image, '/static/images/logo/zulip-icon-128x128.png'); assert.equal(config_opts.image, '/static/images/logo/zulip-icon-128x128.png');
assert.equal(config_opts.locale, 'auto'); assert.equal(config_opts.locale, 'auto');
assert.equal(config_opts.key, '{{publishable_key}}'); assert.equal(config_opts.key, '{{publishable_key}}');
token_func = config_opts.token; token_func = config_opts.token;
stripe_checkout_configure_called = true;
return { return {
open: open_func, open: open_func,
@ -59,11 +68,28 @@ run_test("initialize", () => {
jquery_init(); jquery_init();
assert(set_tab_called);
assert(stripe_checkout_configure_called);
const e = { const e = {
preventDefault: noop, preventDefault: noop,
}; };
const click_handler = $('#update-card-button').get_on_handler('click'); const update_card_click_handler = $('#update-card-button').get_on_handler('click');
click_handler(e); update_card_click_handler(e);
assert(create_ajax_request_called);
assert(open_func_called);
create_ajax_request_called = false;
helpers.create_ajax_request = (url, form_name, stripe_token, numeric_inputs) => {
assert.equal(url, "/json/billing/plan/change");
assert.equal(form_name, "planchange");
assert.equal(stripe_token, undefined);
assert.deepEqual(numeric_inputs, ["status"]);
create_ajax_request_called = true;
};
const change_plan_status_click_handler = $('#change-plan-status').get_on_handler('click');
change_plan_status_click_handler(e);
assert(create_ajax_request_called);
}); });
run_test("billing_template", () => { run_test("billing_template", () => {

View File

@ -24,6 +24,11 @@ exports.initialize = function () {
}); });
e.preventDefault(); e.preventDefault();
}); });
$("#change-plan-status").on('click', function (e) {
helpers.create_ajax_request("/json/billing/plan/change", "planchange", undefined, ["status"]);
e.preventDefault();
});
}; };
window.billing = exports; window.billing = exports;

View File

@ -177,6 +177,7 @@
display: none; display: none;
} }
#planchange-loading,
#cardchange-loading, #cardchange-loading,
#invoice-loading, #invoice-loading,
#autopay-loading { #autopay-loading {
@ -186,6 +187,7 @@
} }
#planchange-success,
#cardchange-success, #cardchange-success,
#invoice-success, #invoice-success,
#autopay-success { #autopay-success {
@ -193,6 +195,7 @@
display: none; display: none;
} }
#planchange-error,
#cardchange-error, #cardchange-error,
#invoice-error, #invoice-error,
#autopay-error { #autopay-error {
@ -221,12 +224,14 @@
stroke: hsl(0, 0%, 100%); stroke: hsl(0, 0%, 100%);
} }
#planchange_loading_indicator,
#cardchange_loading_indicator, #cardchange_loading_indicator,
#invoice_loading_indicator, #invoice_loading_indicator,
#autopay_loading_indicator { #autopay_loading_indicator {
margin: 10px auto; margin: 10px auto;
} }
#planchange_loading_indicator_box_container,
#cardchange_loading_indicator_box_container, #cardchange_loading_indicator_box_container,
#invoice_loading_indicator_box_container, #invoice_loading_indicator_box_container,
#autopay_loading_indicator_box_container { #autopay_loading_indicator_box_container {
@ -234,6 +239,7 @@
left: 50%; left: 50%;
} }
#planchange_loading_indicator_box,
#cardchange_loading_indicator_box, #cardchange_loading_indicator_box,
#invoice_loading_indicator_box, #invoice_loading_indicator_box,
#autopay_loading_indicator_box { #autopay_loading_indicator_box {
@ -244,6 +250,7 @@
border-radius: 6px; border-radius: 6px;
} }
#planchange_loading_indicator .loading_indicator_text,
#cardchange_loading_indicator .loading_indicator_text, #cardchange_loading_indicator .loading_indicator_text,
#invoice_loading_indicator .loading_indicator_text, #invoice_loading_indicator .loading_indicator_text,
#autopay_loading_indicator .loading_indicator_text { #autopay_loading_indicator .loading_indicator_text {

View File

@ -23,6 +23,7 @@
<ul class="nav nav-tabs" id="billing-tabs"> <ul class="nav nav-tabs" id="billing-tabs">
<li class="active"><a data-toggle="tab" href="#overview">Overview</a></li> <li class="active"><a data-toggle="tab" href="#overview">Overview</a></li>
<li><a data-toggle="tab" href="#payment-method">Payment Method</a></li> <li><a data-toggle="tab" href="#payment-method">Payment Method</a></li>
<li><a data-toggle="tab" href="#settings">Settings</a></li>
</ul> </ul>
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}"> <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
@ -39,6 +40,8 @@
{% if free_trial %} {% if free_trial %}
Your plan will be upgraded to <strong>{{ plan_name }}</strong> on <strong>{{ renewal_date }}</strong> for Your plan will be upgraded to <strong>{{ plan_name }}</strong> on <strong>{{ renewal_date }}</strong> for
<strong>${{ renewal_amount }}</strong>. <strong>${{ renewal_amount }}</strong>.
{% elif downgrade_at_end_of_cycle %}
Your plan will be downgraded to <strong>Zulip Limited</strong> on <strong>{{ renewal_date }}</strong>.
{% else %} {% else %}
Your plan will renew on <strong>{{ renewal_date }}</strong> for Your plan will renew on <strong>{{ renewal_date }}</strong> for
<strong>${{ renewal_amount }}</strong>. <strong>${{ renewal_amount }}</strong>.
@ -75,6 +78,49 @@
Card updated. The page will now reload. Card updated. The page will now reload.
</div> </div>
</div> </div>
<div class="tab-pane" id="settings">
<div id="planchange-error" class="alert alert-danger"></div>
<div id="planchange-input-section">
<form id="planchange-form">
<h3>Downgrade</h3>
{% if free_trial %}
<p>
End Free Trial and downgrade plan to <strong>Zulip Limited</strong>.
</p>
<input name="status" type="hidden" value="{{ CustomerPlan.ENDED }}" />
<button class="btn-danger" id="change-plan-status">End free trial</button>
{% else %}
{% if downgrade_at_end_of_cycle %}
<p>
You plan is scheduled for downgrade on <strong>{{ renewal_date }}</strong>.
</p>
<input name="status" type="hidden" value="{{ CustomerPlan.ACTIVE }}" />
<button class="btn-info" id="change-plan-status">Cancel downgrade</button>
{% else %}
<p>
Downgrade to <strong>Zulip Limited</strong> at the end of current billing period.
</p>
<input name="status" type="hidden" value="{{ CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE }}" />
<button class="btn-danger" id="change-plan-status">Start downgrade process</button>
{% endif %}
{% endif %}
</form>
</div>
<div id="planchange-loading">
<div class="zulip-loading-logo">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 40 40" version="1.1">
<g transform="translate(-297.14285,-466.64792)">
<circle cx="317.14285" cy="486.64792" r="19.030317" style="stroke-width:1.93936479;"/>
<path d="m309.24286 477.14791 14.2 0 1.6 3.9-11.2 11.9 9.6 0 1.6 3.2-14.2 0-1.6-3.9 11.2-11.9-9.6 0z"/>
</g>
</svg>
</div>
<div id="planchange_loading_indicator"></div>
</div>
<div id="planchange-success" class="alert alert-success">
Plan updated. The page will now reload.
</div>
</div>
<div class="tab-pane" id="loading"> <div class="tab-pane" id="loading">
</div> </div>
</div> </div>

View File

@ -69,6 +69,7 @@ def check_html_templates(templates: Iterable[str], all_dups: bool, fix: bool) ->
'register', 'register',
'footer', 'footer',
'charged_amount', 'charged_amount',
'change-plan-status',
# Temporary while we have searchbox forked # Temporary while we have searchbox forked
'search_exit', 'search_exit',
'search_query', 'search_query',