How to accept Paypal payments on your Django application


Tue 12 January 2021

In this tutorial you’ll learn how to integrate Django and Paypal to accept payments in your Django web application.

Paypal is a popular solution for online payments. Even if nowadays there are many valid alternatives, Paypal is still a big player in the market with a solid reputation and it’s trusted by millions of users.

Here is a simple step-by-step guide on how to integrate the django-paypal third party app on your website.

1. Install the django-paypal app

pip install django-paypal

2. Insert the app in your Django settings

INSTALLED_APPS = (
  ...
  'paypal.standard.ipn',
)

3. Insert a Paypal form on a view

Assuming you are using Django class-based views, you can use a FormView like this:

from django.views.generic import FormView
from django.urls import reverse
from paypal.standard.forms import PayPalPaymentsForm

class PaypalFormView(FormView):
    template_name = 'paypal_form.html'
    form_class = PayPalPaymentsForm

    def get_initial(self):
        return {
            'business': '[email protected]',
            'amount': 20,
            'currency_code': 'EUR',
            'item_name': 'Example item',
            'invoice': 1234,
            'notify_url': self.request.build_absolute_uri(reverse('paypal-ipn')),
            'return_url': self.request.build_absolute_uri(reverse('paypal-return')),
            'cancel_return': self.request.build_absolute_uri(reverse('paypal-cancel')),
            'lc': 'EN',
            'no_shipping': '1',
        }

This is a regular FormView and the template paypal_form.html is a standard Django template like this:

<html>
  <body>
    {{ form.render }}
  </body>
</html>

Notice how we are settings some initial values for the Paypal form. Let us see all the parameters used here:

business
this is your Paypal account email. Payments will be sent to this account, so choose it carefully.
amount
the payment amount.
currency_code
the currency used in the previous amount parameter. Here is a list of accepted currency codes.
item_name
the name of the item that the customer is paying. Choose it carefully, because Paypal will show this name to the user in the payment confirmation page.
invoice
this is an “invoice code” that you can use to match the payment with an object on your database. I suggest to use some sort of primary key for your transaction here.
notify_url
a complete URI where Paypal will send an IPN (Instant Payment Notification). This is a Django endpoint you’ll have to provide and that we’ll see in a moment. Here you’ll receive an HTTP request by the Paypal servers, when the payment will be correctly registered by Paypal.
return_url
a complete URI where the customer will be redirected upon a successful payment.
cancel_return
a complete URI where the customer will be redirected when the payment is cancelled.
lc
The locale code to use in the checkout page. Here is a list of supported locale codes.
no_shipping
Can be set to 1 if you are selling digital goods, that doesn’t require shipping.

Some of this data will likely be dynamic, because it depends on the object you are selling.

4. Provide an URL for Paypal IPN

In your Django urls.py file, add an urlconf like this:

path('paypal/', include('paypal.standard.ipn.urls')),

5. Create views for success and failure of Paypal checkout

You can add two TemplateView to show your user a message upon success or failure of the Paypal checkout.

from django.views.generic import TemplateView

class PaypalReturnView(TemplateView):
    template_name = 'paypal_success.html'

class PaypalCancelView(TemplateView):
    template_name = 'paypal_cancel.html'

Then you can mount these views in your urls.py:

from . import views
urlpatterns = [
    path('/paypal-return/', views.PaypalReturnView.as_view(), name='paypal-return'),
    path('/paypal-cancel/', views.PaypalCancelView.as_view(), name='paypal-cancel'),
    ...
]

The names of the urls should match with those defined in the form initial data, as shown at step 3.

6. Setup a listener to detect successful Paypal payments

When a successful payment if performed on Paypal, you’ll receive a so-called IPN: “Instant Payment Notification” from Paypal.

django-paypal uses the Django signal dispatcher to let you hook your code when a successful payment arrives. You can define a listener like this:

from paypal.standard.models import ST_PP_COMPLETED
from paypal.standard.ipn.signals import valid_ipn_received

@receiver(valid_ipn_received)
def paypal_payment_received(sender, **kwargs):
    ipn_obj = sender
    if ipn_obj.payment_status == ST_PP_COMPLETED:
        # WARNING !
        # Check that the receiver email is the same we previously
        # set on the `business` field. (The user could tamper with
        # that fields on the payment form before it goes to PayPal)
        if ipn_obj.receiver_email != '[email protected]':
            # Not a valid payment
            return

        # ALSO: for the same reason, you need to check the amount
        # received, `custom` etc. are all what you expect or what
        # is allowed.
        try:
            my_pk = ipn_obj.invoice
            mytransaction = MyTransaction.objects.get(pk=my_pk)
            assert ipn_obj.mc_gross == mytransaction.amount and ipn_obj.mc_currency == 'EUR'
        except Exception:
            logger.exception('Paypal ipn_obj data not valid!')
        else:
            mytransaction.paid = True
            mytransaction.save()
    else:
        logger.debug('Paypal payment status not completed: %s' % ipn_obj.payment_status)

Here we assumed that you had a model MyTransaction on your Django application, and that you want to set the transaction as paid when the Paypal payment is confirmed.

That’s it! Following these steps you should have a working Paypal integration in your Django project. Please let me know if you have questions or comments.


Share: