Welcome to django-paypal’s documentation!

Django PayPal is a pluggable application that implements with PayPal Payments Standard and Payments Pro.

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

  1. Edit settings.py and add paypal.standard.ipn to your INSTALLED_APPS and PAYPAL_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.

  2. Update the database

  3. Create an instance of the PayPalPaymentsForm in the view where you would like to collect money. Call render 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_to_response("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 }}
    
  4. 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 correct notify_url add the following to your urls.py:

    urlpatterns = patterns('',
        (r'^something/paypal/', include('paypal.standard.ipn.urls')),
    )
    
  5. Whenever an IPN is processed a signal will be sent with the result of the transaction. Connect the signals to actions to perform the needed operations when a successful payment is received.

    The IPN signals should be imported from paypal.standard.ipn.signals.

    There are four signals for basic transactions:

    • payment_was_successful
    • payment_was_flagged
    • payment_was_refunded
    • payment_was_reversed

    And four signals for subscriptions:

    • subscription_cancel - Sent when a subscription is cancelled.
    • subscription_eot - Sent when a subscription expires.
    • subscription_modify - Sent when a subscription is modified.
    • subscription_signup - Sent when a subscription is created.

    Several more exist for recurring payments:

    • recurring_create - Sent when a recurring payment is created.
    • recurring_payment - Sent when a payment is received from a recurring payment.
    • recurring_cancel - Sent when a recurring payment is cancelled.
    • recurring_suspend - Sent when a recurring payment is suspended.
    • recurring_reactivate - Sent when a recurring payment is reactivated.

    Connect to these signals and update your data accordingly. Django Signals Documentation.

    models.py:

    from paypal.standard.ipn.signals import payment_was_successful
    
    def show_me_the_money(sender, **kwargs):
        ipn_obj = sender
        # You need to check 'payment_status' of the IPN
    
        if ipn_obj.payment_status == "Completed":
            # Undertake some action depending upon `ipn_obj`.
            if ipn_obj.custom == "Upgrade all users!":
                Users.objects.update(paid=True)
        else:
            #...
    
    payment_was_successful.connect(show_me_the_money)
    

    The data variables that are return on the IPN object are documented here:

    https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/

    You need to pay particular attention to payment_status.

  6. You will also need to implement the return_url and cancel_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.

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:

  1. 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.

  2. Update the database

  3. Create a view that uses PayPalPaymentsForm just like in Using PayPal Standard IPN.

  4. 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:

    ...
    urlpatterns = patterns('',
        (r'^paypal/pdt/', include('paypal.standard.pdt.urls')),
        ...
    )
    

    Then specify the return_url to use this URL.

    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 the flag 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.

  1. Encrypted buttons require the M2Crypto library:

    pip install M2Crypto
    
  2. Encrypted buttons require certificates. Create a private key:

    openssl genrsa -out paypal.pem 1024
    
  3. Create a public key:

    openssl req -new -key paypal.pem -x509 -days 365 -out pubpaypal.pem
    
  4. 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

  5. Copy your cert id - you’ll need it in two steps. It’s on the screen where you uploaded your public key.

  6. Download PayPal’s public certificate - it’s also on that screen.

  7. 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'
    
  8. 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()
    

Using PayPal Payments Standard with Encrypted Buttons and Shared Secrets

This method uses Shared secrets instead of IPN postback to verify that transactions are legit. PayPal recommends you should use Shared Secrets if:

  • You are not using a shared website hosting service.
  • You have enabled SSL on your web server.
  • You are using Encrypted Website Payments.
  • You use the notify_url variable on each individual payment transaction.

Use postbacks for validation if:

  • You rely on a shared website hosting service
  • You do not have SSL enabled on your web server
  1. Swap out your button for a PayPalSharedSecretEncryptedPaymentsForm:

    In views.py:

    from paypal.standard.forms import PayPalSharedSecretEncryptedPaymentsForm
    
    def view_that_asks_for_money(request):
        ...
        # Create the instance.
        form = PayPalSharedSecretEncryptedPaymentsForm(initial=paypal_dict)
        # Works just like before!
        form.render()
    
  2. Verify that your IPN endpoint is running on SSL - request.is_secure() should return True!

Using PayPal Payments Pro (WPP)

PayPal Payments Pro (or “Website Payments Pro”) is a more awesome version of PayPal that lets you accept payments on your site. This is now documented by PayPal as a Classic API and should not be confused with the “PayPal Payments Pro (Payflow Edition)” which is a newer API.

The PayPal Payments Pro solution reuses code from paypal.standard so you’ll need to include both apps. django-paypal makes the whole process incredibly easy to use through the provided PayPalPro class.

  1. Obtain PayPal Pro API credentials: login to PayPal, click My Account, Profile, Request API credentials, Set up PayPal API credentials and permissions, View API Signature.

  2. Edit settings.py and add paypal.standard and paypal.pro to your INSTALLED_APPS and put in your PayPal Pro API credentials.

    INSTALLED_APPS = [
        # ..
        'paypal.standard',
        'paypal.pro',
    ]
    PAYPAL_TEST = True
    PAYPAL_WPP_USER = "???"
    PAYPAL_WPP_PASSWORD = "???"
    PAYPAL_WPP_SIGNATURE = "???"
    
  3. Update the database

  4. Write a wrapper view for paypal.pro.views.PayPalPro:

    In views.py:

    from paypal.pro.views import PayPalPro
    
    def buy_my_item(request):
        item = {"amt": "10.00",             # amount to charge for item
                "inv": "inventory",         # unique tracking variable paypal
                "custom": "tracking",       # custom tracking variable for you
                "cancelurl": "http://...",  # Express checkout cancel url
                "returnurl": "http://..."}  # Express checkout return url
    
        kw = {"item": item,                            # what you're selling
              "payment_template": "payment.html",      # template name for payment
              "confirm_template": "confirmation.html", # template name for confirmation
              "success_url": "/success/"}              # redirect location after success
    
        ppp = PayPalPro(**kw)
        return ppp(request)
    
  5. Create templates for payment and confirmation. By default both templates are populated with the context variable form which contains either a PaymentForm or a Confirmation form.

    payment.html:

    <h1>Show me the money</h1>
    <form method="post" action="">
      {{ form }}
      <input type="submit" value="Pay Up">
    </form>
    

    confirmation.html:

    <!-- confirmation.html -->
    <h1>Are you sure you want to buy this thing?</h1>
    <form method="post" action="">
      {{ form }}
      <input type="submit" value="Yes I Yams">
    </form>
    
  6. Add your view to urls.py, and add the IPN endpoint to receive callbacks from PayPal:

    urlpatterns = ('',
        ...
        (r'^payment-url/$', 'myproject.views.buy_my_item')
        (r'^some/obscure/name/', include('paypal.standard.ipn.urls')),
    )
    
  7. Connect to the provided signals in paypal.pro.signals and have them do something useful:

    • payment_was_successful
    • payment_was_flagged
  8. Profit.

Alternatively, if you want to get down to the nitty gritty and perform some more advanced operations with Payments Pro, use the paypal.pro.helpers.PayPalWPP class directly.

Tests

To run the django-paypal tests:

  • Download the source from GitHub or your fork.

  • Create a virtualenv for the django-paypal project.

  • Install tox:

    pip install tox
    
  • Run tox:

    tox
    

    This will run all the tests on all supported combinations of Django/Python.

Indices and tables