Welcome to django-paypal’s documentation!

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

Note

These docs are for django-paypal 2.0 - 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

If you are using Django < 1.11, you should use django-paypal 0.5.x and refer to its documentation.

You will also need to edit your settings.py, but the specifics depend on whether you are using IPN/PDT/Pro.

In addition, you may need to take some precautions regarding REMOTE_ADDR. In all cases the user’s IP address is recorded when payments are recorded, since this value can be useful in some cases. This value is taken from request.META['REMOTE_ADDR']. In some setups, however, it is possible that this value is incorrect, or may not even validate as an IP address. If it is not a valid IP address, then saving of IPN/PDT/NVP data will fail with a validation error.

Due to the many different ways that systems can be configured, with different proxies etc., correcting REMOTE_ADDR is outside the scope of django-paypal. You are advised to use a custom middleware or a solution like django-xff to ensure that request.META['REMOTE_ADDR'] is correct or at least a valid IP address.

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:

    settings.py:

    #...
    
    INSTALLED_APPS = [
        #...
        'paypal.standard.ipn',
        #...
    ]
    

    For installations on which you want to use the sandbox, set PAYPAL_TEST to True.

    PAYPAL_TEST = True
    
  2. Update the database

  3. Create an instance of the PayPalPaymentsForm in the view where you would like to collect money.

    You must fill a dictionary with the information required to complete the payment, and pass it through the initial parameter when creating the PayPalPaymentsForm.

    Please note: This form is not used like a normal Django form that posts back to a Django view. Rather it is a POST form that has a single button which sends all the data to PayPal. You simply need to call render on the instance in your template to write out the HTML, which includes the <form> tag with the correct endpoint.

    views.py:

    For a full list of variables that can be used in paypal_dict, see PayPal HTML variables documentation.

    Note

    The names of these variables are not the same as the values returned on the IPN object.

    payment.html:

    ...
    <h1>Show me the money!</h1>
    <!-- writes out the form tag automatically -->
    {{ form.render }}
    

    The image used for the button can be customized using the Settings, or by subclassing PayPalPaymentsForm and overriding the get_image method.

  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:

    from django.urls import path, include
    
    urlpatterns = [
        path('paypal/', include("paypal.standard.ipn.urls")),
    ]
    
  5. 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 must check:

      • the payment_status attribute,
      • the business attribute to make sure that the account receiving the payment is the expected one,
      • the amount and currency (see example below),
      • any other attributes relevant for your case
    • 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: yourproject/hooks.py

    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:
            # 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 != "receiver_email@example.com":
                # 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.
    
            # Undertake some action depending upon `ipn_obj`.
            if ipn_obj.custom == "premium_plan":
                price = ...
            else:
                price = ...
    
            if ipn_obj.mc_gross == price and ipn_obj.mc_currency == 'USD':
                ...
        else:
            #...
    
    valid_ipn_received.connect(show_me_the_money)
    

    Remember to ensure that import the hooks file is imported i.e. that you are connecting the signals when your project initializes. The standard way to do this is to create an AppConfig class and add a ready() method, in which you can register your signal handlers or import a module that does this.

    See the IPN/PDT variables documentation for information about attributes on the IPN object that you can use.

  6. You will also need to implement the return and cancel_return views to handle someone returning from PayPal.

    Note that the return view may need @csrf_exempt applied to it, because PayPal may POST to it (depending on the value of the rm parameter and possibly other settings), so it should be a custom view that doesn’t need to handle POSTs otherwise.

    When using PayPal Standard with Subscriptions this is not necessary since PayPal will route the user back to your site via GET.

    For return, 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, in the notify_url, return and cancel_return fields.

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:

  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. While testing, ensure that when you create the PayPalPaymentsForm your receiver email (business parameter) 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.

    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.

    Add the following to your urls.py:

    from django.urls import path, include
    ...
    urlpatterns = [
        path('your_return_url/', your_pdt_return_url_view, name="pdt_return_url"),
        ...
    ]
    

    And then create a view that uses the process_pdt helper function:

    @require_GET
    def your_pdt_return_url_view(request):
        pdt_obj, failed = process_pdt(request)
        context = {"failed": failed, "pdt_obj": pdt_obj}
        if not failed:
    
            # WARNING!
            # Check that the receiver email is the same we previously
            # set on the business field request. (The user could tamper
            # with those fields on payment form before send it to PayPal)
    
            if pdt_obj.receiver_email == "receiver_email@example.com":
    
                # ALSO: for the same reason, you need to check the amount
                # received etc. are all what you expect.
    
                # Do whatever action is needed, then:
                return render(request, 'my_valid_payment_template', context)
        return render(request, 'my_non_valid_payment_template', context)
    

    See the IPN/PDT variables documentation for information about attributes on the PDT object that you can use.

