Documentation for the payment provider plugin API

This commit is contained in:
Raphael Michel
2015-03-15 19:48:42 +01:00
parent 41f816388b
commit 13f88346d4
6 changed files with 274 additions and 69 deletions

View File

@@ -3,10 +3,12 @@ from decimal import Decimal
from django import forms
from django.forms import Form
from django.http import HttpRequest
from django.template import Context
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from pretix.base.forms import SettingsForm
from pretix.base.models import Order
from pretix.base.settings import SettingsSandbox
@@ -66,11 +68,30 @@ class BasePaymentProvider:
def settings_form_fields(self) -> dict:
"""
When the event's administrator administrator visits the event configuration
page,
A dictionary. The keys should be (unprefixed) EventSetting keys,
the values should be corresponding django form fields.
page, this method is called to return the configuration fields available.
We suggest returning a collections.OrderedDict object instead of a dict.
It should therefore return a dictionary where the keys should be (unprefixed)
settings keys and the values should be corresponding Django form fields.
The default implementation returns the appropriate fields for the ``_enabled``,
``_fee_abs`` and ``_fee_percent`` settings mentioned above.
We suggest that you return an ``OrderedDict`` object instead of a dictionary
and make use of the default implementation. Your implementation could look
like this::
@property
def settings_form_fields(self):
return OrderedDict(
list(super().settings_form_fields.items()) + [
('bank_details',
forms.CharField(
widget=forms.Textarea,
label=_('Bank account details'),
required=False
))
]
)
"""
return OrderedDict([
('_enabled',
@@ -96,18 +117,20 @@ class BasePaymentProvider:
@property
def checkout_form_fields(self) -> dict:
"""
A dictionary. The keys should be unprefixed field names,
the values should be corresponding django form fields.
This is used by the default implementation of :py:meth:`checkout_form`.
It should return an object similar to :py:attr:`settings_form_fields`.
We suggest returning a collections.OrderedDict object instead of a dict.
The default implementation returns an empty dictionary.
"""
# TODO: Proper handling of required=True fields in HTML
return {}
def checkout_form(self, request) -> Form:
def checkout_form(self, request: HttpRequest) -> Form:
"""
Returns the Form object of the form that should be displayed when the
user selects this provider as his payment method.
This is called by the default implementation of :py:meth:`checkout_form_render`
to obtain the form that is displayed to the user during the checkout
process. The default implementation constructs the form using
:py:attr:`checkout_form_fields` and sets appropriate prefixes for the form
and all fields and fills the form with data form the user's session.
"""
form = Form(
data=(request.POST if request.method == 'POST' else None),
@@ -121,10 +144,15 @@ class BasePaymentProvider:
form.fields = self.checkout_form_fields
return form
def checkout_form_render(self, request) -> str:
def checkout_form_render(self, request: HttpRequest) -> str:
"""
Returns the HTML of the form that should be displayed when the user
selects this provider as his payment method.
When the user selects this provider as his prefered payment method,
he will be shown the HTML you return from this method.
The default implementation will call :py:meth:`checkout_form`
and render the returned form. If your payment method doesn't require
the user to fill out form fields, you should just return a paragraph
of explainatory text.
"""
form = self.checkout_form(request)
template = get_template('pretixpresale/event/checkout_payment_form_default.html')
@@ -133,23 +161,53 @@ class BasePaymentProvider:
def checkout_confirm_render(self, request) -> str:
"""
Returns the HTML that should be displayed when the user selected this provider
on the 'confirm order' page.
If the user successfully filled in his payment data, he will be redirected
to a confirmation page which lists all details of his order for a final review.
This method should return the HTML which should be displayed inside the
'Payment' box on this page.
In most cases, this should include a short summary of the user's input and
a short explaination on how the payment process will continue.
"""
raise NotImplementedError() # NOQA
def checkout_prepare(self, request, cart) -> "bool|HttpResponse":
def checkout_prepare(self, request: HttpRequest, cart: dict) -> "bool|str":
"""
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.
Will be called after the user selected this provider as his payment method.
If you provided 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.
This method should return ``False``, if the user's input was invalid, ``True``
if the input was valid and the frontend should continue with default behaviour
or a string containing an URL, if the user should be redirected somewhere else.
On errors, it should use Django's message framework to display an error message
On errors, you should use Django's message framework to display an error message
to the user (or the normal form validation error messages).
The default implementation stores the input into the form returned by
:py:meth:`checkout_form` in the user's session.
If your payment method requires you to redirect the user to an external provider,
this might be the place to do so.
.. IMPORTANT:: If this is called, the user has not yet confirmed his or her order.
You may NOT do anything which actually moves money.
:param cart: This dictionary contains at least the following keys:
positions:
A list of ``CartPosition`` objects that are annotated with the special
attributes ``count`` and ``total`` because multiple objects of the
same content are grouped into one.
raw:
The raw list of ``CartPosition`` objects in the users cart
total:
The overall total *including* the fee for the payment method.
payment_fee:
The fee for the payment method.
"""
form = self.checkout_form(request)
if form.is_valid():
@@ -159,49 +217,58 @@ class BasePaymentProvider:
else:
return False
def checkout_is_valid_session(self, request) -> bool:
def checkout_is_valid_session(self, request: HttpRequest) -> 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
``True``, if the user's session is valid and all data your payment provider requires
in future steps is present.
"""
raise NotImplementedError() # NOQA
def checkout_perform(self, request, order) -> str:
def checkout_perform(self, request: HttpRequest, order: Order) -> str:
"""
Will be called if the user submitted his order successfully to initiate the
payment process.
After the user confirmed his purchase, this method will be called to complete
the payment process. This is the place to actually move the money, if applicable.
If you need any speical behaviour, you can return a string
containing an URL the user will be redirected to. If you are done with your process
you should return the user to the order's detail page.
It should return a custom redirct URL, if you need special behaviour, or None to
continue with default behaviour.
If the payment is completed, you should call ``order.mark_paid(provider, info)``
with ``provider`` being your :py:attr:`identifier` and ``info`` being any string
you might want to store for later usage. Please note, that if you want to store
something inside ``order.payment_info``, please do a ``order = order.clone()`` before
modifying or saving the order object.
On errors, it should use Django's message framework to display an error message
to the user (or the normal form validation error messages).
The default implementation just returns ``None`` and therefore leaves the
order unpaid. The user will be redirected to the order's detail page by default.
On errors, you should use Django's message framework to display an error message
to the user.
:param order: The order object
"""
return None
def order_pending_render(self, request, order) -> str:
def order_pending_render(self, request: HttpRequest, order: Order) -> str:
"""
Will be called if the user views the detail page of an unpaid order which is
associated with this payment provider.
If the user visits a detail page of an order which has not yet been paid but
this payment method was selected during checkout, this method will be called
to provide HTML content for the 'payment' box on the page.
It should return HTML code which should be displayed to the user. It should contian
instructions on how to continue with the payment process, either in form of text
or buttons/links/etc.
It should contain instructions on how to continue with the payment process,
either in form of text or buttons/links/etc.
:param order: The order object
"""
raise NotImplementedError() # NOQA
def order_paid_render(self, request, order) -> str:
def order_paid_render(self, request: HttpRequest, order: Order) -> str:
"""
Will be called if the user views the detail page of an paid order which is
associated with this payment provider.
It should return HTML code which should be displayed to the user or None,
if there is nothing to say.
if there is nothing to say (like the default implementation does).
:param order: The order object
"""

View File

@@ -5,7 +5,6 @@ from django.contrib import messages
from django.core.urlresolvers import reverse
from django.template import Context
from django.template.loader import get_template
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as __
from django import forms