Skip to content

Commit

Permalink
Merge 04447ba into adf5227
Browse files Browse the repository at this point in the history
  • Loading branch information
paltman committed Nov 14, 2015
2 parents adf5227 + 04447ba commit 09bdab4
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 100 deletions.
20 changes: 10 additions & 10 deletions payments/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
ChangePlanView,
HistoryView,
SubscribeView,
webhook,
subscribe,
change_card,
change_plan,
cancel
Webhook,
AjaxSubscribe,
AjaxChangeCard,
AjaxChangePlan,
AjaxCancelSubscription
)


urlpatterns = [
url(r"^webhook/$", webhook, name="payments_webhook"),
url(r"^a/subscribe/$", subscribe, name="payments_ajax_subscribe"),
url(r"^a/change/card/$", change_card, name="payments_ajax_change_card"),
url(r"^a/change/plan/$", change_plan, name="payments_ajax_change_plan"),
url(r"^a/cancel/$", cancel, name="payments_ajax_cancel"),
url(r"^webhook/$", Webhook.as_view(), name="payments_webhook"),
url(r"^a/subscribe/$", login_required(AjaxSubscribe.as_view()), name="payments_ajax_subscribe"),
url(r"^a/change/card/$", login_required(AjaxChangeCard.as_view()), name="payments_ajax_change_card"),
url(r"^a/change/plan/$", login_required(AjaxChangePlan.as_view()), name="payments_ajax_change_plan"),
url(r"^a/cancel/$", login_required(AjaxCancelSubscription.as_view()), name="payments_ajax_cancel"),
url(
r"^subscribe/$",
login_required(SubscribeView.as_view()),
Expand Down
251 changes: 166 additions & 85 deletions payments/views.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import json

from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.utils.encoding import smart_str
from django.views.generic import TemplateView
from django.views.generic import TemplateView, View
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

try:
from account.decorators import login_required
except ImportError:
from django.contrib.auth.decorators import login_required

import stripe

from eldarion.ajax.views import EldarionAjaxResponseMixin

from . import settings as app_settings
from .forms import PlanForm
from .models import (
Expand Down Expand Up @@ -79,103 +76,187 @@ class HistoryView(PaymentsContextMixin, TemplateView):
template_name = "payments/history.html"


@require_POST
@login_required
def change_card(request):
try:
customer = request.user.customer
send_invoice = customer.card_fingerprint == ""
customer.update_card(
request.POST.get("stripe_token")
)
if send_invoice:
customer.send_invoice()
customer.retry_unpaid_invoices()
data = {}
except stripe.CardError as e:
data = {"error": smart_str(e)}
return _ajax_response(request, "payments/_change_card_form.html", **data)


@require_POST
@login_required
def change_plan(request):
form = PlanForm(request.POST)
try:
current_plan = request.user.customer.current_subscription.plan
except CurrentSubscription.DoesNotExist:
current_plan = None
if form.is_valid():
class CustomerMixin(object):

@property
def customer(self):
if not hasattr(self, "_customer"):
self._customer = self.request.user.customer
return self._customer


class AjaxChangeCard(EldarionAjaxResponseMixin, CustomerMixin, View):

template_fragment = "payments/_change_card_form.html"

def send_invoice(self):
if self.customer.card_fingerprint == "":
self.customer.send_invoice()

def update_card(self, stripe_token):
self.customer.update_card(stripe_token)

def retry_unpaid_invoices(self):
self.customer.retry_unpaid_invoices()

def post(self, request, *args, **kwargs):
try:
request.user.customer.subscribe(form.cleaned_data["plan"])
self.update_card(request.POST.get("stripe_token"))
self.send_invoice()
self.retry_unpaid_invoices()
data = {}
except stripe.CardError as e:
data = {"error": smart_str(e)}
return self.render_to_response(data)


class AjaxChangePlan(EldarionAjaxResponseMixin, CustomerMixin, View):

form_class = PlanForm
template_fragment = "payments/_change_plan_form.html"

@property
def current_plan(self):
if not hasattr(self, "_current_plan"):
sub = next(iter(CurrentSubscription.objects.filter(customer=self.customer)), None)
if sub:
self._current_plan = sub.plan
return self._current_plan

def subscribe(self, plan):
try:
self.customer.subscribe(plan)
data = {
"form": PlanForm(initial={"plan": form.cleaned_data["plan"]})
"form": PlanForm(initial={"plan": plan})
}
except stripe.StripeError as e:
data = {
"form": PlanForm(initial={"plan": current_plan}),
"form": PlanForm(initial={"plan": self.current_plan}),
"error": smart_str(e)
}
else:
return data

def form_valid(self, form):
data = self.subscribe(plan=form.cleaned_data["plan"])
return self.render_to_response(data)

def form_invalid(self, form):
data = {"form": form}
return self.render_to_response(data)

def post(self, request, *args, **kwargs):
form = PlanForm(request.POST)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)


class AjaxSubscribe(EldarionAjaxResponseMixin, CustomerMixin, View):

form_class = PlanForm
template_fragment = "payments/_subscribe_form.html"

def redirect(self):
return self.response_class(
data=self.render_location(self.get_success_url()),
encoder=self.encoder_class,
safe=self.safe
)

def get_success_url(self):
return reverse("payments_history")

def set_customer(self):
try:
self.customer
except ObjectDoesNotExist:
self._customer = Customer.create(self.request.user)

def update_card(self, token):
if token:
self.customer.update_card(token)

def form_valid(self, form):
data = {"plans": settings.PAYMENTS_PLANS}
self.set_customer()
try:
if self.request.POST.get("stripe_token"):
self.update_card(self.request.POST.get("stripe_token"))
self.customer.subscribe(plan=form.cleaned_data["plan"])
return self.redirect()
except stripe.StripeError as e:
data["form"] = form
data["error"] = smart_str(e) or "Unknown error"
return self.render_to_response(data)

def form_invalid(self, form):
data = {
"error": form.errors,
"form": form
}
return _ajax_response(request, "payments/_change_plan_form.html", **data)
return self.render_to_response(data)

def post(self, request, *args, **kwargs):
form = PlanForm(request.POST)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)


@require_POST
@login_required
def subscribe(request, form_class=PlanForm):
data = {"plans": settings.PAYMENTS_PLANS}
form = form_class(request.POST)
if form.is_valid():
class AjaxCancelSubscription(EldarionAjaxResponseMixin, CustomerMixin, View):

template_fragment = "payments/_cancel_form.html"

def post(self, request, *args, **kwargs):
try:
try:
customer = request.user.customer
except ObjectDoesNotExist:
customer = Customer.create(request.user)
if request.POST.get("stripe_token"):
customer.update_card(request.POST.get("stripe_token"))
customer.subscribe(form.cleaned_data["plan"])
data["form"] = form_class()
data["location"] = reverse("payments_history")
self.customer.cancel()
data = {}
except stripe.StripeError as e:
data["form"] = form
data["error"] = smart_str(e) or "Unknown error"
else:
data["error"] = form.errors
data["form"] = form
return _ajax_response(request, "payments/_subscribe_form.html", **data)


@require_POST
@login_required
def cancel(request):
try:
request.user.customer.cancel()
data = {}
except stripe.StripeError as e:
data = {"error": smart_str(e)}
return _ajax_response(request, "payments/_cancel_form.html", **data)


@csrf_exempt
@require_POST
def webhook(request):
data = json.loads(smart_str(request.body))
if Event.objects.filter(stripe_id=data["id"]).exists():
data = {"error": smart_str(e)}
return self.render_to_response(data)


class Webhook(View):

@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(Webhook, self).dispatch(*args, **kwargs)

def extract_json(self):
data = json.loads(smart_str(self.request.body))
return data

def dupe_exists(self, stripe_id):
return Event.objects.filter(stripe_id=stripe_id).exists()

def log_exception(self, data):
EventProcessingException.objects.create(
data=data,
message="Duplicate event record",
traceback=""
)
else:

def add_event(self, stripe_id, kind, livemode, message):
event = Event.objects.create(
stripe_id=data["id"],
kind=data["type"],
livemode=data["livemode"],
webhook_message=data
stripe_id=stripe_id,
kind=kind,
livemode=livemode,
webhook_message=message
)
event.validate()
event.process()
return HttpResponse()

def post(self, request, *args, **kwargs):
data = self.extract_json()
if self.dupe_exists(data["id"]):
self.log_exception(data)
else:
self.add_event(
stripe_id=data["id"],
kind=data["type"],
livemode=data["livemode"],
message=data
)
return HttpResponse()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def read(*parts):
"Framework :: Django",
],
install_requires=[
"django-eldarion-ajax>=0.1",
"django-jsonfield>=0.9.15",
"stripe>=1.7.9",
"django>=1.7",
Expand Down
12 changes: 7 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ envlist =

[testenv]
deps =
flake8 == 2.4.1
coverage == 3.7.1
flake8 == 2.5.0
coverage == 4.0.2
usedevelop = True
setenv =
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=en_US.UTF-8
commands =
flake8 payments
coverage run setup.py test
coverage report -m
flake8 payments

[testenv:py27-1.7]
basepython = python2.7
Expand All @@ -46,13 +46,15 @@ deps =
[testenv:py32-1.7]
basepython = python3.2
deps =
{[testenv]deps}
flake8 == 2.5.0
coverage == 3.7.1
Django<1.8

[testenv:py32-1.8]
basepython = python3.2
deps =
{[testenv]deps}
flake8 == 2.5.0
coverage == 3.7.1
Django<1.9

[testenv:py33-1.7]
Expand Down

0 comments on commit 09bdab4

Please sign in to comment.