IPN/PDT variables

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

https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNandPDTVariables/

Note

The names of these data variables are not the same as the values that you pass to PayPal - ensure you are looking at the right list!

The IPN/PDT objects are Django models with the same attributes as above, converted to appropriate Python types e.g. Decimal for money values.

Where a variable has multiple values represented with x in the above documentation, the corresponding fields do not exist on the model objects. However, you can still access the data using the posted_data_dict attribute, which returns a dictionary of all data sent by PayPal.

When processing these objects for handling payments, you need to pay particular attention to payment_status (docs). You can use the ST_PP_* constants in paypal.standard.models to help.

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": 'receiver_email@example.com',
    "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": "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()

See PayPal Subscribe button docs.

Using PayPal Standard with Encrypted Buttons

Use this method to encrypt your button so values in the form can’t be tampered with. 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_private.pem 1024
    
  3. Create a public key:

    openssl req -new -key paypal_private.pem -x509 -days 365 -out paypal_public.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_private.pem'
    PAYPAL_PUBLIC_CERT = '/path/to/paypal_public.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 = PayPalEncryptedPaymentsForm(initial=paypal_dict)
        # Works just like before!
        form.render()
    
  9. If you need to use multiple certificates, you can pass the arguments directly to the PayPalEncryptedPaymentsForm as below:

    In views.py:

    from paypal.standard.forms import PayPalEncryptedPaymentsForm
    
    def view_that_asks_for_money(request):
        ...
        # Paypal Certificate Information
        paypal_private_cert = '/path/to/another/paypal_private.pem'
        paypal_public_cert = '/path/to/another/paypal_public.pem'
        paypal_cert = '/path/to/another/paypal_cert.pem'
        paypal_cert_id = 'another-paypal-id'
        # Create the instance.
        form = PayPalEncryptedPaymentsForm(initial=paypal_dict,
             private_cert=paypal_private_cert,
             public_cert=paypal_public_cert,
             paypal_cert=paypal_cert,
             cert_id=paypal_cert_id)
        ...
    

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 Website Payments Pro

Website Payments Pro models and helpers

class paypal.pro.helpers.PayPalWPP

This class wraps the PayPal classic APIs, and sends data using Name-Value Pairs (NVP). The methods all take a params dictionary, the contents of which depend on the API being called. All parameter keys should be passed as lowercase values (unless otherwise specified), not the mixed case/upper case that is shown in PayPal docs.

For API parameters, see the PayPal docs for more information:

The method calls all return a paypal.pro.models.PayPalNVP object on success. If an API call does not return ack=Success or ack=SuccessWithWarning, a PayPalFailure exception is raised. The NVP object is available as an attribute named nvp on this exception object.

__init__(request=None, params=BASE_PARAMS)

Initialize the instance using an optional Django HTTP request object, and an optional parameter dictionary which should contain the keys USER, PWD, SIGNATURE and VERSION. If the parameter dictionary is not supplied, these parameters will be taken from settings PAYPAL_WPP_USER, PAYPAL_WPP_PASSWORD, PAYPAL_WPP_SIGNATURE and the builtin version number.

createBillingAgreement()

The CreateBillingAgreement API operation creates a billing agreement with a PayPal account holder. CreateBillingAgreement is only valid for reference transactions.

from paypal.pro.helpers import PayPalWPP

def create_billing_agreement_view(request):
    wpp = PayPalWPP(request)
    token = request.GET.get('token')
    wpp.createBillingAgreement({'token': token})
createRecurringPaymentsProfile()

The CreateRecurringPaymentsProfile API operation creates a recurring payments profile. You must invoke the CreateRecurringPaymentsProfile API operation for each profile you want to create. The API operation creates a profile and an associated billing agreement.

