working on coinify payments
This commit is contained in:
parent
407c8c31c4
commit
9387b8bdc4
|
@ -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 }}'
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
|
||||||
]
|
]
|
||||||
|
|
135
shop/views.py
135
shop/views.py
|
@ -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')
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue