diff --git a/src/pretix/base/migrations/0015_auto_20150308_0953.py b/src/pretix/base/migrations/0015_auto_20150308_0953.py
new file mode 100644
index 0000000000..993c63e277
--- /dev/null
+++ b/src/pretix/base/migrations/0015_auto_20150308_0953.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0014_auto_20150305_2310'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='order',
+ name='payment_provider',
+ field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Payment provider'),
+ ),
+ migrations.AlterField(
+ model_name='order',
+ name='datetime',
+ field=models.DateTimeField(verbose_name='Date'),
+ ),
+ ]
diff --git a/src/pretix/base/migrations/0016_auto_20150308_1017.py b/src/pretix/base/migrations/0016_auto_20150308_1017.py
new file mode 100644
index 0000000000..2967974276
--- /dev/null
+++ b/src/pretix/base/migrations/0016_auto_20150308_1017.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0015_auto_20150308_0953'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='order',
+ name='code',
+ field=models.CharField(max_length=16, verbose_name='Order code', default=''),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='payment_fee',
+ field=models.DecimalField(max_digits=10, verbose_name='Payment method fee', decimal_places=2, default=0),
+ preserve_default=False,
+ ),
+ ]
diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py
index a3c5099585..0524ad8721 100644
--- a/src/pretix/base/models.py
+++ b/src/pretix/base/models.py
@@ -1,6 +1,7 @@
from itertools import product
import copy
import uuid
+import random
import time
from django.db import models
@@ -1219,8 +1220,9 @@ class Order(Versionable):
expiration date: If items run out of capacity, orders which are over
their expiration date might be cancelled.
- Important: An order holds its total monetary value, as an order is a
- piece of 'history' and must not change due to a change in item prices.
+ An order -- like all objects -- has an ID, which is globally unique,
+ but also a code, which is shorter and easier to memorize, but only
+ unique among a single conference.
"""
STATUS_PENDING = "n"
@@ -1234,6 +1236,10 @@ class Order(Versionable):
(STATUS_CANCELLED, _("cancelled")),
)
+ code = models.CharField(
+ max_length=16,
+ verbose_name=_("Order code")
+ )
status = models.CharField(
max_length=3,
choices=STATUS_CHOICE,
@@ -1248,7 +1254,6 @@ class Order(Versionable):
verbose_name=_("User")
)
datetime = models.DateTimeField(
- auto_now_add=True,
verbose_name=_("Date")
)
expires = models.DateTimeField(
@@ -1258,6 +1263,15 @@ class Order(Versionable):
verbose_name=_("Payment date"),
null=True, blank=True
)
+ payment_provider = models.CharField(
+ null=True, blank=True,
+ max_length=255,
+ verbose_name=_("Payment provider")
+ )
+ payment_fee = models.DecimalField(
+ decimal_places=2, max_digits=10,
+ verbose_name=_("Payment method fee")
+ )
payment_info = models.TextField(
verbose_name=_("Payment information"),
null=True, blank=True
@@ -1271,6 +1285,30 @@ class Order(Versionable):
verbose_name = _("Order")
verbose_name_plural = _("Orders")
+ def str(self):
+ return self.full_code
+
+ @property
+ def full_code(self):
+ """
+ A order code which is unique among all events of a single organizer,
+ built by contatenating the event slug and the order code.
+ """
+ return self.event.slug.upper() + self.code
+
+ def save(self, *args, **kwargs):
+ if not self.code:
+ self.assign_code()
+ super().save(*args, **kwargs)
+
+ def assign_code(self):
+ charset = list('ABCDEFGHKLMNPQRSTUVWXYZ23456789')
+ while True:
+ code = "".join([random.choice(charset) for i in range(5)])
+ if not Order.objects.filter(event=self.event, code=code).exists():
+ self.code = code
+ return
+
class QuestionAnswer(Versionable):
"""
diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py
index be9af95a13..48f9e0e9e7 100644
--- a/src/pretix/base/payment.py
+++ b/src/pretix/base/payment.py
@@ -1,5 +1,9 @@
from decimal import Decimal
+from django.forms import Form
+from django.template import Context
+from django.template.loader import get_template
+
from pretix.base.settings import SettingsSandbox
@@ -50,6 +54,92 @@ class BasePaymentProvider:
def settings_form_fields(self) -> dict:
"""
A dictionary. The keys should be (unprefixed) EventSetting keys,
- the values should be corresponding django form fields
+ the values should be corresponding django form fields.
+
+ We suggest returning a collections.OrderedDict object instead of a dict.
"""
raise NotImplementedError()
+
+ @property
+ def checkout_form_fields(self) -> dict:
+ """
+ A dictionary. The keys should be unprefixed field names,
+ the values should be corresponding django form fields.
+
+ We suggest returning a collections.OrderedDict object instead of a dict.
+ """
+ # TODO: Proper handling of required=True fields in HTML
+ return {}
+
+ def checkout_form(self, request) -> Form:
+ """
+ Returns the Form object of the form that should be displayed when the
+ user selects this provider as his payment method.
+ """
+ form = Form(
+ data=(request.POST if request.method == 'POST' else None),
+ prefix='payment_%s' % self.identifier,
+ initial={
+ k.replace('payment_%s_' % self.identifier, ''): v
+ for k, v in request.session.items()
+ if k.startswith('payment_%s_' % self.identifier)
+ }
+ )
+ form.fields = self.checkout_form_fields
+ return form
+
+ def checkout_form_render(self, request) -> str:
+ """
+ Returns the HTML of the form that should be displayed when the user
+ selects this provider as his payment method.
+ """
+ form = self.checkout_form(request)
+ template = get_template('pretixpresale/event/checkout_payment_form_default.html')
+ ctx = Context({'request': request, 'form': form})
+ return template.render(ctx)
+
+ def checkout_prepare(self, request, total) -> "bool|HttpResponse":
+ """
+ Will be called if the user selects this provider as his payment method.
+ If the payment provider provides a form to the user to enter payment data,
+ this method should at least store the user's input into his session.
+
+ It should return True or False, depending of the validity of the user's input,
+ if the frontend should continue with default behaviour, or a redirect URL,
+ if you need special behaviour.
+
+ On errors, it should use Django's message framework to display an error message
+ to the user (or the normal form validation error messages).
+
+ :param total: The total price of the order, including the payment method fee.
+ """
+ form = self.checkout_form(request)
+ if form.is_valid():
+ for k, v in form.cleaned_data.items():
+ request.session['payment_%s_%s' % (self.identifier, k)] = v
+ return True
+ else:
+ return False
+
+ def checkout_is_valid_session(self, request) -> bool:
+ """
+ This is called at the time the user tries to place the order. It should return
+ True, if the user's session is valid and all data your payment provider requires
+ in future steps is present.
+ """
+ raise NotImplementedError()
+
+ def checkout_perform(self, request, order) -> str:
+ """
+ Will be called if the user submitted his order successfully to initiate the
+ payment process.
+
+ It should return a custom redirct URL, if you need special behaviour, or None to
+ continue with default behaviour.
+
+ On errors, it should use Django's message framework to display an error message
+ to the user (or the normal form validation error messages).
+
+ :param order: The order object
+ """
+ return None
diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py
index 156656b877..97019616f9 100644
--- a/src/pretix/base/settings.py
+++ b/src/pretix/base/settings.py
@@ -10,6 +10,7 @@ DEFAULTS = {
'max_items_per_order': '10',
'attendee_names_asked': 'True',
'attendee_names_required': 'False',
+ 'reservation_time': '30',
}
diff --git a/src/pretix/control/templates/pretixcontrol/event/payment.html b/src/pretix/control/templates/pretixcontrol/event/payment.html
index c965ace7fe..dec5bf91bd 100644
--- a/src/pretix/control/templates/pretixcontrol/event/payment.html
+++ b/src/pretix/control/templates/pretixcontrol/event/payment.html
@@ -24,6 +24,8 @@
{% bootstrap_form provider.form layout='horizontal' %}
+ {% empty %}
+ {% trans "There are no payment providers available. Please go to the plugin settings and activate one or more payment plugins." %}
{% endfor %}
{% blocktrans trimmed %}
+ After completing your purchase, we will ask you to transfer the money to the following
+ bank account, using a personal reference code.
+{% endblocktrans %}