Note: There is a one-to-one correspondence between billing agreements and recurring payments profiles. To associate a recurring payments profile with its billing agreement, you must ensure that the description in the recurring payments profile matches the description of a billing agreement. For version 54.0 and later, use SetExpressCheckout to initiate creation of a billing agreement.

doDirectPayment()

The DoDirectPayment API Operation enables you to process a credit card payment.

doExpressCheckoutPayment()

The DoExpressCheckoutPayment API operation completes an Express Checkout transaction. If you set up a billing agreement in your SetExpressCheckout API call, the billing agreement is created when you call the DoExpressCheckoutPayment API operation.

The DoExpressCheckoutPayment API operation completes an Express Checkout transaction. If you set up a billing agreement in your SetExpressCheckout API call, the billing agreement is created when you call the DoExpressCheckoutPayment API operation.

doReferenceTransaction()

The DoReferenceTransaction API operation processes a payment from a buyer’s account, which is identified by a previous transaction.

from paypal.pro.helpers import PayPalWPP

def do_reference_transaction_view(request):
    wpp = PayPalWPP(request)
    reference_id = request.POST.get('reference_id')
    amount = request.POST.get('amount')
    wpp.doReferenceTransaction({'referenceid': reference_id, 'amt': amount})
getExpressCheckoutDetails()

The GetExpressCheckoutDetails API operation obtains information about a specific Express Checkout transaction.

getTransactionDetails()

The GetTransactionDetails API operation obtains information about a specific transaction.

manageRecurringPaymentsProfileStatus()

The ManageRecurringPaymentsProfileStatus API operation cancels, suspends, or reactivates a recurring payments profile.

setExpressCheckout()

The SetExpressCheckout API operation initiates an Express Checkout transaction. Returns an PayPalNVP object that has the token saved in the .token attribute.

This token can be converted into a URL to redirect to using the helper function express_endpoint_for_token in this module.

See the SetExpressCheckout docs

updateRecurringPaymentsProfile()

The UpdateRecurringPaymentsProfile API operation updates a recurring payments profile.

paypal.pro.helpers.express_endpoint_for_token(token, commit=False)

Returns the PayPal Express Checkout endpoint for a token. Pass commit=True if you will not prompt for confirmation when the user returns to your site.

class paypal.pro.models.PayPalNVP

This stores the response returned by PayPal for any of the API calls above.

It has fields for all the common values. For other values, you can access response_dict which is a dictionary-like object containing everything PayPal returned.

Website Payments Pro is a version of PayPal that lets you accept payments on your site using server side calls. The branding of this is confusing. It was branded as “Paypal Payments Pro” at one point. Later “PayPal Payments Pro (Payflow Edition)” was introduced, and that was later renamed to “PayPal Payments Pro”, while the old “PayPal Payments Pro” was rebranded to “Website Payments Pro”. It is this older API (not Payflow) that is supported by django-paypal and documented here.

