Browse Source

Braintree integration WIP.

pull/20/head
Pēteris Caune 9 years ago
parent
commit
263f50058e
6 changed files with 120 additions and 39 deletions
  1. +27
    -0
      hc/payments/models.py
  2. +4
    -0
      hc/payments/urls.py
  3. +40
    -12
      hc/payments/views.py
  4. +16
    -1
      static/css/pricing.css
  5. +8
    -9
      static/js/pricing.js
  6. +25
    -17
      templates/payments/pricing.html

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

@ -16,6 +16,13 @@ class Subscription(models.Model):
return self._sub return self._sub
def _get_braintree_payment_method(self):
if not hasattr(self, "_pm"):
print("getting payment method over network")
self._pm = braintree.PaymentMethod.find(self.payment_method_token)
return self._pm
def is_active(self): def is_active(self):
if not self.subscription_id: if not self.subscription_id:
return False return False
@ -30,3 +37,23 @@ class Subscription(models.Model):
def next_billing_date(self): def next_billing_date(self):
o = self._get_braintree_sub() o = self._get_braintree_sub()
return o.next_billing_date return o.next_billing_date
def pm_is_credit_card(self):
return isinstance(self._get_braintree_payment_method(),
braintree.credit_card.CreditCard)
def pm_is_paypal(self):
return isinstance(self._get_braintree_payment_method(),
braintree.paypal_account.PayPalAccount)
def card_type(self):
o = self._get_braintree_payment_method()
return o.card_type
def last_4(self):
o = self._get_braintree_payment_method()
return o.last_4
def paypal_email(self):
o = self._get_braintree_payment_method()
return o.email

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

@ -19,4 +19,8 @@ urlpatterns = [
views.cancel_plan, views.cancel_plan,
name="hc-cancel-plan"), name="hc-cancel-plan"),
url(r'^pricing/get_client_token/$',
views.get_client_token,
name="hc-get-client-token"),
] ]

+ 40
- 12
hc/payments/views.py View File

@ -1,6 +1,8 @@
import braintree 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.contrib import messages
from django.http import JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
@ -17,32 +19,55 @@ def setup_braintree():
braintree.Configuration.configure(settings.BRAINTREE_ENV, **kw) braintree.Configuration.configure(settings.BRAINTREE_ENV, **kw)
@login_required
def get_client_token(request):
sub = Subscription.objects.get(user=request.user)
client_token = braintree.ClientToken.generate({
"customer_id": sub.customer_id
})
return JsonResponse({"client_token": client_token})
def pricing(request): def pricing(request):
setup_braintree() setup_braintree()
try:
sub = Subscription.objects.get(user=request.user)
except Subscription.DoesNotExist:
sub = Subscription(user=request.user)
sub.save()
sub = None
if request.user.is_authenticated():
try:
sub = Subscription.objects.get(user=request.user)
except Subscription.DoesNotExist:
sub = Subscription(user=request.user)
sub.save()
ctx = { ctx = {
"page": "pricing", "page": "pricing",
"sub": sub,
"client_token": braintree.ClientToken.generate()
"sub": sub
} }
return render(request, "payments/pricing.html", ctx) return render(request, "payments/pricing.html", ctx)
def log_and_bail(request, result):
for error in result.errors.deep_errors:
messages.error(request, error.message)
return redirect("hc-pricing")
@login_required @login_required
@require_POST @require_POST
def create_plan(request): def create_plan(request):
price = int(request.POST["price"])
assert price in (2, 5, 10, 15, 20, 25, 50, 100)
setup_braintree() setup_braintree()
sub = Subscription.objects.get(user=request.user) 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
if not result.is_success:
return log_and_bail(request, result)
sub.customer_id = result.customer.id sub.customer_id = result.customer.id
sub.save() sub.save()
@ -51,19 +76,22 @@ def create_plan(request):
"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"]
}) })
assert result.is_success
if not result.is_success:
return log_and_bail(request, result)
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": "P%d" % price, "plan_id": "P%d" % price,
"price": price "price": price
}) })
if not result.is_success:
return log_and_bail(request, result)
sub.subscription_id = result.subscription.id sub.subscription_id = result.subscription.id
sub.save() sub.save()


+ 16
- 1
static/css/pricing.css View File

@ -107,4 +107,19 @@
#subscription-status form { #subscription-status form {
display: inline-block; display: inline-block;
}
}
#payment-method-modal .modal-header {
border-bottom: 0;
}
#payment-method-modal .modal-footer {
border-top: 0;
}
.error-message {
font-family: monospace;
}

+ 8
- 9
static/js/pricing.js View File

@ -43,15 +43,14 @@ $(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");
$("#pww-create-payment-method").click(function() {
$.getJSON("/pricing/get_client_token/", function(data) {
var $modal = $("#payment-method-modal");
braintree.setup(data.client_token, "dropin", {
container: "payment-form"
});
$modal.modal("show");
})
}); });
}); });

+ 25
- 17
templates/payments/pricing.html View File

@ -8,17 +8,38 @@
<!-- Plans --> <!-- Plans -->
<section id="plans"> <section id="plans">
<div class="container"> <div class="container">
{% if messages %}
<div class="alert alert-danger">
<p>
<strong>We're sorry!</strong> There was a problem setting
up the subscription. Response from payment processor:</p>
{% for message in messages %}
<p class="error-message">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% if sub.is_active %} {% if sub.is_active %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div id="subscription-status" class="jumbotron"> <div id="subscription-status" class="jumbotron">
<p> <p>
You are currently paying <strong>${{ sub.price }}/month</strong>.
Next billing date will be {{ sub.next_billing_date }}.</p>
You are currently paying <strong>${{ sub.price }}/month</strong>
{% if sub.pm_is_credit_card %}
using {{ sub.card_type }} card
ending with {{ sub.last_4 }}.
{% endif %}
{% if sub.pm_is_paypal %}
using PayPal account {{ sub.paypal_email }}.
{% endif %}
</p>
<p> <p>
Next billing date will be {{ sub.next_billing_date }}.
Thank you for supporting healthchecks.io! Thank you for supporting healthchecks.io!
</p> </p>
<a class="btn btn-default" href="#">Update Payment Method</a>
<form method="post" action="{% url 'hc-cancel-plan' %}"> <form method="post" action="{% url 'hc-cancel-plan' %}">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-default"> <button type="submit" class="btn btn-default">
@ -125,22 +146,9 @@
</button> </button>
</form> </form>
{% else %} {% 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 <button
id="pww-create-payment-method" id="pww-create-payment-method"
class="btn btn-lg btn-default">Select (form)</button>
{% endif %}
class="btn btn-lg btn-default">Select</button>
{% endif %} {% endif %}
{% else %} {% 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>


Loading…
Cancel
Save