Browse Source

Added "Supporter" billing plan.

pull/358/head
Pēteris Caune 5 years ago
parent
commit
f1880657fd
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 220 additions and 84 deletions
  1. +4
    -0
      hc/payments/models.py
  2. +29
    -1
      hc/payments/tests/test_update_subscription.py
  3. +11
    -4
      hc/payments/views.py
  4. +4
    -4
      static/css/billing.css
  5. +56
    -20
      static/css/pricing.css
  6. +18
    -5
      static/js/billing.js
  7. +6
    -4
      static/js/pricing.js
  8. +23
    -7
      templates/accounts/billing.html
  9. +69
    -39
      templates/payments/pricing.html

+ 4
- 0
hc/payments/models.py View File

@ -130,6 +130,10 @@ class Subscription(models.Model):
self.plan_name = "Business Plus ($80 / month)" self.plan_name = "Business Plus ($80 / month)"
elif plan_id == "Y768": elif plan_id == "Y768":
self.plan_name = "Business Plus ($768 / year)" self.plan_name = "Business Plus ($768 / year)"
elif plan_id == "S5":
self.plan_name = "Supporter ($5 / month)"
elif plan_id == "S48":
self.plan_name = "Supporter ($48 / year)"
self.save() self.save()


+ 29
- 1
hc/payments/tests/test_update_subscription.py View File

@ -48,6 +48,34 @@ class UpdateSubscriptionTestCase(BaseTestCase):
self.assertTrue(mock.Subscription.create.called) self.assertTrue(mock.Subscription.create.called)
@patch("hc.payments.models.braintree")
def test_supporter_works(self, mock):
self._setup_mock(mock)
self.profile.sms_limit = 0
self.profile.sms_sent = 1
self.profile.save()
r = self.run_update("S5")
self.assertRedirects(r, "/accounts/profile/billing/")
# Subscription should be filled out:
sub = Subscription.objects.get(user=self.alice)
self.assertEqual(sub.subscription_id, "t-sub-id")
self.assertEqual(sub.plan_id, "S5")
self.assertEqual(sub.plan_name, "Supporter ($5 / month)")
# User's profile should have adjusted limits
self.profile.refresh_from_db()
self.assertEqual(self.profile.ping_log_limit, 1000)
self.assertEqual(self.profile.check_limit, 20)
self.assertEqual(self.profile.team_limit, 2)
self.assertEqual(self.profile.sms_limit, 5)
self.assertEqual(self.profile.sms_sent, 0)
# braintree.Subscription.cancel should have not been called
assert not mock.Subscription.cancel.called
@patch("hc.payments.models.braintree") @patch("hc.payments.models.braintree")
def test_yearly_works(self, mock): def test_yearly_works(self, mock):
self._setup_mock(mock) self._setup_mock(mock)
@ -133,7 +161,7 @@ class UpdateSubscriptionTestCase(BaseTestCase):
self.assertEqual(self.profile.ping_log_limit, 100) self.assertEqual(self.profile.ping_log_limit, 100)
self.assertEqual(self.profile.check_limit, 20) self.assertEqual(self.profile.check_limit, 20)
self.assertEqual(self.profile.team_limit, 2) self.assertEqual(self.profile.team_limit, 2)
self.assertEqual(self.profile.sms_limit, 0)
self.assertEqual(self.profile.sms_limit, 5)
self.assertTrue(mock.Subscription.cancel.called) self.assertTrue(mock.Subscription.cancel.called)


+ 11
- 4
hc/payments/views.py View File

