forked from GithubBackups/healthchecks
Annual subscriptions, updated Braintree Drop-in integration
This commit is contained in:
parent
363f219671
commit
cf5cbfaa3c
@ -31,9 +31,21 @@ class Subscription(models.Model):
|
||||
return 5
|
||||
elif self.plan_id == "P50":
|
||||
return 50
|
||||
elif self.plan_id == "Y48":
|
||||
return 48
|
||||
elif self.plan_id == "Y480":
|
||||
return 480
|
||||
|
||||
return 0
|
||||
|
||||
def period(self):
|
||||
if self.plan_id.startswith("P"):
|
||||
return "month"
|
||||
elif self.plan_id.startswith("Y"):
|
||||
return "year"
|
||||
|
||||
raise NotImplementedError("Unexpected plan: %s" % self.plan_id)
|
||||
|
||||
def _get_braintree_payment_method(self):
|
||||
if not hasattr(self, "_pm"):
|
||||
self._pm = braintree.PaymentMethod.find(self.payment_method_token)
|
||||
|
@ -1,6 +1,5 @@
|
||||
from mock import patch
|
||||
|
||||
from hc.accounts.models import Profile
|
||||
from hc.payments.models import Subscription
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
@ -53,6 +52,35 @@ class CreatePlanTestCase(BaseTestCase):
|
||||
# braintree.Subscription.cancel should have not been called
|
||||
assert not mock.Subscription.cancel.called
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
def test_yearly_works(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
self.profile.sms_limit = 0
|
||||
self.profile.sms_sent = 1
|
||||
self.profile.save()
|
||||
|
||||
r = self.run_create_plan("Y48")
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
|
||||
# Subscription should be filled out:
|
||||
sub = Subscription.objects.get(user=self.alice)
|
||||
self.assertEqual(sub.customer_id, "test-customer-id")
|
||||
self.assertEqual(sub.payment_method_token, "t-token")
|
||||
self.assertEqual(sub.subscription_id, "t-sub-id")
|
||||
self.assertEqual(sub.plan_id, "Y48")
|
||||
|
||||
# User's profile should have a higher limits
|
||||
self.profile.refresh_from_db()
|
||||
self.assertEqual(self.profile.ping_log_limit, 1000)
|
||||
self.assertEqual(self.profile.check_limit, 500)
|
||||
self.assertEqual(self.profile.team_limit, 9)
|
||||
self.assertEqual(self.profile.sms_limit, 50)
|
||||
self.assertEqual(self.profile.sms_sent, 0)
|
||||
|
||||
# braintree.Subscription.cancel should have not been called
|
||||
assert not mock.Subscription.cancel.called
|
||||
|
||||
def test_bad_plan_id(self):
|
||||
r = self.run_create_plan(plan_id="this-is-wrong")
|
||||
self.assertEqual(r.status_code, 400)
|
||||
|
@ -38,9 +38,14 @@ def pricing(request):
|
||||
# subscription object is not created just by viewing a page.
|
||||
sub = Subscription.objects.filter(user_id=request.user.id).first()
|
||||
|
||||
period = "monthly"
|
||||
if sub and sub.plan_id.startswith("Y"):
|
||||
period = "annual"
|
||||
|
||||
ctx = {
|
||||
"page": "pricing",
|
||||
"sub": sub,
|
||||
"period": period,
|
||||
"first_charge": request.session.pop("first_charge", False)
|
||||
}
|
||||
|
||||
@ -48,9 +53,13 @@ def pricing(request):
|
||||
|
||||
|
||||
def log_and_bail(request, result):
|
||||
logged_deep_error = False
|
||||
|
||||
for error in result.errors.deep_errors:
|
||||
messages.error(request, error.message)
|
||||
else:
|
||||
logged_deep_error = True
|
||||
|
||||
if not logged_deep_error:
|
||||
messages.error(request, result.message)
|
||||
|
||||
return redirect("hc-pricing")
|
||||
@ -60,7 +69,7 @@ def log_and_bail(request, result):
|
||||
@require_POST
|
||||
def create_plan(request):
|
||||
plan_id = request.POST["plan_id"]
|
||||
if plan_id not in ("P5", "P50"):
|
||||
if plan_id not in ("P5", "P50", "Y48", "Y480"):
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
sub = Subscription.objects.for_user(request.user)
|
||||
@ -111,14 +120,14 @@ def create_plan(request):
|
||||
|
||||
# Update user's profile
|
||||
profile = request.user.profile
|
||||
if plan_id == "P5":
|
||||
if plan_id in ("P5", "Y48"):
|
||||
profile.ping_log_limit = 1000
|
||||
profile.check_limit = 500
|
||||
profile.team_limit = 9
|
||||
profile.sms_limit = 50
|
||||
profile.sms_sent = 0
|
||||
profile.save()
|
||||
elif plan_id == "P50":
|
||||
elif plan_id in ("P50", "Y480"):
|
||||
profile.ping_log_limit = 1000
|
||||
profile.check_limit = 500
|
||||
profile.team_limit = 500
|
||||
|
@ -1,9 +1,5 @@
|
||||
.panel-pricing .panel-heading {
|
||||
padding: 20px 10px;
|
||||
}
|
||||
.panel-pricing .list-group-item {
|
||||
color: #777777;
|
||||
border-bottom: 1px solid rgba(250, 250, 250, 0.5);
|
||||
}
|
||||
.panel-pricing .list-group-item:last-child {
|
||||
border-bottom-right-radius: 0px;
|
||||
@ -46,10 +42,29 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel-pricing .free {
|
||||
}
|
||||
|
||||
.mo {
|
||||
font-size: 18px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#period-controls {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
#period-controls .btn {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#plans .panel-footer {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#annual-note {
|
||||
margin: 10px 0;
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
#payment-method-modal .modal-header {
|
||||
text-align: center;
|
||||
}
|
@ -1,29 +1,48 @@
|
||||
$(function () {
|
||||
var clientTokenRequested = false;
|
||||
function requestClientToken() {
|
||||
if (!clientTokenRequested) {
|
||||
clientTokenRequested = true;
|
||||
$.getJSON("/pricing/get_client_token/", setupDropin);
|
||||
}
|
||||
}
|
||||
|
||||
function setupDropin(data) {
|
||||
braintree.dropin.create({
|
||||
authorization: data.client_token,
|
||||
container: "#dropin",
|
||||
paypal: { flow: 'vault' }
|
||||
}, function(createErr, instance) {
|
||||
$("#payment-form-submit").click(function() {
|
||||
instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
|
||||
$("#pmm-nonce").val(payload.nonce);
|
||||
$("#payment-form").submit();
|
||||
});
|
||||
}).prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
$(".btn-create-payment-method").hover(requestClientToken);
|
||||
$(".btn-update-payment-method").hover(requestClientToken);
|
||||
|
||||
$(".btn-create-payment-method").click(function() {
|
||||
var planId = $(this).data("plan-id");
|
||||
$("#plan_id").val(planId);
|
||||
$.getJSON("/pricing/get_client_token/", function(data) {
|
||||
var $modal = $("#payment-method-modal");
|
||||
braintree.setup(data.client_token, "dropin", {
|
||||
container: "payment-form"
|
||||
});
|
||||
$modal.modal("show");
|
||||
})
|
||||
requestClientToken();
|
||||
$("#plan_id").val(this.dataset.planId);
|
||||
$("#payment-form").attr("action", this.dataset.action);
|
||||
$("#payment-form-submit").text("Set Up Subscription and Pay $" + this.dataset.planPay);
|
||||
$("#payment-method-modal").modal("show");
|
||||
});
|
||||
|
||||
$(".btn-update-payment-method").click(function() {
|
||||
$.getJSON("/pricing/get_client_token/", function(data) {
|
||||
var $modal = $("#update-payment-method-modal");
|
||||
braintree.setup(data.client_token, "dropin", {
|
||||
container: "update-payment-form"
|
||||
});
|
||||
$modal.modal("show");
|
||||
})
|
||||
requestClientToken();
|
||||
$("#payment-form").attr("action", this.dataset.action);
|
||||
$("#payment-form-submit").text("Update Payment Method");
|
||||
$("#payment-method-modal").modal("show");
|
||||
});
|
||||
|
||||
$(".pm-modal").on("hidden.bs.modal", function() {
|
||||
location.reload();
|
||||
})
|
||||
$("#period-controls :input").change(function() {
|
||||
$("#monthly").toggleClass("hide", this.value != "monthly");
|
||||
$("#annual").toggleClass("hide", this.value != "annual");
|
||||
});
|
||||
|
||||
});
|
@ -1,12 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles compress %}
|
||||
{% load staticfiles compress hc_extras %}
|
||||
|
||||
{% block title %}Pricing - It's Free! - healthchecks.io{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Plans -->
|
||||
<section id="plans">
|
||||
<section id="plans" {% if request.user.is_authenticated %} data- {% endif %}>
|
||||
<div class="container">
|
||||
{% if messages %}
|
||||
<div class="alert alert-danger">
|
||||
@ -28,7 +28,7 @@
|
||||
{% if first_charge %}
|
||||
Success! You just paid ${{ sub.price }}.
|
||||
{% else %}
|
||||
You are currently paying ${{ sub.price }}/month
|
||||
You are currently paying ${{ sub.price }}/{{ sub.period }}
|
||||
|
||||
{% if sub.pm_is_credit_card %}
|
||||
using {{ sub.card_type }} card
|
||||
@ -47,7 +47,9 @@
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'hc-billing' %}">See Billing History</a>
|
||||
{% if not first_charge %}
|
||||
<button class="btn btn-default btn-update-payment-method">
|
||||
<button
|
||||
class="btn btn-default btn-update-payment-method"
|
||||
data-action="{% url 'hc-update-payment-method' %}">
|
||||
Change Payment Method
|
||||
</button>
|
||||
{% endif %}
|
||||
@ -55,22 +57,51 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<h1>{% site_name %} Pricing Plans</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12" id="period-controls">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<label class="btn btn-default {% if period == "monthly" %} active {% endif %}">
|
||||
<input
|
||||
type="radio"
|
||||
name="period"
|
||||
value="monthly"
|
||||
{% if period == "monthly" %} checked {% endif %}
|
||||
autocomplete="off"> Monthly
|
||||
</label>
|
||||
<label class="btn btn-default {% if period == "annual" %} active {% endif %}">
|
||||
<input
|
||||
type="radio"
|
||||
name="period"
|
||||
value="annual"
|
||||
{% if period == "annual" %} checked {% endif %}
|
||||
autocomplete="off"> Annual
|
||||
</label>
|
||||
</div>
|
||||
<p id="annual-note">(20% off on annual plan)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="monthly" class="row {% if period != "monthly" %} hide {% endif %}">
|
||||
<!-- Free -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default panel-pricing">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center free">
|
||||
<p>free</p>
|
||||
<h1>Free</h1>
|
||||
<h2>$0<span class="mo">/mo</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">100 log entries per check</li>
|
||||
<li class="list-group-item"><i class="fa fa-check"></i> Personal or Commercial use</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"> </li>
|
||||
<li class="list-group-item"> </li>
|
||||
</ul>
|
||||
@ -96,16 +127,16 @@
|
||||
|
||||
<!-- P5 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default panel-pricing">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<p>$5<span class="mo">/mo</span></p>
|
||||
<h1>Standard</h1>
|
||||
<h2>$5<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">Personal or Commercial use</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">50 SMS alerts per month</li>
|
||||
<li class="list-group-item">Email Support</li>
|
||||
</ul>
|
||||
@ -118,11 +149,13 @@
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="P5"
|
||||
data-plan-pay="5"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if sub.plan_id == "P50" %}
|
||||
Switch to $5/mo
|
||||
{% else %}
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $5/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
@ -138,16 +171,16 @@
|
||||
|
||||
<!-- P50 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default panel-pricing">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<p>$50<span class="mo">/mo</span></p>
|
||||
<h1>Plus</h1>
|
||||
<h2>$50<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">Personal or Commercial use</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">500 SMS alerts per month</li>
|
||||
<li class="list-group-item">Priority Email Support</li>
|
||||
</ul>
|
||||
@ -160,8 +193,94 @@
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="P50"
|
||||
data-plan-pay="50"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
Upgrade Your Account
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $50/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
</div>
|
||||
|
||||
<div id="annual" class="row {% if period != "annual" %} hide {% endif %}">
|
||||
<!-- Free -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center free">
|
||||
<h1>Free</h1>
|
||||
<h2>$0<span class="mo">/mo</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">100 log entries per check</li>
|
||||
<li class="list-group-item"> </li>
|
||||
<li class="list-group-item"> </li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.subscription_id %}
|
||||
<form method="post" action="{% url 'hc-cancel-plan' %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-lg btn-default">
|
||||
Switch To Free
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-success disabled" href="#">Selected</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-success" href="{% url 'hc-login' %}">Get Started</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- Y48 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Standard</h1>
|
||||
<h2>$4<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited 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">50 SMS alerts per month</li>
|
||||
<li class="list-group-item">Email Support</li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.plan_id == "Y48" %}
|
||||
<button class="btn btn-lg btn-success disabled">
|
||||
Selected
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="Y48"
|
||||
data-plan-pay="48"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $4/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
@ -174,6 +293,49 @@
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- Y480 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Plus</h1>
|
||||
<h2>$40<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited 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">500 SMS alerts per month</li>
|
||||
<li class="list-group-item">Priority Email Support</li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.plan_id == "Y480" %}
|
||||
<button class="btn btn-lg btn-success disabled">
|
||||
Selected
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="Y480"
|
||||
data-plan-pay="480"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $40/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -257,62 +419,35 @@
|
||||
|
||||
<div id="payment-method-modal" class="modal pm-modal">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" action="{% url 'hc-create-plan' %}">
|
||||
<form id="payment-form" method="post" action="{% url 'hc-create-plan' %}">
|
||||
{% csrf_token %}
|
||||
<input id="plan_id" type="hidden" name="plan_id" value="" />
|
||||
<input id="pmm-nonce" type="hidden" name="payment_method_nonce" />
|
||||
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Set Up Subscription
|
||||
<small>for {{ request.user.profile }}</small>
|
||||
</h4>
|
||||
<h4>Subscription for {{ request.user.profile }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="payment-form"></div>
|
||||
<div id="dropin"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Set Up Subscription
|
||||
<button id="payment-form-submit" type="button" class="btn btn-primary" disabled>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-payment-method-modal" class="modal pm-modal">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" action="{% url 'hc-update-payment-method' %}">
|
||||
{% csrf_token %}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Your Payment Method</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="update-payment-form"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Confirm Payment Method
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
||||
<script src="https://js.braintreegateway.com/web/dropin/1.8.0/js/dropin.min.js"></script>
|
||||
{% compress js %}
|
||||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user