working on coinify payments

This commit is contained in:
Thomas Steen Rasmussen 2016-05-25 22:48:02 +02:00
parent 407c8c31c4
commit 9387b8bdc4
4 changed files with 116 additions and 45 deletions

View file

@ -18,3 +18,5 @@ DATABASES = {
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
LETTERHEAD_PDF_PATH = os.path.join(local_dir('static_src'), 'pdf', 'bornhack_2016_test_letterhead.pdf') LETTERHEAD_PDF_PATH = os.path.join(local_dir('static_src'), 'pdf', 'bornhack_2016_test_letterhead.pdf')
COINIFY_API_KEY = '{{ coinify_api_key }}'
COINIFY_API_SECRET = '{{ coinify_api_secret }}'

View file

@ -91,6 +91,9 @@ class Order(CreatedUpdatedModel):
def get_epay_cancel_url(self, request): def get_epay_cancel_url(self, request):
return 'https://' + request.get_host() + str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk})) return 'https://' + request.get_host() + str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk}))
def get_epay_callback_url(self, request):
return 'https://' + request.get_host() + str(reverse_lazy('shop:epay_callback', kwargs={'pk': self.pk}))
@property @property
def description(self): def description(self):
return "BornHack 2016 order #%s" % self.pk return "BornHack 2016 order #%s" % self.pk
@ -207,3 +210,15 @@ class Invoice(CreatedUpdatedModel):
self.sent_to_customer, self.sent_to_customer,
) )
class CoinifyAPIInvoice(CreatedUpdatedModel):
invoicejson = JSONField()
order = models.OneToOneField('shop.Order')
class CoinifyCallback(CreatedUpdatedModel):
payload = JSONField()
def __str__(self):
return 'callback at %s' % self.created

View file

@ -3,12 +3,19 @@ from views import *
urlpatterns = [ urlpatterns = [
url(r'^$', ShopIndexView.as_view(), name='index'), url(r'^$', ShopIndexView.as_view(), name='index'),
url(r'products/(?P<slug>[-_\w+]+)/$', ProductDetailView.as_view(), name='product_detail'), url(r'products/(?P<slug>[-_\w+]+)/$', ProductDetailView.as_view(), name='product_detail'),
url(r'orders/$', OrderListView.as_view(), name='order_list'), url(r'orders/$', OrderListView.as_view(), name='order_list'),
url(r'orders/(?P<pk>[0-9]+)/$', OrderDetailView.as_view(), name='order_detail'), url(r'orders/(?P<pk>[0-9]+)/$', OrderDetailView.as_view(), name='order_detail'),
url(r'orders/(?P<pk>[0-9]+)/pay/creditcard/$', EpayFormView.as_view(), name='epay_form'), url(r'orders/(?P<pk>[0-9]+)/pay/creditcard/$', EpayFormView.as_view(), name='epay_form'),
url(r'orders/(?P<pk>[0-9]+)/pay/creditcard/callback/$',EpayCallbackView.as_view(), name='epay_callback'),
url(r'orders/(?P<pk>[0-9]+)/pay/creditcard/thanks/$', EpayThanksView.as_view(), name='epay_thanks'), url(r'orders/(?P<pk>[0-9]+)/pay/creditcard/thanks/$', EpayThanksView.as_view(), name='epay_thanks'),
url(r'orders/(?P<pk>[0-9]+)/pay/blockchain/$', CoinifyRedirectView.as_view(), name='coinify_pay'), url(r'orders/(?P<pk>[0-9]+)/pay/blockchain/$', CoinifyRedirectView.as_view(), name='coinify_pay'),
url(r'orders/(?P<pk>[0-9]+)/pay/blockchain/callback/$', CoinifyCallbackView.as_view(), name='coinify_callback'),
url(r'orders/(?P<pk>[0-9]+)/pay/blockchain/thanks/$', CoinifyThanksView.as_view(), name='coinify_thanks'),
url(r'orders/(?P<pk>[0-9]+)/pay/banktransfer/$', BankTransferView.as_view(), name='bank_transfer'), url(r'orders/(?P<pk>[0-9]+)/pay/banktransfer/$', BankTransferView.as_view(), name='bank_transfer'),
url(r'epay_callback/', EpayCallbackView.as_view(), name='epay_callback'),
] ]

View file

@ -3,7 +3,7 @@ from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.db.models import Count, F from django.db.models import Count, F
from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest, Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.generic import ( from django.views.generic import (
View, View,
@ -22,11 +22,13 @@ from shop.models import (
ProductCategory, ProductCategory,
EpayCallback, EpayCallback,
EpayPayment, EpayPayment,
CoinifyAPIInvoice,
) )
from .forms import AddToOrderForm from .forms import AddToOrderForm
from .epay import calculate_epay_hash, validate_epay_callback from .epay import calculate_epay_hash, validate_epay_callback
from collections import OrderedDict from collections import OrderedDict
from vendor.coinify_api import CoinifyAPI from vendor.coinify_api import CoinifyAPI
from vendor.coinify_callback import CoinifyCallback
class EnsureUserOwnsOrderMixin(SingleObjectMixin): class EnsureUserOwnsOrderMixin(SingleObjectMixin):
@ -234,49 +236,6 @@ class ProductDetailView(LoginRequiredMixin, FormView, DetailView):
return Order.objects.get(user=self.request.user, open__isnull=False).get_absolute_url() return Order.objects.get(user=self.request.user, open__isnull=False).get_absolute_url()
class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView):
model = Order
template_name = 'coinify_redirect.html'
def get_context_data(self, **kwargs):
order = self.get_object()
context = super(CoinifyRedirectView, self).get_context_data(**kwargs)
context['order'] = order
# Initiate coinify API and create invoice
coinifyapi = CoinifyAPI(
settings.COINIFY_API_KEY,
settings.COINIFY_API_SECRET
)
response = coinifyapi.invoice_create(
amount,
currency,
plugin_name='BornHack 2016 webshop',
plugin_version='1.0',
description='BornHack 2016 order id #%s' % order.id,
callback_url=reverse_lazy(
'shop:coinfy_callback',
kwargs={'orderid': order.id}
),
return_url=reverse_lazy(
'shop:order_paid',
kwargs={'orderid': order.id}
),
)
if not response['success']:
api_error = response['error']
print "API error: %s (%s)" % (
api_error['message'],
api_error['code']
)
invoice = response['data']
# change this to pass only needed data when we get that far
context['invoice'] = invoice
return context
class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView): class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView):
model = Order model = Order
template_name = 'epay_form.html' template_name = 'epay_form.html'
@ -285,6 +244,7 @@ class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrd
order = self.get_object() order = self.get_object()
accept_url = order.get_epay_accept_url(self.request) accept_url = order.get_epay_accept_url(self.request)
cancel_url = order.get_epay_cancel_url(self.request) cancel_url = order.get_epay_cancel_url(self.request)
callback_url = order.get_epay_callback_url(self.request)
amount = order.total * 100 amount = order.total * 100
epay_hash = calculate_epay_hash(order, self.request) epay_hash = calculate_epay_hash(order, self.request)
@ -296,6 +256,7 @@ class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrd
context['order_id'] = order.pk context['order_id'] = order.pk
context['accept_url'] = accept_url context['accept_url'] = accept_url
context['cancel_url'] = cancel_url context['cancel_url'] = cancel_url
context['callback_url'] = callback_url
context['epay_hash'] = epay_hash context['epay_hash'] = epay_hash
return context return context
@ -364,3 +325,89 @@ class BankTransferView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpai
model = Order model = Order
template_name = 'bank_transfer.html' template_name = 'bank_transfer.html'
class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView):
model = Order
template_name = 'coinify_redirect.html'
def get_context_data(self, **kwargs):
order = self.get_object()
context = super(CoinifyRedirectView, self).get_context_data(**kwargs)
if hasattr(order, 'coinifyapiinvoice'):
# we already have an invoice for this order, just redirect
context['redirecturl'] == order.coinifyapiinvoice.payload['data']['payment_url']
else:
# Initiate coinify API and create invoice
coinifyapi = CoinifyAPI(
settings.COINIFY_API_KEY,
settings.COINIFY_API_SECRET
)
response = coinifyapi.invoice_create(
order.total,
'DKK',
plugin_name='BornHack 2016 webshop',
plugin_version='1.0',
description='BornHack 2016 order id #%s' % order.id,
callback_url=reverse_lazy(
'shop:coinfy_callback',
kwargs={'orderid': order.id}
),
return_url=reverse_lazy(
'shop:coinify_thanks',
kwargs={'orderid': order.id}
),
)
if not response['success']:
api_error = response['error']
print "API error: %s (%s)" % (
api_error['message'],
api_error['code']
)
else:
# save this coinify invoice
CoinifyAPIInvoice.objects.create(
invoicejson = response['data'],
order = order,
)
context['redirecturl'] == response['data']['payment_url']
return context
class CoinifyCallbackView(View):
def post(self, request, *args, **kwargs):
# Get the signature from the HTTP or email headers
signature = request.META['HTTP_X_COINIFY_CALLBACK_SIGNATURE']
sdk = CoinifyCallback(
settings.COINIFY_API_KEY,
settings.COINIFY_API_SECRET
)
if sdk.validate_callback(request.body, signature):
# callback is valid, save it to db
callbackobject = CoinifyCallback.objects.create(
payload=request.body
)
# parse json
callbackjson = json.loads(request.body)
if callbackjson['event'] == 'invoice_state_change' or callbackjson['event'] == 'invoice_manual_resend':
# get invoice from db
try:
invoice = CoinifyAPIInvoice.objects.get(id=callbackjson['data']['id'])
except CoinifyAPIInvoice.DoesNotExist:
return HttpResponseBadRequest('bad invoice id')
# save new invoice payload
invoice.payload = callbackjson['data']
invoice.save()
# so, is the invoice paid now?
if callbackjson['data']['state'] == 'complete':
invoice.order.mark_as_paid()
else:
HttpResponseBadRequest('unsupported event')
else:
HttpResponseBadRequest('something is fucky')