From 20edec4c943823ee0bbddd0d4d90b53ed6d9c2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Sun, 22 Nov 2015 12:20:36 +0200 Subject: [PATCH] Payments WIP --- hc/payments/models.py | 23 ++++ hc/payments/urls.py | 16 +-- hc/payments/views.py | 86 +++++++++------ static/css/pricing.css | 8 ++ static/js/pricing.js | 35 +++++- templates/payments/create_subscription.html | 23 ---- templates/payments/pricing.html | 113 ++++++++++++++++++-- templates/payments/status.html | 39 ------- 8 files changed, 231 insertions(+), 112 deletions(-) delete mode 100644 templates/payments/create_subscription.html delete mode 100644 templates/payments/status.html diff --git a/hc/payments/models.py b/hc/payments/models.py index 30045204..4a8df4bd 100644 --- a/hc/payments/models.py +++ b/hc/payments/models.py @@ -1,3 +1,4 @@ +import braintree from django.contrib.auth.models import User from django.db import models @@ -7,3 +8,25 @@ class Subscription(models.Model): customer_id = models.CharField(max_length=36, blank=True) payment_method_token = models.CharField(max_length=35, 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 diff --git a/hc/payments/urls.py b/hc/payments/urls.py index 500291b3..053acb64 100644 --- a/hc/payments/urls.py +++ b/hc/payments/urls.py @@ -7,12 +7,16 @@ urlpatterns = [ views.pricing, name="hc-pricing"), - url(r'^create_subscription/$', - views.create, - name="hc-create-subscription"), + url(r'^pricing/create_plan/$', + views.create_plan, + name="hc-create-plan"), - url(r'^subscription_status/$', - views.status, - name="hc-subscription-status"), + url(r'^pricing/update_plan/$', + views.update_plan, + name="hc-update-plan"), + + url(r'^pricing/cancel_plan/$', + views.cancel_plan, + name="hc-cancel-plan"), ] diff --git a/hc/payments/views.py b/hc/payments/views.py index e56f36f2..9bf9bde4 100644 --- a/hc/payments/views.py +++ b/hc/payments/views.py @@ -2,6 +2,7 @@ import braintree from django.conf import settings from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, render +from django.views.decorators.http import require_POST from .models import Subscription @@ -17,29 +18,35 @@ def setup_braintree(): def pricing(request): + setup_braintree() + + try: + sub = Subscription.objects.get(user=request.user) + except Subscription.DoesNotExist: + sub = Subscription(user=request.user) + sub.save() + ctx = { "page": "pricing", + "sub": sub, + "client_token": braintree.ClientToken.generate() } + return render(request, "payments/pricing.html", ctx) @login_required -def create(request): +@require_POST +def create_plan(request): setup_braintree() - - try: - sub = Subscription.objects.get(user=request.user) - except Subscription.DoesNotExist: - sub = Subscription(user=request.user) + sub = Subscription.objects.get(user=request.user) + if not sub.customer_id: + result = braintree.Customer.create({}) + assert result.is_success + sub.customer_id = result.customer.id sub.save() - if request.method == "POST": - if not sub.customer_id: - result = braintree.Customer.create({}) - assert result.is_success - sub.customer_id = result.customer.id - sub.save() - + if "payment_method_nonce" in request.POST: result = braintree.PaymentMethod.create({ "customer_id": sub.customer_id, "payment_method_nonce": request.POST["payment_method_nonce"] @@ -48,34 +55,47 @@ def create(request): sub.payment_method_token = result.payment_method.token sub.save() - result = braintree.Subscription.create({ - "payment_method_token": sub.payment_method_token, - "plan_id": "pww", - "price": 5 - }) + price = int(request.POST["price"]) + assert price in (2, 5, 10, 15, 20, 25, 50, 100) - sub.subscription_id = result.subscription.id - sub.save() + result = braintree.Subscription.create({ + "payment_method_token": sub.payment_method_token, + "plan_id": "P%d" % price, + "price": price + }) - return redirect("hc-subscription-status") + sub.subscription_id = result.subscription.id + sub.save() - ctx = { - "page": "pricing", - "client_token": braintree.ClientToken.generate() - } - return render(request, "payments/create_subscription.html", ctx) + return redirect("hc-pricing") @login_required -def status(request): +@require_POST +def update_plan(request): setup_braintree() - sub = Subscription.objects.get(user=request.user) - subscription = braintree.Subscription.find(sub.subscription_id) - ctx = { - "page": "pricing", - "subscription": subscription + price = int(request.POST["price"]) + assert price in (2, 5, 10, 15, 20, 25, 50, 100) + + 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") diff --git a/static/css/pricing.css b/static/css/pricing.css index 5bcd2eab..648a4737 100644 --- a/static/css/pricing.css +++ b/static/css/pricing.css @@ -100,3 +100,11 @@ -webkit-animation-name: tadaIn; animation-name: tadaIn; } + +#pww-switch-btn { + display: none; +} + +#subscription-status form { + display: inline-block; +} \ No newline at end of file diff --git a/static/js/pricing.js b/static/js/pricing.js index 22ceb3a1..b20d7511 100644 --- a/static/js/pricing.js +++ b/static/js/pricing.js @@ -1,13 +1,28 @@ $(function () { - var prices = [2, 5, 10, 15, 20, 25, 50, 75, 100, 125, 150, 175, 200]; - var priceIdx = 2; + var prices = [2, 5, 10, 15, 20, 25, 50, 100]; + 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() { - if (priceIdx >= 12) + if (priceIdx > 6) return; priceIdx += 1; - $("#pricing-value").text(prices[priceIdx]); + updateDisplayPrice(prices[priceIdx]); $("#piggy").removeClass().addClass("tada animated").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){ $(this).removeClass(); @@ -20,7 +35,7 @@ $(function () { return; priceIdx -= 1; - $("#pricing-value").text(prices[priceIdx]); + updateDisplayPrice(prices[priceIdx]); $("#piggy").removeClass().addClass("tadaIn animated").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){ $(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"); + }); }); \ No newline at end of file diff --git a/templates/payments/create_subscription.html b/templates/payments/create_subscription.html deleted file mode 100644 index 0e6065fc..00000000 --- a/templates/payments/create_subscription.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block title %}Pricing - It's Free! - healthchecks.io{% endblock %} - -{% block content %} - -
- {% csrf_token %} -
- -
- - - - -{% endblock %} \ No newline at end of file diff --git a/templates/payments/pricing.html b/templates/payments/pricing.html index 920f4917..b355f6a4 100644 --- a/templates/payments/pricing.html +++ b/templates/payments/pricing.html @@ -8,17 +8,40 @@
+ {% if sub.is_active %} +
+
+
+

+ You are currently paying ${{ sub.price }}/month. + Next billing date will be {{ sub.next_billing_date }}.

+

+ Thank you for supporting healthchecks.io! +

+ Update Payment Method +
+ {% csrf_token %} + +
+
+
+
+ {% endif %} + +
-
+

Free Plan

-

€0 / Month

+

$0 / Month

  • Personal or Commercial use
  • @@ -27,14 +50,27 @@
  •  
-
+
@@ -44,7 +80,13 @@

- 10 / Month + + {% if sub.is_active %} + ${{ sub.price }} / Month + {% else %} + $10 / Month + {% endif %} + + +

+ {% csrf_token %} + + +
+ {% else %} + {% if sub.payment_method_token %} +
+ {% csrf_token %} + + +
+ {% else %} + + {% endif %} + + {% endif %} + {% else %} + Get Started + {% endif %}
-
+
+ + {% endblock %} {% block scripts %} + {% compress js %} diff --git a/templates/payments/status.html b/templates/payments/status.html deleted file mode 100644 index 20171e2f..00000000 --- a/templates/payments/status.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block title %}Pricing - It's Free! - healthchecks.io{% endblock %} - -{% block content %} - -

Subscription Status

- -
-
Status
-
{{ subscription.status }}
- -
Next Billing Date:
-
{{ subscription.next_billing_date }}
- - -
Amount
-
{{ subscription.price }} / Month
- - -
- -

Transactions

- - - - - - {% for tx in subscription.transactions %} - - - - - {% endfor %} -
DateAmount
???{{ tx.amount }}
- - -{% endblock %} \ No newline at end of file