@ -110,7 +110,7 @@ def update(request):
request.session["payment_method_status"] = "success" request.session["payment_method_status"] = "success"
return redirect("hc-billing") return redirect("hc-billing")
if plan_id not in ("", "P20", "P80", "Y192", "Y768"):
if plan_id not in ("", "P20", "P80", "Y192", "Y768", "S5", "S48"):
return HttpResponseBadRequest() return HttpResponseBadRequest()
# Cancel the previous plan and reset limits: # Cancel the previous plan and reset limits:
@ -120,7 +120,7 @@ def update(request):
profile.ping_log_limit = 100 profile.ping_log_limit = 100
profile.check_limit = 20 profile.check_limit = 20
profile.team_limit = 2 profile.team_limit = 2
profile.sms_limit = 0
profile.sms_limit = 5
profile.save() profile.save()
if plan_id == "": if plan_id == "":
@ -133,17 +133,24 @@ def update(request):
# Update user's profile # Update user's profile
profile = request.user.profile profile = request.user.profile
if plan_id in ("P20", "Y192"):
if plan_id in ("S5", "S48"):
profile.check_limit = 20
profile.team_limit = 2
profile.ping_log_limit = 1000 profile.ping_log_limit = 1000
profile.sms_limit = 5
profile.sms_sent = 0
profile.save()
elif plan_id in ("P20", "Y192"):
profile.check_limit = 100 profile.check_limit = 100
profile.team_limit = 9 profile.team_limit = 9
profile.ping_log_limit = 1000
profile.sms_limit = 50 profile.sms_limit = 50
profile.sms_sent = 0 profile.sms_sent = 0
profile.save() profile.save()
elif plan_id in ("P80", "Y768"): elif plan_id in ("P80", "Y768"):
profile.ping_log_limit = 1000
profile.check_limit = 1000 profile.check_limit = 1000
profile.team_limit = 500 profile.team_limit = 500
profile.ping_log_limit = 1000
profile.sms_limit = 500 profile.sms_limit = 500
profile.sms_sent = 0 profile.sms_sent = 0
profile.save() profile.save()


+ 4
- 4
static/css/billing.css View File

@ -13,7 +13,7 @@
@media (min-width: 992px) { @media (min-width: 992px) {
#change-billing-plan-modal .modal-dialog, #payment-method-modal .modal-dialog, #please-wait-modal .modal-dialog { #change-billing-plan-modal .modal-dialog, #payment-method-modal .modal-dialog, #please-wait-modal .modal-dialog {
width: 850px;
width: 900px;
} }
} }
@ -75,7 +75,7 @@
#change-billing-plan-modal .plan li { #change-billing-plan-modal .plan li {
margin: 0; margin: 0;
padding: 0;
padding: 1px 0;
} }
#change-billing-plan-modal .text-warning { #change-billing-plan-modal .text-warning {
@ -89,5 +89,5 @@
#please-wait-modal .modal-body { #please-wait-modal .modal-body {
text-align: center; text-align: center;
padding: 100px; padding: 100px;
font-size: 18px;
}
font-size: 18px;
}

+ 56
- 20
static/css/pricing.css View File

@ -1,24 +1,64 @@
.panel-pricing .list-group-item {
color: #777777;
#subscription-status form {
display: inline-block;
} }
.panel-pricing .list-group-item:last-child {
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
#pricing-table {
} }
.panel-pricing .list-group-item:first-child {
border-top-right-radius: 0px;
border-top-left-radius: 0px;
.panel.plan h1 {
font-size: 24px;
font-weight: bold;
display: inline-block;
line-height: 0.9;
text-shadow: 0px 1px white, -1px 1px white, 1px 1px white, -1px 0px white;
} }
.panel-pricing .panel-body {
font-size: 40px;
color: #777777;
padding: 20px;
margin: 0px;
background-color: #f0f0f0;
.hobbyist h1 {
color: #0a220c;
border-bottom: 3px solid #A5D6A7;
} }
#subscription-status form {
display: inline-block;
.supporter h1 {
color: #1d190f;
border-bottom: 3px solid #FFE082;
}
.business h1 {
color: #141c22;
border-bottom: 3px solid #90CAF9;
}
.business-plus h1 {
color: #221f1e;
border-bottom: 3px solid #BCAAA4;
}
.mo {
font-size: 16px;
}
.plan .btn {
font-weight: bold;
color: #1ea65a;
border-color: #1ea65a;
background: #FFF;
transition: all 0.2s ease;
border-radius: 5px;
padding: 8px 20px;
}
.plan .btn:hover {
color: #fff;
background: #22bc66;
border-color: #22bc66;
}
.plan .list-group-item:last-child {
border-bottom: 0;
} }
#payment-method-modal .modal-header { #payment-method-modal .modal-header {
@ -42,10 +82,6 @@
font-weight: bold; font-weight: bold;
} }
.mo {
font-size: 18px;
color: #888;
}
#period-controls { #period-controls {
text-align: center; text-align: center;


+ 18
- 5
static/js/billing.js View File

