We don't only operate on tickets - renaming and refactoring accordingly

This commit is contained in:
Víðir Valberg Guðmundsson 2016-05-10 22:20:01 +02:00
parent b00b3f3156
commit 5cfcf13e7d
31 changed files with 634 additions and 439 deletions

View file

@ -26,7 +26,7 @@ INSTALLED_APPS = [
'profiles',
'camps',
'tickets',
'shop',
'allauth',
'allauth.account',

View file

@ -37,8 +37,8 @@
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% if current_camp.ticket_sale_open %}
<li><a href="{% url 'tickets:index' %}">Tickets</a></li>
{% if current_camp.shop_open %}
<li><a href="{% url 'shop:index' %}">Shop</a></li>
{% endif %}
<li><a href="{% url 'good-to-know' %}">Good to know</a></li>
{% if user.is_authenticated %}

View file

@ -36,8 +36,8 @@ urlpatterns = [
include('profiles.urls', namespace='profiles')
),
url(
r'^tickets/',
include('tickets.urls', namespace='tickets')
r'^shop/',
include('shop.urls', namespace='shop')
),
url(r'^accounts/', include('allauth.urls')),

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-05-10 20:11
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('camps', '0004_camp_ticket_sale_open'),
]
operations = [
migrations.RemoveField(
model_name='camp',
name='ticket_sale_open',
),
migrations.AddField(
model_name='camp',
name='shop_open',
field=models.BooleanField(default=False, help_text='Whether the shop is open or not.', verbose_name='Shop open?'),
),
]

View file

