Adding initial Epay integration - far from done.

This commit is contained in:
Víðir Valberg Guðmundsson 2016-05-10 17:55:54 +02:00
parent 9617582b2a
commit bbaf3ea964
11 changed files with 210 additions and 11 deletions

View file

@ -1,5 +1,8 @@
import os import os
import environ
env = environ.Env()
environ.Env.read_env()
def local_dir(entry): def local_dir(entry):
return os.path.join( return os.path.join(
@ -91,3 +94,6 @@ BOOTSTRAP3 = {
'jquery_url': '/static/js/jquery.min.js', 'jquery_url': '/static/js/jquery.min.js',
'javascript_url': '/static/js/bootstrap.min.js' 'javascript_url': '/static/js/bootstrap.min.js'
} }
EPAY_MERCHANT_NUMBER = env('EPAY_MERCHANT_NUMBER')
EPAY_MD5_SECRET = env('EPAY_MD5_SECRET')

View file

@ -7,3 +7,5 @@ EMAIL_HOST_USER='mymailuser'
EMAIL_HOST_PASSWORD='mymailpassword' EMAIL_HOST_PASSWORD='mymailpassword'
EMAIL_USE_TLS=True EMAIL_USE_TLS=True
EMAIL_FROM='noreply@example.com' EMAIL_FROM='noreply@example.com'
EPAY_MERCHANT_NUMBER=something
EPAY_MD5_SECRET=something

View file

@ -8,12 +8,14 @@ class TicketAdmin(admin.ModelAdmin):
list_display = [ list_display = [
'user', 'user',
'ticket_type', 'ticket_type',
'payment_method',
'paid', 'paid',
] ]
list_filter = [ list_filter = [
'paid', 'paid',
'ticket_type', 'ticket_type',
'payment_method',
] ]

View file

@ -8,9 +8,14 @@ class TicketForm(forms.ModelForm):
model = Ticket model = Ticket
fields = [ fields = [
'ticket_type', 'ticket_type',
'payment_method',
] ]
widgets = {
'payment_method': forms.RadioSelect()
}
ticket_type = forms.ModelChoiceField( ticket_type = forms.ModelChoiceField(
queryset=TicketType.objects.available() queryset=TicketType.objects.available()
) )

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-08 11:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0003_auto_20160506_2016'),
]
operations = [
migrations.AddField(
model_name='ticket',
name='payment_method',
field=models.CharField(choices=[('credit_card', 'Credit card'), ('altcoin', 'Altcoin'), ('bank_transfer', 'Bank transfer')], default='credit_card', max_length=50),
),
]

View file

