.. highlight:: python
   :linenothreshold: 5

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 :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.

.. warning:: We changed our payment provider API a lot in pretix 2.x. Our documentation page on :ref:`payment2.0`
             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 ``pretix.base.payment.BasePaymentProvider``
that the plugin will provide:

.. code-block:: python

    from django.dispatch import receiver

    from pretix.base.signals import register_payment_providers


    @receiver(register_payment_providers, dispatch_uid="payment_paypal")
    def register_payment_provider(sender, **kwargs):
        from .payment import Paypal
        return Paypal


The provider class
------------------

.. py:class:: pretix.base.payment.BasePaymentProvider

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

   .. py:attribute:: BasePaymentProvider.event

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

   .. py:attribute:: 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.

   .. autoattribute:: identifier

      This is an abstract attribute, you **must** override this!

   .. autoattribute:: verbose_name

      This is an abstract attribute, you **must** override this!

   .. autoattribute:: public_name

   .. autoattribute:: confirm_button_name

   .. autoattribute:: is_enabled

   .. autoattribute:: priority

   .. autoattribute:: settings_form_fields

   .. autoattribute:: walletqueries

   .. automethod:: settings_form_clean

   .. automethod:: settings_content_render

   .. automethod:: is_allowed

   .. automethod:: payment_form_render

   .. automethod:: payment_form

   .. autoattribute:: payment_form_fields

   .. automethod:: payment_is_valid_session

   .. automethod:: checkout_prepare

   .. automethod:: checkout_confirm_render

      This is an abstract method, you **must** override this!

   .. automethod:: execute_payment

   .. automethod:: calculate_fee

   .. automethod:: order_pending_mail_render

   .. automethod:: payment_pending_render

   .. autoattribute:: abort_pending_allowed

   .. automethod:: render_invoice_text

   .. automethod:: render_invoice_stamp

   .. automethod:: order_change_allowed

   .. automethod:: payment_prepare

   .. automethod:: payment_control_render

   .. automethod:: payment_control_render_short

   .. automethod:: payment_refund_supported

   .. automethod:: payment_partial_refund_supported

   .. automethod:: payment_presale_render

   .. automethod:: execute_refund

   .. automethod:: refund_control_render

   .. automethod:: refund_control_render_short

   .. automethod:: new_refund_control_form_render

   .. automethod:: new_refund_control_form_process

   .. automethod:: api_payment_details

   .. automethod:: api_refund_details

   .. automethod:: matching_id

   .. automethod:: refund_matching_id

   .. automethod:: shred_payment_info

   .. automethod:: cancel_payment

   .. autoattribute:: is_implicit

   .. autoattribute:: is_meta

   .. autoattribute:: execute_payment_needs_user

   .. autoattribute:: multi_use_supported

   .. autoattribute:: test_mode_message

   .. autoattribute:: requires_invoice_immediately


Additional views
----------------

See also: :ref:`customview`.

For most simple payment providers it is more than sufficient to implement
some of the :py:class:`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
:py:meth:`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:

.. code-block:: python

    @login_required
    def success(request):
        pid = request.GET.get('paymentId')
        payer = request.GET.get('PayerID')
        # We stored some information in the session in checkout_prepare(),
        # let's compare the new information to double-check that this is about
        # the same payment
        if pid == request.session['payment_paypal_id']:
            # Save the new information to the user's session
            request.session['payment_paypal_payer'] = payer
            try:
                # Redirect back to the confirm page. We chose to save the
                # event ID in the user's session. We could also put this
                # information into a URL parameter.
                event = Event.objects.current.get(identity=request.session['payment_paypal_event'])
                return redirect(reverse('presale:event.checkout.confirm', kwargs={
                    'event': event.slug,
                    'organizer': event.organizer.slug,
                }))
            except Event.DoesNotExist:
                pass  # TODO: Display error message
        else:
            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 :py:meth:`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.