@ -30,9 +30,9 @@ class Camp(CreatedUpdatedModel, UUIDModel):
unique=True,
)
ticket_sale_open = models.BooleanField(
verbose_name=_('Ticket sale open?'),
help_text=_('Whether tickets are for sale or not.'),
shop_open = models.BooleanField(
verbose_name=_('Shop open?'),
help_text=_('Whether the shop is open or not.'),
default=False,
)

46
shop/admin.py Normal file
View file

@ -0,0 +1,46 @@
from django.contrib import admin
from .models import Order, ProductCategory, Product, OrderProductRelation
@admin.register(ProductCategory)
class ProductCategoryAdmin(admin.ModelAdmin):
list_display = [
'name',
]
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = [
'name',
'category',
'price',
'available_in',
]
class ProductInline(admin.TabularInline):
model = OrderProductRelation
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = [
'user',
'camp',
'payment_method',
'paid',
]
list_filter = [
'user',
'camp',
'payment_method',
'paid',
]
exclude = ['products']
inlines = [ProductInline]

5
shop/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ShopConfig(AppConfig):
name = 'shop'

218
shop/epayintegration.py Normal file
View file

@ -0,0 +1,218 @@
### epay callback function
@require_safe
def api_epay_callback(request):
### save GET payload
callback = EpayCallback.objects.create(
json_payload=json.dumps(request.GET),
callback_time=timezone.now(),
)
### find order
if 'orderid' in request.GET:
### find order type
if request.GET['orderid'][0] == "M":
### find epay order
try:
order = EpayOrder.objects.get(id=request.GET['orderid'][1:])
except EpayOrder.DoesNotExist:
print "epay callback - epayorder %s not found" % request.GET['orderid']
return HttpResponse("Not OK")
### check hash here
if 'hash' not in request.GET:
print "epay callback - missing epay hash"
return HttpResponse("Not OK")
### this does not work sometimes, ordering is off maybe?
hashstring = ''
qs = request.META['QUERY_STRING']
print "querystring is %s" % qs
for kv in qs.split("&"):
print "hashstring is now %s" % hashstring
if kv.split("=")[0] != "hash":
hashstring += kv.split("=")[1]
print "hashstring is now %s" % hashstring
hashstring += settings.EPAY_MD5_SECRET
epayhash = hashlib.md5(hashstring).hexdigest()
if epayhash != request.GET['hash']:
print "epay callback - wrong epay hash"
return HttpResponse("Not OK")
### save callback in epayorder
order.epay_callback = callback
if 'txnid' in request.GET:
order.epay_txnid=request.GET['txnid']
if 'amount' in request.GET:
order.epay_amount=int(request.GET['amount'])/100
if 'fraud' in request.GET:
if request.GET['fraud'] == '1':
order.epay_fraud=True
else:
order.epay_fraud=False
### delay epayorder depending on user level
if settings.ACCOUNT_LEVEL_SETTINGS[str(order.user.profile.account_level)]['MOBILEPAY_DELAY_DAYS'] > 0:
order.btc_processing_delayed = True
### all ok?
if order.epay_amount == order.xxx_amount and not order.epay_fraud:
order.epay_payment_ok=True
### save
order.save()
else:
print "epay callback - order %s not recognized" % request.GET['orderid']
return HttpResponse("Not OK")
return HttpResponse("OK")
### epay order function
@login_required
def epay_order(request):
if 'HTTP_X_FORWARDED_FOR' in request.META:
ip = request.META['HTTP_X_FORWARDED_FOR']
else:
ip = request.META['REMOTE_ADDR']
country = GeoIP().country(ip)["country_code"]
if country not in settings.PERMITTED_EPAY_COUNTRIES:
return render(request, 'epay_sepa_only.html')
### get provision percent
provision = get_provision_percent(request.user, "epayorder")
### convert BTC price to DKK
btc_usd_price = CurrencyPrice.objects.get(item='BTCUSD').price
btc_dkk_price = ConvertCurrency(btc_usd_price, 'USD', 'DKK')
### add provision
btc_dkk_price_with_provision = btc_dkk_price * provision
### instantiate form
form = EpayOrderForm(request.POST or None, request=request, initial={
'currency': request.user.profile.preferred_currency,
'btc_address': request.user.profile.btc_address,
})
if form.is_valid():
### Check exhange rates
check_if_exchange_rates_are_too_old()
### save order
try:
### save epay order with commit=False
epayorder = form.save(commit=False)
except Exception as E:
print "unable to save epay order with commit=false"
return render(request, 'epay_order_fail.html', {
'message': _('Unable to save epay order. Please try again, and please contact us if the problem persists.')
})
### set useragent and IP
epayorder.useragent = request.META['HTTP_USER_AGENT']
epayorder.ip = ip
### create time
epayorder.create_time = timezone.now()
### set order user
epayorder.user = request.user
### save DKK amount
epayorder.dkk_amount = ConvertCurrency(epayorder.xxx_amount, epayorder.currency, 'DKK')
### set fee
epayorder.fee = settings.ACCOUNT_LEVEL_SETTINGS[str(epayorder.user.profile.account_level)]['MOBILEPAY_PROVISION']
### save epayorder
epayorder.save()
### save btc_address to profile if needed
if not request.user.profile.btc_address:
request.user.profile.btc_address = epayorder.btc_address
request.user.profile.save()
send_amqp_message("epayorder M%s created by user %s" % (epayorder.id, request.user), "epayorder.create")
return HttpResponseRedirect(reverse('epay_epay_form', kwargs={'epayorderid': epayorder.id}))
### calculate BTC price for the users native currency
btc_xxx_price = ConvertCurrency(amount=btc_usd_price, fromcurrency="USD", tocurrency=request.user.profile.preferred_currency or "EUR")
### add provision
btc_xxx_price = btc_xxx_price * provision
### find out if new orders by this user should be delayed
if settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level)]['MOBILEPAY_DELAY_DAYS'] == 0:
skipdelay = True
else:
skipdelay = False
### delay relevant?
if settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level)]['MOBILEPAY_DELAY_DAYS'] > 0:
nextlevel_history_delay = settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level+1)]['BUY_HISTORY_DELAY_DAYS']
nextlevel_history_amount = settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level+1)]['BUY_HISTORY_MINIMUM']
else:
nextlevel_history_delay = None
nextlevel_history_amount = None
### render the response
return render(request, 'epay_order.html', {
'form': form,
'btc_xxx_price': round(btc_xxx_price, 2),
'currency': request.user.profile.preferred_currency or "EUR",
'provision': round((provision-1)*100, 1),
'skipdelay': skipdelay,
'delaydays': settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level)]['MOBILEPAY_DELAY_DAYS'],
'daily_limit': settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level)]['MOBILEPAY_LIMIT_DAY'],
'nextlevel_history_delay': nextlevel_history_delay,
'nextlevel_history_amount': nextlevel_history_amount,
'btc_address': request.user.profile.btc_address,
})
### the page which shows the epay epay form
@login_required
def epay_epay_form(request, epayorderid):
epayorder = get_object_or_404(EpayOrder, id=epayorderid, user=request.user, user_cancelled=False, epay_callback__isnull=True)
accepturl = request.build_absolute_uri(reverse('epay_thanks', kwargs={'epayorderid': epayorder.id})).replace('http://', 'https://')
description = str(request.user.id)
hashstring = settings.EPAY_MERCHANT_NUMBER+description+'11'+str(epayorder.xxx_amount*100)+str(epayorder.currency)+'M'+str(epayorder.id)+accepturl+settings.EPAY_MD5_SECRET
epayhash = hashlib.md5(hashstring).hexdigest()
return render(request, 'epay_form.html', {
'orderid': 'M%s' % epayorder.id,
'description': description,
'merchantnumber': settings.EPAY_MERCHANT_NUMBER,
'amount': epayorder.xxx_amount*100,
'currency': epayorder.currency,
'epayhash': epayhash,
'accepturl': accepturl,
})
### after epay payment thanks page
@login_required
def epay_thanks(request, epayorderid):
### get order - if it is owned by this user and uncancelled
epayorder = get_object_or_404(EpayOrder, id=epayorderid, user=request.user, user_cancelled=False)
### check for querystring
if request.GET:
### update order to register thanks page view
epayorder.thanks_page_view_time=timezone.now()
epayorder.save()
### redirect to get rid of GET querystring
return HttpResponseRedirect(reverse('epay_thanks', kwargs={'epayorderid': epayorder.id}))
if epayorder.btc_processing_delayed:
minamount = ConvertLimit(settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level+1)]['BUY_HISTORY_MINIMUM'], epayorder.currency)
delaydays = settings.ACCOUNT_LEVEL_SETTINGS[str(request.user.profile.account_level)]['MOBILEPAY_DELAY_DAYS']
else:
minamount = None
delaydays = None
return render(request, 'epay_thanks.html', {
'order': epayorder,
'delaydays': delaydays,
'minamount': minamount,
})