@ -1,7 +1,8 @@
from django.db import models from django.db import models
from django.contrib.postgres.fields import DateTimeRangeField from django.contrib.postgres.fields import DateTimeRangeField, JSONField
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.core.urlresolvers import reverse_lazy
from bornhack.utils import CreatedUpdatedModel, UUIDModel from bornhack.utils import CreatedUpdatedModel, UUIDModel
@ -31,12 +32,33 @@ class Ticket(CreatedUpdatedModel, UUIDModel):
verbose_name=_('Ticket type'), verbose_name=_('Ticket type'),
) )
CREDIT_CARD = 'credit_card'
ALTCOIN = 'altcoin'
BANK_TRANSFER = 'bank_transfer'
PAYMENT_METHODS = [
(CREDIT_CARD, 'Credit card'),
(ALTCOIN, 'Altcoin'),
(BANK_TRANSFER, 'Bank transfer'),
]
payment_method = models.CharField(
max_length=50,
choices=PAYMENT_METHODS,
default=CREDIT_CARD
)
def __str__(self): def __str__(self):
return '{} ({})'.format( return '{} ({})'.format(
self.ticket_type.name, self.ticket_type.name,
self.ticket_type.camp self.ticket_type.camp
) )
def get_absolute_url(self):
return reverse_lazy('ticket:detail', kwargs={
'pk': self.pk
})
class TicketType(CreatedUpdatedModel, UUIDModel): class TicketType(CreatedUpdatedModel, UUIDModel):
class Meta: class Meta:
@ -74,3 +96,20 @@ class TicketType(CreatedUpdatedModel, UUIDModel):
def is_available(self): def is_available(self):
now = timezone.now() now = timezone.now()
return now in self.available_in return now in self.available_in
class EpayCallback(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = 'Epay Callback'
verbose_name_plural = 'Epay Callbacks'
payload = JSONField()
class EpayPayment(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = 'Epay Payment'
verbose_name_plural = 'Epay Payments'
ticket = models.OneToOneField('tickets.Ticket')
callback = models.ForeignKey('tickets.EpayCallback')
txnid = models.IntegerField()

View file

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
<article>
<script charset="UTF-8" src="https://ssl.ditonlinebetalingssystem.dk/integration/ewindow/paymentwindow.js" type="text/javascript"></script>
<script type="text/javascript">
paymentwindow = new PaymentWindow({
'merchantnumber': "{{ merchant_number }}",
'description': "{{ description }}",
'instantcapture': 1,
'ownreceipt': 1,
'amount': "{{ amount }}",
'currency': "DKK",
'orderid': "{{ order_id }}",
'accepturl': "{{ accept_url }}",
'hash': "{{ epay_hash }}",
});
</script>
<input onclick="javascript: paymentwindow.open()" type="button" value="Go to payment">
<script type="text/javascript">
$( document ).ready(function() {
paymentwindow.open();
});
</script>
</article>
{% endblock content %}

View file

@ -36,7 +36,9 @@ Here you can see the different ticket types, their prices and availability.
{% endif %} {% endif %}
<td> <td>
{% if ticket_type.is_available %} {% if ticket_type.is_available %}
<a href="{% url 'tickets:buy' %}?ticket_type={{ ticket_type.pk }}">Buy</a> <a href="{% url 'tickets:order' %}?ticket_type={{ ticket_type.pk }}">
Order
</a>
{% else %} {% else %}
N/A N/A
{% endif %} {% endif %}
@ -63,7 +65,7 @@ Here you can see the different ticket types, their prices and availability.
{% endfor %} {% endfor %}
</table> </table>
<a href="{% url 'tickets:buy' %}" class="btn btn-success">Buy tickets</a> <a href="{% url 'tickets:order' %}" class="btn btn-success">Order tickets</a>
{% else %} {% else %}
<a href="{% url 'account_signup' %}?next={% url 'tickets:index' %}">Sign up</a> or <a href="{% url 'account_signup' %}?next={% url 'tickets:index' %}">Sign up</a> or

View file

@ -1,9 +1,15 @@
from django.conf.urls import url from django.conf.urls import url
from .views import BuyTicketView, TicketIndexView, TicketDetailView from .views import (
TicketIndexView,
TicketOrderView,
TicketDetailView,
EpayView,
)
urlpatterns = [ urlpatterns = [
url(r'buy/$', BuyTicketView.as_view(), name='buy'), url(r'order/$', TicketOrderView.as_view(), name='order'),
url(r'pay/credit_card/(?P<ticket_id>[a-zA-Z0-9\-]+)/$', EpayView.as_view(), name='epay_form'),
url( url(
r'detail/(?P<pk>[a-zA-Z0-9\-]+)/$', r'detail/(?P<pk>[a-zA-Z0-9\-]+)/$',
TicketDetailView.as_view(), TicketDetailView.as_view(),

View file

@ -1,12 +1,15 @@
import hashlib
from django.http import HttpResponseRedirect, Http404 from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render from django.views.generic import CreateView, TemplateView, DetailView, View
from django.views.generic import CreateView, TemplateView, DetailView
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse
from camps.models import Camp from camps.models import Camp
from .models import Ticket, TicketType from .models import Ticket, TicketType, EpayPayment, EpayCallback
from .forms import TicketForm from .forms import TicketForm
@ -33,13 +36,13 @@ class TicketDetailView(LoginRequiredMixin, CampTicketSaleCheck, DetailView):
context_object_name = 'ticket' context_object_name = 'ticket'
class BuyTicketView(LoginRequiredMixin, CampTicketSaleCheck, CreateView): class TicketOrderView(LoginRequiredMixin, CampTicketSaleCheck, CreateView):
model = Ticket model = Ticket
template_name = "tickets/buy.html" template_name = "tickets/order.html"
form_class = TicketForm form_class = TicketForm
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(BuyTicketView, self).get_form_kwargs() kwargs = super(TicketOrderView, self).get_form_kwargs()
ticket_type = self.request.GET.get('ticket_type', None) ticket_type = self.request.GET.get('ticket_type', None)
if ticket_type: if ticket_type:
kwargs['initial'] = { kwargs['initial'] = {
@ -52,9 +55,96 @@ class BuyTicketView(LoginRequiredMixin, CampTicketSaleCheck, CreateView):
instance = form.save(commit=False) instance = form.save(commit=False)
instance.user = self.request.user instance.user = self.request.user
instance.save() instance.save()
if instance.payment_method == Ticket.ALTCOIN:
return HttpResponse('Altcoin')
if instance.payment_method == Ticket.CREDIT_CARD:
return HttpResponseRedirect(
reverse_lazy('tickets:epay_form', kwargs={
'ticket_id': str(instance.pk)
})
)
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy('tickets:detail', kwargs={ reverse_lazy('tickets:detail', kwargs={
'pk': str(instance.pk) 'pk': str(instance.pk)
}) })
) )
class EpayView(TemplateView):
template_name = 'tickets/epay_form.html'
def get_context_data(self, **kwargs):
ticket = Ticket.objects.get(pk=kwargs).select_related('ticket_type')
accept_url = ticket.get_absolute_url()
amount = ticket.ticket_type.price * 100
order_id = str(ticket.pk)
description = str(ticket.user.pk)
hashstring = (
'{merchantnumber}{description}11{amount}DKK'
'{order_id}{accept_url}{md5_secret}'
).format(
settings.EPAY_MERCHANT_NUMBER,
description,
str(amount),
str(order_id),
accept_url,
settings.EPAY_MD5_SECRET,
)
epay_hash = hashlib.md5(hashstring).hexdigest()
context = super(EpayView, self).get_context_data(**kwargs)
context['merchant_number'] = settings.EPAY_MERCHANT_NUMBER
context['description'] = description
context['order_id'] = order_id
context['accept_url'] = accept_url
context['amount'] = amount
context['epay_hash'] = epay_hash
return context
class EpayCallbackView(View):
def get(self, request, **kwargs):
callback = EpayCallback.objects.create(
payload=request.GET
)
if 'orderid' in request.GET:
ticket = Ticket.objects.get(pk=request.GET.get('order_id'))
query = dict(
map(
lambda x: tuple(x.split('=')),
request.META['QUERY_STRING'].split('&')
)
)
hashstring = (
'{merchantnumber}{description}11{amount}DKK'
'{order_id}{accept_url}{md5_secret}'
).format(
query.get('merchantnumber'),
query.get('description'),
query.get('amount'),
query.get('order_id'),
query.get('accept_url'),
settings.EPAY_MD5_SECRET,
)
epay_hash = hashlib.md5(hashstring).hexdigest()
if not epay_hash == request.GET['hash']:
return HttpResponse(status=400)
EpayPayment.objects.create(
ticket=ticket,
callback=callback,
txnid=request.GET['txnid'],
)
else:
return HttpResponse(status=400)
return HttpResponse('OK')