Writing a payment provider plugin

In this document, we will walk through the creation of a payment provider plugin. This is very similar to creating an export output.

Please read Creating a plugin first, if you haven’t already.

Warning

We changed our payment provider API a lot in eventyay 2.x. Our documentation page on Porting a payment provider from eventyay 1.x to eventyay 2.x might be insightful even if you do not have a payment provider to port, as it outlines the rationale behind the current design.

Provider registration

The payment provider API does not make a lot of usage from signals, however, it does use a signal to get a list of all available payment providers. Your plugin should listen for this signal and return the subclass of eventyay.base.payment.BasePaymentProvider that the plugin will provide:

1from django.dispatch import receiver
2
3from eventyay.base.signals import register_payment_providers
4
5
6@receiver(register_payment_providers, dispatch_uid="payment_paypal")
7def register_payment_provider(sender, **kwargs):
8    from .payment import Paypal
9    return Paypal

The provider class

class eventyay.base.payment.BasePaymentProvider

The central object of each payment provider is the subclass of BasePaymentProvider.

BasePaymentProvider.event

The default constructor sets this property to the event we are currently working for.

BasePaymentProvider.settings

The default constructor sets this property to a SettingsSandbox object. You can use this object to store settings using its get and set methods. All settings you store are transparently prefixed, so you get your very own settings namespace.

identifier

A short and unique identifier for this payment provider.

This is an abstract attribute, you must override this!

verbose_name

A human-readable name for this payment provider.

This is an abstract attribute, you must override this!

public_name

The public name of this payment provider as shown to customers.

is_enabled

Whether this payment provider is enabled.

priority

The priority of this payment provider. Lower values are shown first.

settings_form_fields

A dictionary of form fields for the provider settings.

settings_form_clean(cleaned_data)

Validate the settings form data.

settings_content_render(request)

Render additional content for the settings page.

is_allowed(request, total=None)

Check if this payment provider is allowed for the given request.

payment_form_render(request)

Render the payment form shown during checkout.

payment_form(request)

Return a form instance for payment details.

payment_form_fields

A dictionary of form fields for payment details.

payment_is_valid_session(request)

Check if the payment session is valid.

checkout_prepare(request, cart)

Prepare the checkout process.

checkout_confirm_render(request)

Render content for the checkout confirmation page.

This is an abstract method, you must override this!

execute_payment(request, payment)

Execute the payment.

calculate_fee(price)

Calculate the fee for this payment method.

order_pending_mail_render(order)

Render content for the pending order email.

payment_pending_render(request, payment)

Render content for pending payment status.

abort_pending_allowed

Whether aborting pending payments is allowed.

render_invoice_text(order, payment)

Render text to be shown on the invoice.

order_change_allowed(order)

Check if order changes are allowed for this payment provider.

payment_prepare(request, payment)

Prepare a payment.

payment_control_render(request, payment)

Render control interface for a payment.

payment_control_render_short(payment)

Render short control interface for a payment.

payment_refund_supported(payment)

Check if refunds are supported for this payment.

payment_partial_refund_supported(payment)

Check if partial refunds are supported for this payment.

payment_presale_render(payment)

Render payment information in the presale interface.

execute_refund(refund)

Execute a refund.

refund_control_render(request, refund)

Render control interface for a refund.

new_refund_control_form_render(request, payment)

Render form for creating a new refund.

new_refund_control_form_process(request, payment, form_data)

Process the new refund form.

api_payment_details(payment)

Return payment details for API responses.

matching_id(payment)

Return an identifier for matching payments.

shred_payment_info(payment)

Shred sensitive payment information.

cancel_payment(payment)

Cancel a payment.

is_implicit

Whether this is an implicit payment provider.

is_meta

Whether this is a meta payment provider.

test_mode_message

Message to display when in test mode.

requires_invoice_immediately

Whether this provider requires an invoice to be generated immediately.

Additional views

See also: Creating custom views.

For most simple payment providers it is more than sufficient to implement some of the BasePaymentProvider methods. However, in some cases it is necessary to introduce additional views. One example is the PayPal provider. It redirects the user to a PayPal website in the BasePaymentProvider.checkout_prepare() step of the checkout process and provides PayPal with a URL to redirect back to. This URL points to a view which looks roughly like this:

 1@login_required
 2def success(request):
 3    pid = request.GET.get('paymentId')
 4    payer = request.GET.get('PayerID')
 5    # We stored some information in the session in checkout_prepare(),
 6    # let's compare the new information to double-check that this is about
 7    # the same payment
 8    if pid == request.session['payment_paypal_id']:
 9        # Save the new information to the user's session
10        request.session['payment_paypal_payer'] = payer
11        try:
12            # Redirect back to the confirm page. We chose to save the
13            # event ID in the user's session. We could also put this
14            # information into a URL parameter.
15            event = Event.objects.current.get(identity=request.session['payment_paypal_event'])
16            return redirect(reverse('presale:event.checkout.confirm', kwargs={
17                'event': event.slug,
18                'organizer': event.organizer.slug,
19            }))
20        except Event.DoesNotExist:
21            pass  # TODO: Display error message
22    else:
23        pass  # TODO: Display error message

If you do not want to provide a view of your own, you could even let PayPal redirect directly back to the confirm page and handle the query parameters inside BasePaymentProvider.checkout_is_valid_session(). However, because some external providers (not PayPal) force you to have a constant redirect URL, it might be necessary to define custom views.