The PayPal Website 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 nvp_handler(nvp):
        # This is passed a PayPalNVP object when payment succeeds.
        # This should do something useful!
        pass
    
    def buy_my_item(request):
        item = {"paymentrequest_0_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
    
        ppp = PayPalPro(
                  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
                  nvp_handler=nvp_handler)
        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:

    from django.urls import path, include
    
    from myproject import views
    
    
    urlpatterns = [
        ...
        path('payment-url/', views.buy_my_item),
        path('paypal/', include('paypal.standard.ipn.urls')),
    ]
    
  7. 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.

If you are testing locally using the WPP sandbox and are having SSL problems, please see issue 145.

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.

  • To run tests just in a single Python environment, do this in your venv:

    pip install -e .
    pip install -r requirements-test.txt
    ./runtests.py
    
  • If you’re testing on Linux, due to m2crypto dependencies you’ll probably need various development header packages installed, plus swig tool.

  • If you’re testing on a Mac, then, as m2crypto uses openssl, the command line should be:

    env LDFLAGS=”-L”$(brew –prefix openssl)”/lib” CFLAGS=”-I”$(brew –prefix openssl)”/include” SWIG_FEATURES=”-cpperraswarn -includeall -I”$(brew –prefix openssl)”/include” tox

Release notes

Version 2.0 (2022-03-25)

  • Better fix for Django 4.0 form rendering, enabling custom subclasses to work.
  • Dropped support for old Python versions (< 3.6) and old Django versions (< 2.2)

Version 1.1.2 (2021-12-13)

  • Fixed Django 4.0 support

Version 1.1.1 (2021-04-08)

  • Corrected PayPal URL used in IPN/PDT forms. This is a correction of the fix in 1.1 for POSTBACK_ENDPOINT, which wrongly changed both the IPN postback URL and the PayPal login URL. The fix introduces a pair of new settings (LOGIN_URL and SANDBOX_LOGIN_URL). The fix also changes the (undocumented) get_endpoint method on the PayPalPaymentsForm to get_login_url(), in case you are overriding that method.

Version 1.1 (2021-03-14)

  • Fix PayPalSharedSecretEncryptedPaymentsForm in Python 3 - thanks Emilio Moretti
  • Dropped Python 3.4 support
  • Fixed some bugs with CreditCard.get_type() due to bad regexes
  • Fixed a bunch of warnings emitted under modern Django
  • Changed default values of POSTBACK_ENDPOINT and SANDBOX_POSTBACK_ENDPOINT to ones now recommended by PayPal.

Version 1.0 (2019-03-22)

  • Dropped support for versions of Django before 1.11
  • Encrypted button corrections
  • .encode() the encrypted result to avoid b’’ decoration under Python 3
  • Fix the encrypted button examples in the documentation to use the encrypted form
  • Fixed issue #206 - DB migration required by Django 2.1
  • Support for almost all deprecated features removed, including:
    • Signals deprecated in v0.2 (see notes below)
    • Not passing nvp_handler to PayPalPro (see notes under 0.2)
    • Using "amt" parameter with SetExpressCheckout and DoExpressCheckoutPayment (see notes under 0.1.4 below)
    • Settings deprecated in v0.4
    • setCustomerBillingAgreement (pre 0.1.3 feature)
    • PAYPAL_RECEIVER_EMAIL (see notes under 0.3)
    • pdt view (see notes under 0.3)
    • sandbox method on forms (see notes under 0.2)

Version 0.5.0

  • Dropped official support for Python 3.3

  • Support for Django 2.0

  • Fixed bug with IPv6 addresses (thanks @alexcrawley)

  • Tidy up and update PayPalPaymentsForm. Specifically:

    • Where possible, remove explicit fields, leaving them to be handled by __init__(), which creates fields as required from the contents of initial.

    • Deprecate field return_url - use field return instead. PayPal expects field return, but Python’s return keyword meant it wasn’t possible to set that field in the class’s definition. Later, code in __init__ was added to handle any value in initial, in particular initial['return']. As the work around which renamed ‘return’ to ‘return_url’ is not necessary, it is now being deprecated. To maintain backwards compatibility initial[‘return_url’] is remapped to initial[‘return’], with a deprecation warning.

      Thanks @JonathanRoach

    • Add cmd choices for _xclick-auto-billing and _xclick-payment-plan.

Version 0.4.1

  • Added forgotten docs file

Version 0.4.0

  • Cleaned up and documented all settings related to button images. Specifically:
    • The default images have been updated to recent ones. This is backwards incompatible if you were relying on the previous (very old) image and had not set PAYPAL_IMAGE in your settings.
    • Removed separate settings for sandbox mode - these only meant more work when configuring, and production looked different from sandbox by default. This is backwards incompatible, but only affects development mode.
    • Names of settings made clearer. The new names are:
      • PAYPAL_BUY_BUTTON_IMAGE (was: PAYPAL_IMAGE)
      • PAYPAL_DONATION_BUTTON_IMAGE (was: PAYPAL_DONATION_IMAGE)
      • PAYPAL_SUBSCRIPTION_BUTTON_IMAGE (was: PAYPAL_SUBSCRIPTION_IMAGE)

Version 0.3.6

  • Version bump due to messed up version numbers in previous release.

Version 0.3.4

  • Use multi certificates with PaypalEncryptedPaymentsForm
  • Fixed issue #166 - regression from 0.2.7 when using USE_TZ=False
  • Django 1.11 compatibility.
  • Added warnings for untested code.

Version 0.3.3

  • Fixed issue #147 - compatibility with Django 1.10

Version 0.3.2

  • Fixed verify method of IPN/PDT so that it can be re-run in the case of a PayPal server error.
  • Added ‘re-verify’ admin action for IPNs.
  • Other IPN admin improvements.
  • IMPORTANT: Removed the undocumented and untested item_check_callable parameter from several IPN and PDT processing functions. You should implement checks in signal handlers like valid_ipn_received or other calling code.
  • Fixed issue #119 - flagged IPNs not excluded from duplicate checking.
  • Fixed issue #126 - documented need to check amount received.

Version 0.3.1

  • Better handling of unknown datetime formats, thanks rebwok, PR #137
  • Added pytz dependency

Version 0.3

  • Dropped support for Django 1.4 and 1.5.
  • Fixed crasher with AmbiguousTimeError.
  • Better logging for paypal.pro.
  • Fixed Django 1.7/1.8 compat for EmailField.
  • Added missing migration for PDT model.
  • Added missing South migrations
  • Fixed max_length of IPN/PDT custom and transaction_subject fields
  • Fixed issue #105 - IPN failure when running under non-English locale
  • Added missing fields option_selection1 and option_selection2 to IPN/PDT
  • IMPORTANT: Deprecated the PAYPAL_RECEIVER_EMAIL setting to allow multiple receiver emails in a single app. This has several consequences for your code, which must be fixed before upgrading to 0.4.x, when this setting will be dropped entirely:
    • When creating a PayPalPaymentsForm you must provide the business field in the initial parameter.
    • Validation of receiver_email must be done in your valid_ipn_received signal handler and your PDT processing view. Take into account the fact that the user can tamper with the form fields before posting them to PayPal.
  • The use of the pdt view for PDT payments is deprecated. Now you should provide your own view and use the process_pdt helper function.

Version 0.2.7

  • Small fix to logging, thanks frankier

Version 0.2.6

  • Small fixes, including not depending on South.

Version 0.2.5

  • Fixed some PayPalIPN DateTimeFields that were not being handled like the rest. Thanks thiagogds for the patch.
  • Fixed PayPalNVP.timestamp field so that it receives timezone-aware datetimes if you have USE_TZ = True

Version 0.2.4

  • Fixed timezone parsing of PalPal data so that PayPalIPN.payment_date and others are handled correctly (if you have USE_TZ = True).

    This does not include a migration to fix old data - see the release notes if you need that.

  • Work-arounds for bugs in the IPN Simulator

  • Other small fixes

Regarding the handling of dates: If you want to fix historic data in your IPN tables, you need to apply a migration like the following:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import pytz
from datetime import datetime
from django.db import migrations
from django.utils import timezone


PAYPAL_DATE_FORMATS = [
    "%H:%M:%S %b. %d, %Y PST",
    "%H:%M:%S %b. %d, %Y PDT",
    "%H:%M:%S %b %d, %Y PST",
    "%H:%M:%S %b %d, %Y PDT",
]


def parse_date(datestring):
    for format in PAYPAL_DATE_FORMATS:
        try:
            return datetime.strptime(datestring, format)
        except (ValueError, TypeError):
            continue


def fix_ipn_dates(apps, schema_editor):
    PayPalIPN = apps.get_model("ipn", "PayPalIPN")

    for ipn in PayPalIPN.objects.all():
        # Need to recreate PayPalIPN.posted_data_dict
        posted_data_dict = None
        if ipn.query:
            from django.http import QueryDict
            roughdecode = dict(item.split('=', 1) for item in ipn.query.split('&'))
            encoding = roughdecode.get('charset', None)
            if encoding is not None:
                query = ipn.query.encode('ascii')
                data = QueryDict(query, encoding=encoding)
                posted_data_dict = data.dict()
        if posted_data_dict is None:
            continue

        for field in ['time_created', 'payment_date', 'next_payment_date', 'subscr_date', 'subscr_effective',
                      'retry_at', 'case_creation_date', 'auction_closing_date']:
            if field in posted_data_dict:
                raw = posted_data_dict[field]
                naive = parse_date(raw)
                if naive is not None:
                    aware = timezone.make_aware(naive, pytz.timezone('US/Pacific'))
                    setattr(ipn, field, aware)
        ipn.save()


class Migration(migrations.Migration):

    dependencies = [
        ('ipn', '0003_auto_20141117_1647'),
    ]

    operations = [
        migrations.RunPython(fix_ipn_dates,
                             lambda apps, schema_editor: None)  # allowing reverse migration is harmless)
    ]

Version 0.2.3

  • Fixed various deprecation warnings when running under Django 1.8

Version 0.2.2

  • Added ‘commit’ kwarg to express_endpoint_for_token()

Version 0.2.1

  • Added PayPalNVP.response_dict attribute.
  • Added PayPalFailure.nvp attribute to get full info
  • Switched to using requests library for HTTP calls.

Version 0.2

  • Introduced new, less confusing signals, and deprecated the old ones. This is a bit of an API overhaul, but the migration path is clear, don’t worry!

    • IPN:

      Previously, there were IPN signals like payment_was_successful which fired even if the payment_status on the IPN was 'Failed', and there were other signals like payment_was_refunded to cover other specific statuses, but not all of them. There were also bugs that meant that some signals would never fire.

      To sort out all these issues, and to future proof the design, the signals have been reduced to:

      • valid_ipn_received
      • invalid_ipn_received

      The ‘invalid’ signals are sent when the 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.

      The ‘valid’ signals need to be handled. However, you will need to check the payment_status and other attributes to know what to do.

      The old signals still exist and are used, but are deprecated. They will be removed in version 1.0.

      Please see Using PayPal Standard IPN.

    • Pro:

      This used signals even though they weren’t really appropriate.

      Instead:

      • If you are using PayPalWPP directly, the returned PayPalNVP objects from all method should just be used. Remember that you need to handle PayPalFailure exceptions from all direct calls.
      • If you are using the PayPalPro wrapper, you should pass a callable nvp_handler keyword argument.

      Please see Using Website Payments Pro.

  • You must explicitly set PAYPAL_TEST to True or False in your settings, depending on whether you want production or sandbox PayPal. (The default is True i.e. sandbox mode).

    The sandbox() method on any forms is deprecated. You should use render and set PAYPAL_TEST in your settings instead.

Version 0.1.5

  • Fixed support for custom User model in South migrations

    If you:

    • are using a custom AUTH_USER_MODEL
    • are using the ‘pro’ app
    • installed version 0.1.4 and ran the migrations,

    you will need to reverse the migrations in the ‘pro’ app that were applied when you ran “./manage.py migrate”.

Version 0.1.4

  • New docs!
  • Python 3 support.
  • Django 1.7 support.
  • Support for custom User model via AUTH_USER_MODEL. If you change AUTH_USER_MODEL you will still need to write your own migrations.
  • Support for all possible ‘initial’ options that could be wanted in PayPalStandardForm
  • Support for PayPalPro CreateBillingAgreement method
  • Support for PayPalPro DoReferenceTransaction method
  • Upgraded to PayPal Pro API version 116.0
    • This deprecates the “amt” parameter for SetExpressCheckout and DoExpressCheckoutPayment. paymentrequest_0_amt should be used instead. Use of amt will raise a DeprecationWarning for now.
  • Various bug fixes, refactorings and small features.
  • Removed PDT signals (which were never fired)

Version 0.1.3

  • Missing payment types added
  • Additional signals:
    • payment_was_refunded
    • payment_was_reversed
  • Django 1.6 compatibility
  • Various bug fixes, including:
    • Fixes for non-ASCII characters

Update the database

django-paypal uses the built in Django migrations framework.

To update your database:

./manage.py migrate

If you using or upgrading from much older versions of Django (e.g. before 1.7 which didn’t have a built in migrations framework), please upgrade to django-paypal 0.5.x first and follow the docs found in that version.

Settings

Some settings are documented on other documentation pages. In addition, you can set the following values in your settings.py to customise the behaviour of django-paypal.

PAYPAL_BUY_BUTTON_IMAGE

The URL of the image to be used for ‘buy’ buttons.

PAYPAL_DONATE_BUTTON_IMAGE

The URL of the image to be used for ‘donate’ buttons.

PAYPAL_SUBSCRIPTION_BUTTON_IMAGE

The URL of the image to be used for ‘subscription’ buttons.

Indices and tables