Adding initial Epay integration - far from done.
This commit is contained in:
parent
9617582b2a
commit
bbaf3ea964
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
20
tickets/migrations/0004_ticket_payment_method.py
Normal file
20
tickets/migrations/0004_ticket_payment_method.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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()
|
||||||
|
|
27
tickets/templates/tickets/epay_form.html
Normal file
27
tickets/templates/tickets/epay_form.html
Normal 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 %}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
102
tickets/views.py
102
tickets/views.py
|
@ -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')
|
||||||
|
|
Loading…
Reference in a new issue