1
shop/forms.py Normal file
View file

@ -0,0 +1 @@
from django import forms

View file

@ -4,7 +4,7 @@ from django.db.models import QuerySet
from django.utils import timezone
class TicketTypeQuerySet(QuerySet):
class ProductQuerySet(QuerySet):
def available(self):
now = timezone.now()

View file

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-05-10 20:00
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.postgres.fields.jsonb
import django.contrib.postgres.fields.ranges
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('camps', '0004_camp_ticket_sale_open'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='EpayCallback',
fields=[
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('payload', django.contrib.postgres.fields.jsonb.JSONField()),
],
options={
'verbose_name': 'Epay Callback',
'verbose_name_plural': 'Epay Callbacks',
},
),
migrations.CreateModel(
name='EpayPayment',
fields=[
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('txnid', models.IntegerField()),
('callback', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.EpayCallback')),
],
options={
'verbose_name': 'Epay Payment',
'verbose_name_plural': 'Epay Payments',
},
),
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('paid', models.BooleanField(default=False, help_text='Whether this order has been paid.', verbose_name='Paid?')),
('payment_method', models.CharField(choices=[(b'credit_card', b'Credit card'), (b'altcoin', b'Altcoin'), (b'bank_transfer', b'Bank transfer')], default=b'credit_card', max_length=50)),
('camp', models.ForeignKey(help_text='The camp this order is for.', on_delete=django.db.models.deletion.CASCADE, to='camps.Camp', verbose_name='Camp')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='OrderProductRelation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField()),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.Order')),
],
),
migrations.CreateModel(
name='Product',
fields=[
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=150)),
('price', models.IntegerField(help_text='Price of the ticket (in DKK).')),
('description', models.TextField()),
('available_in', django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='Which period is this ticket available for purchase? | (Format: YYYY-MM-DD HH:MM) | Only one of start/end is required')),
],
options={
'ordering': ['available_in'],
'verbose_name': 'Product',
'verbose_name_plural': 'Products',
},
),
migrations.CreateModel(
name='ProductCategory',
fields=[
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=150)),
],
options={
'verbose_name': 'Product category',
'verbose_name_plural': 'Product categories',
},
),
migrations.AddField(
model_name='product',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.ProductCategory'),
),
migrations.AddField(
model_name='orderproductrelation',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.Product'),
),
migrations.AddField(
model_name='order',
name='products',
field=models.ManyToManyField(through='shop.OrderProductRelation', to='shop.Product'),
),
migrations.AddField(
model_name='order',
name='user',
field=models.ForeignKey(help_text='The user this order belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AddField(
model_name='epaypayment',
name='order',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='shop.Order'),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-05-10 20:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shop', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='orderproductrelation',
name='handed_out',
field=models.BooleanField(default=False),
),
]

View file

@ -2,34 +2,36 @@ from django.db import models
from django.contrib.postgres.fields import DateTimeRangeField, JSONField
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.core.urlresolvers import reverse_lazy
from bornhack.utils import CreatedUpdatedModel, UUIDModel
from .managers import TicketTypeQuerySet
from .managers import ProductQuerySet
class Ticket(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = _('Ticket')
verbose_name_plural = _('Tickets')
class Order(CreatedUpdatedModel):
products = models.ManyToManyField(
'shop.Product',
through='shop.OrderProductRelation'
)
user = models.ForeignKey(
'auth.User',
verbose_name=_('User'),
help_text=_('The user this ticket belongs to.'),
related_name='tickets',
help_text=_('The user this order belongs to.'),
related_name='orders',
)
paid = models.BooleanField(
verbose_name=_('Paid?'),
help_text=_('Whether the user has paid.'),
help_text=_('Whether this order has been paid.'),
default=False,
)
ticket_type = models.ForeignKey(
'tickets.TicketType',
verbose_name=_('Ticket type'),
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('The camp this order is for.'),
)
CREDIT_CARD = 'credit_card'
@ -48,44 +50,42 @@ class Ticket(CreatedUpdatedModel, UUIDModel):
default=CREDIT_CARD
)
def __str__(self):
return '{} ({})'.format(
self.ticket_type.name,
self.ticket_type.camp
)
def get_absolute_url(self):
return reverse_lazy('tickets:detail', kwargs={
'pk': self.pk
})
class TicketType(CreatedUpdatedModel, UUIDModel):
class ProductCategory(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = _('Ticket Type')
verbose_name_plural = _('Ticket Types')
ordering = ['available_in']
verbose_name = 'Product category'
verbose_name_plural = 'Product categories'
name = models.CharField(max_length=150)
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('The camp this ticket type is for.'),
)
def __str__(self):
return self.name
class Product(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = 'Product'
verbose_name_plural = 'Products'
ordering = ['available_in']
category = models.ForeignKey('shop.ProductCategory')
name = models.CharField(max_length=150)
price = models.IntegerField(
help_text=_('Price of the ticket (in DKK).')
help_text=_('Price of the product (in DKK).')
)
description = models.TextField()
available_in = DateTimeRangeField(
help_text=_(
'Which period is this ticket available for purchase? | '
'Which period is this product available for purchase? | '
'(Format: YYYY-MM-DD HH:MM) | Only one of start/end is required'
)
)
objects = TicketTypeQuerySet.as_manager()
objects = ProductQuerySet.as_manager()
def __str__(self):
return '{} ({} DKK)'.format(
@ -98,6 +98,13 @@ class TicketType(CreatedUpdatedModel, UUIDModel):
return now in self.available_in
class OrderProductRelation(models.Model):
order = models.ForeignKey('shop.Order')
product = models.ForeignKey('shop.Product')
quantity = models.PositiveIntegerField()
handed_out = models.BooleanField(default=False)
class EpayCallback(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = 'Epay Callback'
@ -110,6 +117,6 @@ class EpayPayment(CreatedUpdatedModel, UUIDModel):
verbose_name = 'Epay Payment'
verbose_name_plural = 'Epay Payments'
ticket = models.OneToOneField('tickets.Ticket')
callback = models.ForeignKey('tickets.EpayCallback')
order = models.OneToOneField('shop.Order')
callback = models.ForeignKey('shop.EpayCallback')
txnid = models.IntegerField()

View file

@ -22,21 +22,21 @@ Here you can see the different ticket types, their prices and availability.
<tbody>
{% for ticket_type in ticket_types %}
{% for product in tickets %}
<tr {% if not ticket_type.is_available %}style="color: lightgrey"{%endif%}>
<tr {% if not product.is_available %}style="color: lightgrey"{%endif%}>
<td>
{{ ticket_type.name }}
{{ product.name }}
<td>
{{ ticket_type.price }} DKK
{{ product.price }} DKK
<td>
{{ ticket_type.available_in.lower }}
{% if ticket_type.available_in.upper %}
- {{ ticket_type.available_in.upper }}
{{ product.available_in.lower }}
{% if product.available_in.upper %}
- {{ product.available_in.upper }}
{% endif %}
<td>
{% if ticket_type.is_available %}
<a href="{% url 'tickets:order' %}?ticket_type={{ ticket_type.pk }}">
{% if product.is_available %}
<a href="">
Order
</a>
{% else %}
@ -47,7 +47,7 @@ Here you can see the different ticket types, their prices and availability.
</table>
{% comment %}
{% if user.is_authenticated %}
<hr />
<h3>Your tickets</h3>
@ -71,5 +71,6 @@ Here you can see the different ticket types, their prices and availability.
<a href="{% url 'account_signup' %}?next={% url 'tickets:index' %}">Sign up</a> or
<a href="{% url 'account_login' %}?next={% url 'tickets:index' %}">login</a> to buy tickets.
{% endif %}
{% endcomment %}
{% endblock %}

27
shop/urls.py Normal file
View file

@ -0,0 +1,27 @@
from django.conf.urls import url
from .views import (
ShopIndexView,
# EpayView,
# EpayCallbackView,
)
urlpatterns = [
#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(
#r'epay_callback/',
#EpayCallbackView,
#name='epay_callback'
#),
#url(
#r'detail/(?P<pk>[a-zA-Z0-9\-]+)/$',
#TicketDetailView.as_view(),
#name='detail'
#),
url(r'$', ShopIndexView.as_view(), name='index'),
]

102
shop/views.py Normal file
View file

@ -0,0 +1,102 @@
import hashlib
from django.http import HttpResponseRedirect, Http404
from django.views.generic import CreateView, TemplateView, DetailView, View
from django.core.urlresolvers import reverse_lazy
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse
from .models import Order, Product, EpayCallback, EpayPayment
class ShopIndexView(TemplateView):
template_name = "shop/index.html"
def get_context_data(self, **kwargs):
context = super(ShopIndexView, self).get_context_data(**kwargs)
context['tickets'] = Product.objects.filter(category__name='Tickets')
return context
class ProductDetailView(LoginRequiredMixin, DetailView):
model = Product
template_name = 'product/detail.html'
context_object_name = 'product'
# class EpayView(TemplateView):
# template_name = 'tickets/epay_form.html'
#
# def get_context_data(self, **kwargs):
# ticket = Ticket.objects.get(pk=kwargs.get('ticket_id'))
# accept_url = ticket.get_absolute_url()
# amount = ticket.ticket_type.price * 100
# order_id = str(ticket.pk)
# description = str(ticket.user.pk)
#
# hashstring = (
# '{merchant_number}{description}11{amount}DKK'
# '{order_id}{accept_url}{md5_secret}'
# ).format(
# merchant_number=settings.EPAY_MERCHANT_NUMBER,
# description=description,
# amount=str(amount),
# order_id=str(order_id),
# accept_url=accept_url,
# md5_secret=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 = (
# '{merchant_number}{description}11{amount}DKK'
# '{order_id}{accept_url}{md5_secret}'
# ).format(
# merchant_number=query.get('merchantnumber'),
# description=query.get('description'),
# amount=query.get('amount'),
# order_id=query.get('orderid'),
# accept_url=query.get('accepturl'),
# md5_secret=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')

View file

@ -1,29 +0,0 @@
from django.contrib import admin
from .models import Ticket, TicketType
@admin.register(Ticket)
class TicketAdmin(admin.ModelAdmin):
list_display = [
'user',
'ticket_type',
'payment_method',
'paid',
]
list_filter = [
'paid',
'ticket_type',
'payment_method',
]
@admin.register(TicketType)
class TicketTypeAdmin(admin.ModelAdmin):
list_display = [
'name',
'price',
'available_in',
'camp',
]

View file

@ -1,5 +0,0 @@
from django.apps import AppConfig
class TicketsConfig(AppConfig):
name = 'tickets'

View file

@ -1,21 +0,0 @@
from django import forms
from .models import Ticket, TicketType
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = [
'ticket_type',
'payment_method',
]
widgets = {
'payment_method': forms.RadioSelect()
}
ticket_type = forms.ModelChoiceField(
queryset=TicketType.objects.available()
)

View file

@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-04-22 20:19
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.postgres.fields.ranges
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('camps', '0003_auto_20160422_2019'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Ticket',
fields=[
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('paid', models.BooleanField(default=False, help_text='Whether the user has paid.', verbose_name='Paid?')),
('camp', models.ForeignKey(help_text='The camp this ticket is for.', on_delete=django.db.models.deletion.CASCADE, to='camps.Camp', verbose_name='Camp')),
],
options={
'verbose_name_plural': 'Tickets',
'verbose_name': 'Ticket',
},
),
migrations.CreateModel(
name='TicketType',
fields=[
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=100)),
('price', models.IntegerField(help_text='Price of the ticket (in DKK).')),
('available_in', django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='Which period is this ticket available for purchase?')),
('camp', models.ForeignKey(help_text='The camp this ticket type is for.', on_delete=django.db.models.deletion.CASCADE, to='camps.Camp', verbose_name='Camp')),
],
options={
'verbose_name_plural': 'Ticket Types',
'verbose_name': 'Ticket Type',
},
),
migrations.AddField(
model_name='ticket',
name='ticket_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.TicketType'),
),
migrations.AddField(
model_name='ticket',
name='user',
field=models.ForeignKey(help_text='The user this ticket belongs to.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View file

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-06 16:02
from __future__ import unicode_literals
import django.contrib.postgres.fields.ranges
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tickets', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='ticket',
name='camp',
),
migrations.AlterField(
model_name='ticket',
name='ticket_type',
field=models.ForeignKey(help_text='The type of the ticket.', on_delete=django.db.models.deletion.CASCADE, to='tickets.TicketType', verbose_name='Ticket type'),
),
migrations.AlterField(
model_name='tickettype',
name='available_in',
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='Which period is this ticket available for purchase? | (Format: YYYY-MM-DD HH:MM) | Only one of start/end is required'),
),
migrations.AlterField(
model_name='tickettype',
name='name',
field=models.CharField(max_length=150),
),
]

