forked from GithubBackups/healthchecks
Payments WIP
This commit is contained in:
parent
3a93ab77a9
commit
20edec4c94
@ -1,3 +1,4 @@
|
|||||||
|
import braintree
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
@ -7,3 +8,25 @@ class Subscription(models.Model):
|
|||||||
customer_id = models.CharField(max_length=36, blank=True)
|
customer_id = models.CharField(max_length=36, blank=True)
|
||||||
payment_method_token = models.CharField(max_length=35, blank=True)
|
payment_method_token = models.CharField(max_length=35, blank=True)
|
||||||
subscription_id = models.CharField(max_length=10, blank=True)
|
subscription_id = models.CharField(max_length=10, blank=True)
|
||||||
|
|
||||||
|
def _get_braintree_sub(self):
|
||||||
|
if not hasattr(self, "_sub"):
|
||||||
|
print("getting subscription over network")
|
||||||
|
self._sub = braintree.Subscription.find(self.subscription_id)
|
||||||
|
|
||||||
|
return self._sub
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
if not self.subscription_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
o = self._get_braintree_sub()
|
||||||
|
return o.status == "Active"
|
||||||
|
|
||||||
|
def price(self):
|
||||||
|
o = self._get_braintree_sub()
|
||||||
|
return int(o.price)
|
||||||
|
|
||||||
|
def next_billing_date(self):
|
||||||
|
o = self._get_braintree_sub()
|
||||||
|
return o.next_billing_date
|
||||||
|
@ -7,12 +7,16 @@ urlpatterns = [
|
|||||||
views.pricing,
|
views.pricing,
|
||||||
name="hc-pricing"),
|
name="hc-pricing"),
|
||||||
|
|
||||||
url(r'^create_subscription/$',
|
url(r'^pricing/create_plan/$',
|
||||||
views.create,
|
views.create_plan,
|
||||||
name="hc-create-subscription"),
|
name="hc-create-plan"),
|
||||||
|
|
||||||
url(r'^subscription_status/$',
|
url(r'^pricing/update_plan/$',
|
||||||
views.status,
|
views.update_plan,
|
||||||
name="hc-subscription-status"),
|
name="hc-update-plan"),
|
||||||
|
|
||||||
|
url(r'^pricing/cancel_plan/$',
|
||||||
|
views.cancel_plan,
|
||||||
|
name="hc-cancel-plan"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -2,6 +2,7 @@ import braintree
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from .models import Subscription
|
from .models import Subscription
|
||||||
|
|
||||||
@ -17,14 +18,6 @@ def setup_braintree():
|
|||||||
|
|
||||||
|
|
||||||
def pricing(request):
|
def pricing(request):
|
||||||
ctx = {
|
|
||||||
"page": "pricing",
|
|
||||||
}
|
|
||||||
return render(request, "payments/pricing.html", ctx)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def create(request):
|
|
||||||
setup_braintree()
|
setup_braintree()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -33,13 +26,27 @@ def create(request):
|
|||||||
sub = Subscription(user=request.user)
|
sub = Subscription(user=request.user)
|
||||||
sub.save()
|
sub.save()
|
||||||
|
|
||||||
if request.method == "POST":
|
ctx = {
|
||||||
|
"page": "pricing",
|
||||||
|
"sub": sub,
|
||||||
|
"client_token": braintree.ClientToken.generate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, "payments/pricing.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def create_plan(request):
|
||||||
|
setup_braintree()
|
||||||
|
sub = Subscription.objects.get(user=request.user)
|
||||||
if not sub.customer_id:
|
if not sub.customer_id:
|
||||||
result = braintree.Customer.create({})
|
result = braintree.Customer.create({})
|
||||||
assert result.is_success
|
assert result.is_success
|
||||||
sub.customer_id = result.customer.id
|
sub.customer_id = result.customer.id
|
||||||
sub.save()
|
sub.save()
|
||||||
|
|
||||||
|
if "payment_method_nonce" in request.POST:
|
||||||
result = braintree.PaymentMethod.create({
|
result = braintree.PaymentMethod.create({
|
||||||
"customer_id": sub.customer_id,
|
"customer_id": sub.customer_id,
|
||||||
"payment_method_nonce": request.POST["payment_method_nonce"]
|
"payment_method_nonce": request.POST["payment_method_nonce"]
|
||||||
@ -48,34 +55,47 @@ def create(request):
|
|||||||
sub.payment_method_token = result.payment_method.token
|
sub.payment_method_token = result.payment_method.token
|
||||||
sub.save()
|
sub.save()
|
||||||
|
|
||||||
|
price = int(request.POST["price"])
|
||||||
|
assert price in (2, 5, 10, 15, 20, 25, 50, 100)
|
||||||
|
|
||||||
result = braintree.Subscription.create({
|
result = braintree.Subscription.create({
|
||||||
"payment_method_token": sub.payment_method_token,
|
"payment_method_token": sub.payment_method_token,
|
||||||
"plan_id": "pww",
|
"plan_id": "P%d" % price,
|
||||||
"price": 5
|
"price": price
|
||||||
})
|
})
|
||||||
|
|
||||||
sub.subscription_id = result.subscription.id
|
sub.subscription_id = result.subscription.id
|
||||||
sub.save()
|
sub.save()
|
||||||
|
|
||||||
return redirect("hc-subscription-status")
|
return redirect("hc-pricing")
|
||||||
|
|
||||||
ctx = {
|
|
||||||
"page": "pricing",
|
|
||||||
"client_token": braintree.ClientToken.generate()
|
|
||||||
}
|
|
||||||
return render(request, "payments/create_subscription.html", ctx)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def status(request):
|
@require_POST
|
||||||
|
def update_plan(request):
|
||||||
setup_braintree()
|
setup_braintree()
|
||||||
|
|
||||||
sub = Subscription.objects.get(user=request.user)
|
sub = Subscription.objects.get(user=request.user)
|
||||||
subscription = braintree.Subscription.find(sub.subscription_id)
|
|
||||||
|
|
||||||
ctx = {
|
price = int(request.POST["price"])
|
||||||
"page": "pricing",
|
assert price in (2, 5, 10, 15, 20, 25, 50, 100)
|
||||||
"subscription": subscription
|
|
||||||
|
fields = {
|
||||||
|
"plan_id": "P%s" % price,
|
||||||
|
"price": price
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "payments/status.html", ctx)
|
braintree.Subscription.update(sub.subscription_id, fields)
|
||||||
|
return redirect("hc-pricing")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def cancel_plan(request):
|
||||||
|
setup_braintree()
|
||||||
|
sub = Subscription.objects.get(user=request.user)
|
||||||
|
|
||||||
|
braintree.Subscription.cancel(sub.subscription_id)
|
||||||
|
sub.subscription_id = ""
|
||||||
|
sub.save()
|
||||||
|
|
||||||
|
return redirect("hc-pricing")
|
||||||
|
@ -100,3 +100,11 @@
|
|||||||
-webkit-animation-name: tadaIn;
|
-webkit-animation-name: tadaIn;
|
||||||
animation-name: tadaIn;
|
animation-name: tadaIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pww-switch-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#subscription-status form {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
@ -1,13 +1,28 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
var prices = [2, 5, 10, 15, 20, 25, 50, 75, 100, 125, 150, 175, 200];
|
var prices = [2, 5, 10, 15, 20, 25, 50, 100];
|
||||||
var priceIdx = 2;
|
var initialPrice = parseInt($("#pricing-value").text());
|
||||||
|
var priceIdx = prices.indexOf(initialPrice);
|
||||||
|
|
||||||
|
function updateDisplayPrice(price) {
|
||||||
|
$("#pricing-value").text(price);
|
||||||
|
$(".selected-price").val(price);
|
||||||
|
$("#pww-switch-btn").text("Switch to $" + price + " / mo");
|
||||||
|
|
||||||
|
if (price == initialPrice) {
|
||||||
|
$("#pww-selected-btn").show();
|
||||||
|
$("#pww-switch-btn").hide();
|
||||||
|
} else {
|
||||||
|
$("#pww-selected-btn").hide();
|
||||||
|
$("#pww-switch-btn").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$("#pay-plus").click(function() {
|
$("#pay-plus").click(function() {
|
||||||
if (priceIdx >= 12)
|
if (priceIdx > 6)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
priceIdx += 1;
|
priceIdx += 1;
|
||||||
$("#pricing-value").text(prices[priceIdx]);
|
updateDisplayPrice(prices[priceIdx]);
|
||||||
|
|
||||||
$("#piggy").removeClass().addClass("tada animated").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
|
$("#piggy").removeClass().addClass("tada animated").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
|
||||||
$(this).removeClass();
|
$(this).removeClass();
|
||||||
@ -20,7 +35,7 @@ $(function () {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
priceIdx -= 1;
|
priceIdx -= 1;
|
||||||
$("#pricing-value").text(prices[priceIdx]);
|
updateDisplayPrice(prices[priceIdx]);
|
||||||
|
|
||||||
$("#piggy").removeClass().addClass("tadaIn animated").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
|
$("#piggy").removeClass().addClass("tadaIn animated").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
|
||||||
$(this).removeClass();
|
$(this).removeClass();
|
||||||
@ -28,5 +43,15 @@ $(function () {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#pww-create-payment-method").click(function(){
|
||||||
|
var $modal = $("#payment-method-modal");
|
||||||
|
var clientToken = $modal.attr("data-client-token");
|
||||||
|
|
||||||
|
braintree.setup(clientToken, "dropin", {
|
||||||
|
container: "payment-form"
|
||||||
|
});
|
||||||
|
|
||||||
|
$modal.modal("show");
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
@ -1,23 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block title %}Pricing - It's Free! - healthchecks.io{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<form id="checkout" method="post" action="{% url 'hc-create-subscription' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div id="payment-form"></div>
|
|
||||||
<input class="btn" type="submit" value="Set Up Subscription">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
|
||||||
<script>
|
|
||||||
var clientToken = "{{ client_token }}";
|
|
||||||
|
|
||||||
braintree.setup(clientToken, "dropin", {
|
|
||||||
container: "payment-form"
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -8,17 +8,40 @@
|
|||||||
<!-- Plans -->
|
<!-- Plans -->
|
||||||
<section id="plans">
|
<section id="plans">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{% if sub.is_active %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="subscription-status" class="jumbotron">
|
||||||
|
<p>
|
||||||
|
You are currently paying <strong>${{ sub.price }}/month</strong>.
|
||||||
|
Next billing date will be {{ sub.next_billing_date }}.</p>
|
||||||
|
<p>
|
||||||
|
Thank you for supporting healthchecks.io!
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-default" href="#">Update Payment Method</a>
|
||||||
|
<form method="post" action="{% url 'hc-cancel-plan' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-default">
|
||||||
|
Cancel Subscription
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<!-- item -->
|
<!-- item -->
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-sm-4 text-center">
|
||||||
<div class="panel panel-success panel-pricing">
|
<div class="panel panel-success panel-pricing">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<i class="glyphicon glyphicon-heart"></i>
|
<i class="glyphicon glyphicon-heart"></i>
|
||||||
<h3>Free Plan</h3>
|
<h3>Free Plan</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body text-center">
|
<div class="panel-body text-center">
|
||||||
<p><strong>€0 / Month</strong></p>
|
<p><strong>$0 / Month</strong></p>
|
||||||
</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> Personal or Commercial use</li>
|
<li class="list-group-item"><i class="fa fa-check"></i> Personal or Commercial use</li>
|
||||||
@ -27,14 +50,27 @@
|
|||||||
<li class="list-group-item"> </li>
|
<li class="list-group-item"> </li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="panel-footer">
|
<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>
|
<a class="btn btn-lg btn-success" href="{% url 'hc-login' %}">Get Started</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /item -->
|
<!-- /item -->
|
||||||
|
|
||||||
<!-- item -->
|
<!-- item -->
|
||||||
<div class="col-md-8 text-center">
|
<div class="col-sm-8 text-center">
|
||||||
<div class="panel panel-success panel-pricing">
|
<div class="panel panel-success panel-pricing">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div id="piggy">
|
<div id="piggy">
|
||||||
@ -44,7 +80,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body text-center">
|
<div class="panel-body text-center">
|
||||||
<p>
|
<p>
|
||||||
<strong>€<span id="pricing-value">10</span> / Month</strong>
|
<strong>
|
||||||
|
{% if sub.is_active %}
|
||||||
|
$<span id="pricing-value">{{ sub.price }}</span> / Month
|
||||||
|
{% else %}
|
||||||
|
$<span id="pricing-value">10</span> / Month
|
||||||
|
{% endif %}
|
||||||
|
</strong>
|
||||||
|
|
||||||
<span class="btn-group" role="group">
|
<span class="btn-group" role="group">
|
||||||
<button id="pay-minus" type="button" class="btn btn-default">
|
<button id="pay-minus" type="button" class="btn btn-default">
|
||||||
@ -67,20 +109,79 @@
|
|||||||
<li class="list-group-item"><i class="fa fa-check"></i> Priority Support</li>
|
<li class="list-group-item"><i class="fa fa-check"></i> Priority Support</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
{% if sub.is_active %}
|
||||||
|
<button id="pww-selected-btn"
|
||||||
|
class="btn btn-lg btn-success disabled">Selected</button>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'hc-update-plan' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="selected-price" type="hidden" name="price" />
|
||||||
|
<button
|
||||||
|
id="pww-switch-btn"
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-lg btn-default">
|
||||||
|
Switch To
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
{% if sub.payment_method_token %}
|
||||||
|
<form method="post" action="{% url 'hc-create-plan' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="selected-price" type="hidden" name="price" value="10" />
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-lg btn-default">
|
||||||
|
Select (direct)
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<button
|
||||||
|
id="pww-create-payment-method"
|
||||||
|
class="btn btn-lg btn-default">Select (form)</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">Get Started</a>
|
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">Get Started</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /item -->
|
<!-- /item -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- /Plans -->
|
<!-- /Plans -->
|
||||||
|
|
||||||
|
<div id="payment-method-modal" class="modal" data-client-token="{{ client_token }}">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="post" action="{% url 'hc-create-plan' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="selected-price" type="hidden" name="price" value="10" />
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</span></button>
|
||||||
|
<h4>Set Up Subscription</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="payment-method-body">
|
||||||
|
<div id="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">Set Up Subscription</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block title %}Pricing - It's Free! - healthchecks.io{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h1>Subscription Status</h1>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>Status</dt>
|
|
||||||
<dd>{{ subscription.status }}</dd>
|
|
||||||
|
|
||||||
<dt>Next Billing Date:</dt>
|
|
||||||
<dd>{{ subscription.next_billing_date }}</dd>
|
|
||||||
|
|
||||||
|
|
||||||
<dt>Amount</dt>
|
|
||||||
<dd>{{ subscription.price }} / Month</dd>
|
|
||||||
|
|
||||||
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h2>Transactions</h2>
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
</tr>
|
|
||||||
{% for tx in subscription.transactions %}
|
|
||||||
<tr>
|
|
||||||
<td>???</td>
|
|
||||||
<td>{{ tx.amount }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
Loading…
x
Reference in New Issue
Block a user