@ -32,9 +32,9 @@ $(function () {
} }
$("#payment-form-submit").prop("disabled", true); $("#payment-form-submit").prop("disabled", true);
$("#payment-method-modal").modal("show");
$("#payment-method-modal").modal("show");
getToken(function(token) {
getToken(function(token) {
braintree.dropin.create({ braintree.dropin.create({
authorization: token, authorization: token,
container: "#dropin", container: "#dropin",
@ -48,7 +48,7 @@ $(function () {
instance.requestPaymentMethod(function (err, payload) { instance.requestPaymentMethod(function (err, payload) {
$("#payment-method-modal").modal("hide"); $("#payment-method-modal").modal("hide");
$("#please-wait-modal").modal("show"); $("#please-wait-modal").modal("show");
$("#nonce").val(payload.nonce); $("#nonce").val(payload.nonce);
$("#update-subscription-form").submit(); $("#update-subscription-form").submit();
}); });
@ -76,7 +76,7 @@ $(function () {
}); });
$("#update-payment-method").click(function() { $("#update-payment-method").click(function() {
showPaymentMethodForm($("#old-plan-id").val());
showPaymentMethodForm($("#old-plan-id").val());
}); });
$("#billing-history").load("/accounts/profile/billing/history/"); $("#billing-history").load("/accounts/profile/billing/history/");
@ -98,6 +98,12 @@ $(function () {
updateChangePlanForm(); updateChangePlanForm();
}); });
$("#plan-supporter").click(function() {
$(".plan").removeClass("selected");
$("#plan-supporter").addClass("selected");
updateChangePlanForm();
});
$("#plan-business").click(function() { $("#plan-business").click(function() {
$(".plan").removeClass("selected"); $(".plan").removeClass("selected");
$("#plan-business").addClass("selected"); $("#plan-business").addClass("selected");
@ -116,6 +122,7 @@ $(function () {
// "Monthly" is selected: dispplay monthly prices // "Monthly" is selected: dispplay monthly prices
if ($("#billing-monthly").is(":checked")) { if ($("#billing-monthly").is(":checked")) {
var period = "monthly"; var period = "monthly";
$("#supporter-price").text("$5");
$("#business-price").text("$20"); $("#business-price").text("$20");
$("#business-plus-price").text("$80"); $("#business-plus-price").text("$80");
} }
@ -123,6 +130,7 @@ $(function () {
// "Annual" is selected: dispplay annual prices // "Annual" is selected: dispplay annual prices
if ($("#billing-annual").is(":checked")) { if ($("#billing-annual").is(":checked")) {
var period = "annual"; var period = "annual";
$("#supporter-price").text("$4");
$("#business-price").text("$16"); $("#business-price").text("$16");
$("#business-plus-price").text("$64"); $("#business-plus-price").text("$64");
} }
@ -132,6 +140,11 @@ $(function () {
planId = ""; planId = "";
} }
// "Supporter" is selected, set planId
if ($("#plan-supporter").hasClass("selected")) {
planId = period == "monthly" ? "S5" : "S48";
}
// "Business" is selected, set planId // "Business" is selected, set planId
if ($("#plan-business").hasClass("selected")) { if ($("#plan-business").hasClass("selected")) {
planId = period == "monthly" ? "P20" : "Y192"; planId = period == "monthly" ? "P20" : "Y192";
@ -141,7 +154,7 @@ $(function () {
if ($("#plan-business-plus").hasClass("selected")) { if ($("#plan-business-plus").hasClass("selected")) {
planId = period == "monthly" ? "P80" : "Y768"; planId = period == "monthly" ? "P80" : "Y768";
} }
if (planId == $("#old-plan-id").val()) { if (planId == $("#old-plan-id").val()) {
$("#change-plan-btn") $("#change-plan-btn")
.attr("disabled", "disabled") .attr("disabled", "disabled")


+ 6
- 4
static/js/pricing.js View File

@ -1,13 +1,15 @@
$(function () { $(function () {
$("#period-controls :input").change(function() { $("#period-controls :input").change(function() {
if (this.value == "monthly") { if (this.value == "monthly") {
$("#s-price").text("$20");
$("#p-price").text("$80");
$("#supporter-price").text("$5");
$("#business-price").text("$20");
$("#business-plus-price").text("$80");
} }
if (this.value == "annual") { if (this.value == "annual") {
$("#s-price").text("$16");
$("#p-price").text("$64");
$("#supporter-price").text("$4");
$("#business-price").text("$16");
$("#business-plus-price").text("$64");
} }
}); });
}); });

+ 23
- 7
templates/accounts/billing.html View File

@ -176,27 +176,43 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-sm-4">
<div class="col-sm-6 col-md-3">
<div id="plan-hobbyist" class="panel plan {% if sub.plan_id == "" %}selected{% endif %}"> <div id="plan-hobbyist" class="panel plan {% if sub.plan_id == "" %}selected{% endif %}">
<div class="marker">Selected Plan</div> <div class="marker">Selected Plan</div>
<h2>Hobbyist</h2> <h2>Hobbyist</h2>
<ul> <ul>
<li>Checks: 20</li> <li>Checks: 20</li>
<li>Team members: 3</li>
<li>Team size: 3</li>
<li>Log entries: 100</li> <li>Log entries: 100</li>
</ul> </ul>
<h3>Free</h3> <h3>Free</h3>
</div> </div>
</div> </div>
<div class="col-sm-4">
<div class="col-sm-6 col-md-3">
<div id="plan-supporter" class="panel plan {% if sub.plan_id == "S5" or sub.plan_id == "S48" %}selected{% endif %}">
<div class="marker">Selected Plan</div>
<h2>Supporter</h2>
<ul>
<li>Checks: 20</li>
<li>Team size: 3</li>
<li>Log entries: 1000</li>
</ul>
<h3>
<span id="supporter-price"></span>
<small>/ month</small>
</h3>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div id="plan-business" class="panel plan {% if sub.plan_id == "P20" or sub.plan_id == "Y192" %}selected{% endif %}"> <div id="plan-business" class="panel plan {% if sub.plan_id == "P20" or sub.plan_id == "Y192" %}selected{% endif %}">
<div class="marker">Selected Plan</div> <div class="marker">Selected Plan</div>
<h2>Business</h2> <h2>Business</h2>
<ul> <ul>
<li>Checks: 100</li> <li>Checks: 100</li>
<li>Team members: 10</li>
<li>Team size: 10</li>
<li>Log entries: 1000</li> <li>Log entries: 1000</li>
</ul> </ul>
<h3> <h3>
@ -205,14 +221,14 @@
</h3> </h3>
</div> </div>
</div> </div>
<div class="col-sm-4">
<div class="col-sm-6 col-md-3">
<div id="plan-business-plus" class="panel plan {% if sub.plan_id == "P80" or sub.plan_id == "Y768" %}selected{% endif %}"> <div id="plan-business-plus" class="panel plan {% if sub.plan_id == "P80" or sub.plan_id == "Y768" %}selected{% endif %}">
<div class="marker">Selected Plan</div> <div class="marker">Selected Plan</div>
<h2>Business Plus</h2> <h2>Business Plus</h2>
<ul> <ul>
<li>Checks: 1000</li> <li>Checks: 1000</li>
<li>Team members: Unlimited</li>
<li>Team size: Unlim.</li>
<li>Log entries: 1000</li> <li>Log entries: 1000</li>
</ul> </ul>
<h3> <h3>
@ -508,7 +524,7 @@
</div> </div>
<form id="update-subscription-form" method="post" action="{% url 'hc-update-subscription' %}"> <form id="update-subscription-form" method="post" action="{% url 'hc-update-subscription' %}">
{% csrf_token %}
{% csrf_token %}
<input id="nonce" type="hidden" name="nonce" /> <input id="nonce" type="hidden" name="nonce" />
<input type="hidden" id="old-plan-id" value="{{ sub.plan_id }}"> <input type="hidden" id="old-plan-id" value="{{ sub.plan_id }}">
<input id="plan-id" type="hidden" name="plan_id" /> <input id="plan-id" type="hidden" name="plan_id" />


+ 69
- 39
templates/payments/pricing.html View File

@ -69,90 +69,120 @@
</div> </div>
</div> </div>
<!-- Free -->
<div class="col-sm-4 text-center">
<div class="panel panel-default">
<div class="panel-body text-center free">
<!-- Hobbyist -->
<div class="col-sm-3 text-center">
<div class="panel panel-default plan hobbyist">
<div class="panel-body text-center">
<h1>Hobbyist</h1> <h1>Hobbyist</h1>
<h2>$0<span class="mo">/mo</span></h2>
<h2>$0 <span class="mo">/ month</span></h2>
</div> </div>
<ul class="list-group text-center"> <ul class="list-group text-center">
<li class="list-group-item"><i class="fa fa-check"></i> 20 Checks</li>
<li class="list-group-item">3 Team Members</li>
<li class="list-group-item">100 Log Entries per Check</li>
<li class="list-group-item">API Access</li>
<li class="list-group-item">5 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} Alerts per Month</li>
<li class="list-group-item"><i class="fa fa-check"></i> 20 checks</li>
<li class="list-group-item">3 team members</li>
<li class="list-group-item">100 log entries per check</li>
<li class="list-group-item">API access</li>
<li class="list-group-item">5 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} credits</li>
<li class="list-group-item">&nbsp;</li> <li class="list-group-item">&nbsp;</li>
</ul> </ul>
{% if not request.user.is_authenticated %}
<div class="panel-footer">
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn">Sign Up</a>
</div>
{% endif %}
</div>
</div>
<!-- Supporter -->
<div class="col-sm-3 text-center">
<div class="panel panel-default plan supporter">
<div class="panel-body text-center free">
<h1>Supporter</h1>
<h2>
<span id="supporter-price">$5</span>
<span class="mo">/ month</span>
</h2>
</div>
<ul class="list-group text-center">
<li class="list-group-item"><i class="fa fa-check"></i> 20 checks</li>
<li class="list-group-item">3 team members</li>
<li class="list-group-item">1000 log entries per check</li>
<li class="list-group-item">API access</li>
<li class="list-group-item">5 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} credits</li>
<li class="list-group-item">Email support</li>
</ul>
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
<div class="panel-footer"> <div class="panel-footer">
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn btn-lg btn-success">Get Started</a>
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn">
Sign Up
</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- /item -->
<!-- P5 -->
<div class="col-sm-4 text-center">
<div class="panel panel-default">
<!-- Business -->
<div class="col-sm-3 text-center">
<div class="panel panel-default plan business">
<div class="panel-body text-center"> <div class="panel-body text-center">
<h1>Business</h1> <h1>Business</h1>
<h2> <h2>
<span id="s-price">$20</span><span class="mo">/mo</span>
<span id="business-price">$20</span>
<span class="mo">/ month</span>
</h2> </h2>
</div> </div>
<ul class="list-group text-center"> <ul class="list-group text-center">
<li class="list-group-item">100 Checks</li>
<li class="list-group-item">10 Team Members</li>
<li class="list-group-item">1000 Log Entries per Check</li>
<li class="list-group-item">API Access</li>
<li class="list-group-item">50 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} Alerts per Month</li>
<li class="list-group-item">Email Support</li>
<li class="list-group-item">100 checks</li>
<li class="list-group-item">10 team members</li>
<li class="list-group-item">1000 log entries per check</li>
<li class="list-group-item">API access</li>
<li class="list-group-item">50 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} credits</li>
<li class="list-group-item">Email support</li>
</ul> </ul>
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
<div class="panel-footer"> <div class="panel-footer">
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn btn-lg btn-primary">
Get Started
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn">
Sign Up
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- /item -->
<!-- P50 -->
<div class="col-sm-4 text-center">
<div class="panel panel-default">
<!-- Business Plus -->
<div class="col-sm-3 text-center">
<div class="panel panel-default plan business-plus">
<div class="panel-body text-center"> <div class="panel-body text-center">
<h1>Business Plus</h1> <h1>Business Plus</h1>
<h2> <h2>
<span id="p-price">$80</span><span class="mo">/mo</span>
<span id="business-plus-price">$80</span>
<span class="mo">/ month</span>
</h2> </h2>
</div> </div>
<ul class="list-group text-center"> <ul class="list-group text-center">
<li class="list-group-item">1000 Checks</li>
<li class="list-group-item">Unlimited Team Members</li>
<li class="list-group-item">1000 Log Entries per Check</li>
<li class="list-group-item">API Access</li>
<li class="list-group-item">500 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} Alerts per Month</li>
<li class="list-group-item">Priority Email Support</li>
<li class="list-group-item">1000 checks</li>
<li class="list-group-item">Unlimited team members</li>
<li class="list-group-item">1000 log entries per check</li>
<li class="list-group-item">API access</li>
<li class="list-group-item">500 SMS {% if enable_whatsapp %}&amp; WhatsApp{% endif %} credits</li>
<li class="list-group-item">Priority email support</li>
</ul> </ul>
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
<div class="panel-footer"> <div class="panel-footer">
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn btn-lg btn-primary">
Get Started
<a href="#" data-toggle="modal" data-target="#signup-modal" class="btn">
Sign Up
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- /item -->
</div> </div>
</section> </section>
<!-- /Plans -->
<section id="faq"> <section id="faq">
<div class="container"> <div class="container">


Loading…
Cancel
Save