Welcome to django-paypal’s documentation!¶
Django PayPal is a pluggable application that implements with PayPal Payments Standard and Payments Pro.
Nota
These docs are for django-paypal 0.2.5 - please ensure that corresponds to the version you are using!
Contents:
Install¶
Install into a virtualenv using pip:
pip install django-paypal
Or using the latest version from GitHub:
pip install git://github.com/spookylukey/django-paypal.git#egg=django-paypal
Overview¶
Before diving in, a quick review of PayPal’s payment methods is in order! PayPal Payments Standard is the “Buy it Now” buttons you may have seen floating around the internet. Buyers click on the button and are taken to PayPal’s website where they can pay for the product.
After this point, you can get notification of the payment using either Payment Data Transfer (PDT) or Instant Payment Notification (IPN).
For IPN, as soon as PayPal has taken payment details, it sends a message to a configured endpoint on your site in a separate HTTP request which you must handle. It will make multiple attempts for the case of connectivity issues. This method has the disadvantage that the user may arrive back at your site before your site has been notified about the transaction.
For PDT, PayPal redirects the user back to your website with a transaction ID in the query string. This has the disadvantage that if there is some kind of connection issue at this point, you won’t get notification. However, for the success case, you can be sure that the information about the transaction arrives at the same time as the users arrives back at your site.
PayPal Payments Pro allows you to accept payments on your website. It contains two distinct payment flows: Direct Payment allows the user to enter credit card information on your website and pay on your website. Express Checkout sends the user over to PayPal to confirm their payment method before redirecting back to your website for confirmation. PayPal rules state that both methods must be implemented.
More recently, PayPal have implemented newer APIs, including “PayPal Payments Pro (Payflow Edition)”. This is not to be confused with the “Classic” PayPal Payments Pro that is implemented by django-paypal. “Payflow Edition” is not yet supported by django-paypal.
See also:
PayPal Payments Standard¶
Using PayPal Standard IPN¶
Edit
settings.py
and addpaypal.standard.ipn
to yourINSTALLED_APPS
andPAYPAL_RECEIVER_EMAIL
:settings.py:
#... INSTALLED_APPS = [ #... 'paypal.standard.ipn', #... ] #... PAYPAL_RECEIVER_EMAIL = "yourpaypalemail@example.com"
For installations on which you want to use the sandbox, set PAYPAL_TEST to True. Ensure PAYPAL_RECEIVER_EMAIL is set to your sandbox account email too.
Create an instance of the
PayPalPaymentsForm
in the view where you would like to collect money. Callrender
on the instance in your template to write out the HTML.views.py:
from paypal.standard.forms import PayPalPaymentsForm def view_that_asks_for_money(request): # What you want the button to do. paypal_dict = { "business": settings.PAYPAL_RECEIVER_EMAIL, "amount": "10000000.00", "item_name": "name of the item", "invoice": "unique-invoice-id", "notify_url": "https://www.example.com" + reverse('paypal-ipn'), "return_url": "https://www.example.com/your-return-location/", "cancel_return": "https://www.example.com/your-cancel-location/", } # Create the instance. form = PayPalPaymentsForm(initial=paypal_dict) context = {"form": form} return render(request, "payment.html", context)
For a full list of variables that can be used in
paypal_dict
, see PayPal HTML variables documentation.payment.html:
... <h1>Show me the money!</h1> <!-- writes out the form tag automatically --> {{ form.render }}
When someone uses this button to buy something PayPal makes a HTTP POST to your “notify_url”. PayPal calls this Instant Payment Notification (IPN). The view
paypal.standard.ipn.views.ipn
handles IPN processing. To set the correctnotify_url
add the following to yoururls.py
:from django.conf.urls import url, include urlpatterns = [ url(r'^something/paypal/', include('paypal.standard.ipn.urls')), ]
Whenever an IPN is processed a signal will be sent with the result of the transaction.
The IPN signals should be imported from
paypal.standard.ipn.signals
. They are:valid_ipn_received
This indicates a correct, non-duplicate IPN message from PayPal. The handler will receive a
paypal.standard.ipn.models.PayPalIPN
object as the sender. You will need to check thepayment_status
attribute and other attributes to know what action to take.invalid_ipn_received
This is sent when a transaction was flagged - because of a failed check with PayPal, for example, or a duplicate transaction ID. You should never act on these, but might want to be notified of a problem.
Connect the signals to actions to perform the needed operations when a successful payment is received (as described in the Django Signals Documentation).
In the past there were more specific signals, but they were named confusingly, and used inconsistently, and are now deprecated. (See v0.1.5 docs for details)
Example code:
from paypal.standard.models import ST_PP_COMPLETED from paypal.standard.ipn.signals import valid_ipn_received def show_me_the_money(sender, **kwargs): ipn_obj = sender if ipn_obj.payment_status == ST_PP_COMPLETED: # Undertake some action depending upon `ipn_obj`. if ipn_obj.custom == "Upgrade all users!": Users.objects.update(paid=True) else: #... valid_ipn_received.connect(show_me_the_money)
The data variables that are returned on the IPN object are documented here:
You need to pay particular attention to
payment_status
(docs). Use can use theST_PP_*
constants inpaypal.standard.models
to help.You will also need to implement the
return_url
andcancel_return
views to handle someone returning from PayPal. Note that these views need@csrf_exempt
applied to them, because PayPal will POST to them, so they should be custom views that don’t need to handle POSTs otherwise.For
return_url
, you need to cope with the possibility that the IPN has not yet been received and handled by the IPN listener you implemented (which can happen rarely), or that there was some kind of error with the IPN.
Testing¶
If you are attempting to test this in development, using the PayPal sandbox, and
your machine is behind a firewall/router and therefore is not publicly
accessible on the internet (this will be the case for most developer machines),
PayPal will not be able to post back to your view. You will need to use a tool
like https://ngrok.com/ to make your machine publicly accessible, and ensure
that you are sending PayPal your public URL, not localhost
.
Simulator testing¶
The PayPal IPN simulator at https://developer.paypal.com/developer/ipnSimulator has some unfortunate bugs:
it doesn’t send the
encoding
parameter. django-paypal deals with this using a guess.the default ‘payment_date’ that is created for you is in the wrong format. You need to change it to something like:
23:04:06 Feb 02, 2015 PDT
Using PayPal Standard PDT¶
Paypal Payment Data Transfer (PDT) allows you to display transaction details to a customer immediately on return to your site unlike PayPal IPN which may take some seconds. You will need to enable PDT in your PayPal account to use it.
However, PDT also has the disadvantage that you only get one chance to handle the notification - if there is a connection error for your user, the notification will never arrive at your site. For this reason, using PDT with django-paypal is not as well supported as IPN.
To use PDT:
Edit settings.py and add paypal.standard.pdt to your INSTALLED_APPS. Also set PAYPAL_IDENTITY_TOKEN - you can find the correct value of this setting from the PayPal website:
settings.py:
#... INSTALLED_APPS = [ #... 'paypal.standard.pdt', #... ] #... PAYPAL_IDENTITY_TOKEN = "xxx"
For installations on which you want to use the sandbox, set PAYPAL_TEST to True. Ensure PAYPAL_RECEIVER_EMAIL is set to your sandbox account email too.
Create a view that uses PayPalPaymentsForm just like in Using PayPal Standard IPN.
After someone uses this button to buy something PayPal will return the user to your site at your
return_url
with some extra GET parameters.The view
paypal.standard.pdt.views.pdt
handles PDT processing and renders a simple template. It can be used as follows:Add the following to your urls.py:
from django.conf.urls import url, include ... urlpatterns = [ url(r'^paypal/pdt/', include('paypal.standard.pdt.urls')), ... ]
Then specify the
return_url
to use this URL.You will also need to have a
base.html
template with a blockcontent
. This template is inherited by the PDT view template.More than likely, however, you will want to write a custom view that calls
paypal.standard.pdt.views.process_pdt
. This function returns a tuple containing(PDT object, flag)
, where theflag
is True if verification failed.
Using PayPal Standard with Subscriptions¶
For subscription actions, you’ll need to add a parameter to tell it to use the subscription buttons and the command, plus any subscription-specific settings:
views.py:
paypal_dict = {
"cmd": "_xclick-subscriptions",
"business": "your_account@paypal",
"a3": "9.99", # monthly price
"p3": 1, # duration of each unit (depends on unit)
"t3": "M", # duration unit ("M for Month")
"src": "1", # make payments recur
"sra": "1", # reattempt payment on payment error
"no_note": "1", # remove extra notes (optional)
"item_name": "my cool subscription",
"notify_url": "http://www.example.com/your-ipn-location/",
"return_url": "http://www.example.com/your-return-location/",
"cancel_return": "http://www.example.com/your-cancel-location/",
}
# Create the instance.
form = PayPalPaymentsForm(initial=paypal_dict, button_type="subscribe")
# Output the button.
form.render()
Using PayPal Standard with Encrypted Buttons¶
Use this method to encrypt your button so sneaky gits don’t try to hack it. Thanks to Jon Atkinson for the tutorial.
Encrypted buttons require the M2Crypto library:
pip install M2Crypto
Encrypted buttons require certificates. Create a private key:
openssl genrsa -out paypal.pem 1024
Create a public key:
openssl req -new -key paypal.pem -x509 -days 365 -out pubpaypal.pem
Upload your public key to the paypal website (sandbox or live).
https://www.paypal.com/us/cgi-bin/webscr?cmd=_profile-website-cert
https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_profile-website-cert
Copy your
cert id
- you’ll need it in two steps. It’s on the screen where you uploaded your public key.Download PayPal’s public certificate - it’s also on that screen.
Edit your
settings.py
to include cert information:PAYPAL_PRIVATE_CERT = '/path/to/paypal.pem' PAYPAL_PUBLIC_CERT = '/path/to/pubpaypal.pem' PAYPAL_CERT = '/path/to/paypal_cert.pem' PAYPAL_CERT_ID = 'get-from-paypal-website'
Swap out your unencrypted button for a
PayPalEncryptedPaymentsForm
:In views.py:
from paypal.standard.forms import PayPalEncryptedPaymentsForm def view_that_asks_for_money(request): ... # Create the instance. form = PayPalPaymentsForm(initial=paypal_dict) # Works just like before! form.render()