View file

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-06 20:16
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tickets', '0002_auto_20160506_1602'),
]
operations = [
migrations.AlterModelOptions(
name='tickettype',
options={'ordering': ['available_in'], 'verbose_name': 'Ticket Type', 'verbose_name_plural': 'Ticket Types'},
),
migrations.AlterField(
model_name='ticket',
name='ticket_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.TicketType', verbose_name='Ticket type'),
),
migrations.AlterField(
model_name='ticket',
name='user',
field=models.ForeignKey(help_text='The user this ticket belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View file

@ -1,20 +0,0 @@
# -*- 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,29 +0,0 @@
from django.conf.urls import url
from .views import (
TicketIndexView,
TicketOrderView,
TicketDetailView,
EpayView,
EpayCallbackView,
)
urlpatterns = [
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(
r'epay_callback/',
EpayCallbackView,
name='epay_callback'
),
url(
r'detail/(?P<pk>[a-zA-Z0-9\-]+)/$',
TicketDetailView.as_view(),
name='detail'
),
url(r'$', TicketIndexView.as_view(), name='index'),
]

View file

@ -1,150 +0,0 @@
import hashlib
from django.http import HttpResponseRedirect, Http404
from django.views.generic import CreateView, TemplateView, DetailView, View
from django.core.urlresolvers import reverse_lazy
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse
from camps.models import Camp
from .models import Ticket, TicketType, EpayPayment, EpayCallback
from .forms import TicketForm
class CampTicketSaleCheck(object):
def dispatch(self, *args, **kwargs):
current_camp = Camp.objects.current()
if current_camp and current_camp.ticket_sale_open:
return super(CampTicketSaleCheck, self).dispatch(*args, **kwargs)
raise Http404()
class TicketIndexView(CampTicketSaleCheck, TemplateView):
template_name = "tickets/index.html"
def get_context_data(self, **kwargs):
context = super(TicketIndexView, self).get_context_data(**kwargs)
context['ticket_types'] = TicketType.objects.all()
return context
class TicketDetailView(LoginRequiredMixin, CampTicketSaleCheck, DetailView):
model = Ticket
template_name = 'tickets/detail.html'
context_object_name = 'ticket'
class TicketOrderView(LoginRequiredMixin, CampTicketSaleCheck, CreateView):
model = Ticket
template_name = "tickets/order.html"
form_class = TicketForm
def get_form_kwargs(self):
kwargs = super(TicketOrderView, self).get_form_kwargs()
ticket_type = self.request.GET.get('ticket_type', None)
if ticket_type:
kwargs['initial'] = {
'ticket_type': ticket_type
}
return kwargs
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
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(
reverse_lazy('tickets:detail', kwargs={
'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.get('ticket_id'))
accept_url = ticket.get_absolute_url()
amount = ticket.ticket_type.price * 100
order_id = str(ticket.pk)
description = str(ticket.user.pk)
hashstring = (
'{merchant_number}{description}11{amount}DKK'
'{order_id}{accept_url}{md5_secret}'
).format(
merchant_number=settings.EPAY_MERCHANT_NUMBER,
description=description,
amount=str(amount),
order_id=str(order_id),
accept_url=accept_url,
md5_secret=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 = (
'{merchant_number}{description}11{amount}DKK'
'{order_id}{accept_url}{md5_secret}'
).format(
merchant_number=query.get('merchantnumber'),
description=query.get('description'),
amount=query.get('amount'),
order_id=query.get('orderid'),
accept_url=query.get('accepturl'),
md5_secret=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')