Using PayPal WPS with Cartridge (Mezzanine / Django)

💬 14

I recently built a web site using Mezzanine, a CMS built on top of Django. I decided to go with Mezzanine (which I've never used before) for two reasons: it nicely enhances Django's admin experience (plus it enhances, but doesn't get in the way of, the Django developer experience); and there's a shopping cart app called Cartridge that's built on top of Mezzanine, and for this particular site (a children's art class business in Sydney) I needed shopping cart / e-commerce functionality.

This suite turned out to deliver virtually everything I needed out-of-the-box, with one exception: Cartridge currently lacks support for payment methods that require redirecting to the payment gateway and then returning after payment completion (such as PayPal Website Payments Standard, or WPS). It only supports payment methods where payment is completed on-site (such as PayPal Website Payments Pro, or WPP). In this case, with the project being small and low-budget, I wanted to avoid the overhead of dealing with SSL and on-site payment, so PayPal WPS was the obvious candidate.

Turns out that, with a bit of hackery, making Cartridge play nice with WPS isn't too hard to achieve. Here's how you go about it.

Install dependencies

Note / disclaimer: this section is mostly copied from my Django Facebook user integration with whitelisting article from over two years ago, because the basic dependencies are quite similar.

I'm assuming that you've already got an environment set up, that's equipped for Django development. I.e. you've already installed Python (my examples here are tested on Python 2.7), a database engine (preferably SQLite on your local environment), pip (recommended), and virtualenv (recommended). If you want to implement these examples fully, then as well as a dev environment with these basics set up, you'll also need a server to which you can deploy a Django site, and on which you can set up a proper public domain or subdomain DNS (because the PayPal API won't actually talk to your localhost, it refuses to do that).

You'll also need a PayPal (regular and "sandbox") account, which you will use for authenticating with the PayPal API.

Here are the basic dependencies for the project. I've copy-pasted this straight out of my requirements.txt file, which I install on a virtualenv using pip install -E . -r requirements.txt (I recommend you do the same):

Django==1.6.2
Mezzanine==3.0.9
South==0.8.4
Cartridge==0.9.2
cartridge-payments==0.97.0
-e git+https://github.com/dcramer/django-paypal.git@4d582243#egg=django_paypal
django-uuidfield==0.5.0

Note: for dcramer/django-paypal, which has no versioned releases, I'm using the latest git commit as of writing this. I recommend that you check for a newer commit and update your requirements accordingly. For the other dependencies, you should also be able to update version numbers to latest stable releases without issues (although Mezzanine 3.0.x / Cartridge 0.9.x is only compatible with Django 1.6.x, not Django 1.7.x which is still in beta as of writing this).

Once you've got those dependencies installed, make sure this Mezzanine-specific setting is in your settings.py file:

# If True, the south application will be automatically added to the
# INSTALLED_APPS setting.
USE_SOUTH = True

Then, let's get a new project set up per Mezzanine's standard install:

mezzanine-project myproject
cd myproject
python manage.py createdb
python manage.py migrate --all

(When it asks "Would you like to install an initial demo product and sale?", I've gone with "yes" for my test / demo project; feel free to do the same, if you'd like some products available out-of-the-box with which to test checkout / payment).

This will get the Mezzanine foundations installed for you. The basic configuration of the Django / Mezzanine settings file, I leave up to you. If you have some experience already with Django (and if you've got this far, then I assume that you do), you no doubt have a standard settings template already in your toolkit (or at least a standard set of settings tweaks), so feel free to use it. I'll be going over the settings you'll need specifically for this app, in just a moment.

Fire up ye 'ol runserver, open your browser at http://localhost:8000/, and confirm that the "Congratulations!" default Mezzanine home page appears for you. Also confirm that you can access the admin. And that's the basics set up!

Basic Django / Mezzanine / Cartridge site: default look after install.
Basic Django / Mezzanine / Cartridge site: default look after install.

At this point, you should also be able to test out adding an item to your cart and going to checkout. After entering some billing / delivery details, on the 'payment details' screen it should ask for credit card details. This is the default Cartridge payment setup: we'll be switching this over to PayPal shortly.

Configure Django settings

I'm not too fussed about what else you have in your Django settings file (or in how your Django settings are structured or loaded, for that matter); but if you want to follow along, then you should have certain settings configured per the following guidelines (note: much of these instructions are virtually the same as the cartridge-payments install instructions):

Implement PayPal payment

Here's how you do it:

That should be all you need, in order to get checkout with PayPal WPS working on your site. So, deploy everything that's been done so far to your online server, log in to the Django admin, and for some of the variations for the sample product in the database, add values for "number in stock".

Then, log out of the admin, and navigate to the "shop" section of the site. Try out adding an item to your cart.

Basic Django / Mezzanine / Cartridge site: adding an item to shopping cart.
Basic Django / Mezzanine / Cartridge site: adding an item to shopping cart.

Once on the "your cart" page, continue by clicking "go to checkout". On the "billing details" page, enter sample billing information as necessary, then click "next". On the "payment" page, you should see a single button labelled "pay with pay-pal".

Basic Django / Mezzanine / Cartridge site: 'go to pay-pal' button.
Basic Django / Mezzanine / Cartridge site: 'go to pay-pal' button.

Click the button, and you should be taken to the PayPal (sandbox, unless configured otherwise) payment landing page. For test cases, log in with a PayPal test account, and click 'Pay Now' to try out the process.

Basic Django / Mezzanine / Cartridge site: PayPal payment screen.
Basic Django / Mezzanine / Cartridge site: PayPal payment screen.

If payment is successful, you should see the PayPal confirmation page, saying "thanks for your order". Click the link labelled "return to email@here.com" to return to the Django site. You should see Cartridge's "order complete" page.

Basic Django / Mezzanine / Cartridge site: order complete screen.
Basic Django / Mezzanine / Cartridge site: order complete screen.

And that's it, you're done! You should be able to verify that the IPN callback was triggered, by checking that the "number in stock" has decreased to reflect the item that was just purchased, and by confirming that an order email / confirmation email was received.

Finished process

I hope that this guide is of assistance, to anyone else who's looking to integrate PayPal WPS with Cartridge. The difficulties associated with it are also documented in this mailing list thread (to which I posted a rough version of what I've illustrated in this article). Feel free to leave comments here, and/or in that thread.

Hopefully the hacks necessary to get this working at the moment, will no longer be necessary in the future; it's up to the maintainers of the various projects to get the fixes for these committed. Ideally, the custom signal implementation won't be necessary either in the future: it would be great if Cartridge could work out-of-the-box with PayPal WPS. Unfortunately, the current architecture of Cartridge's payment system simply isn't designed for something like IPN, it only plays nicely with payment methods that keep the user on the Django site the entire time. In the meantime, with the help of this article, you should at least be able to get it working, even if more custom code is needed than what would be ideal.

Post a comment

💬   14 comments

Rad

you have to add paypal receiver email to your settings before adding

'payments.multipayments',
'paypal.standard.ipn',

to installed apps or your migration will fail.

PAYPAL_BUSINESS = 'cartwpstest@blablablaaaaaaa.com'
PAYPAL_RECEIVER_EMAIL = PAYPAL_BUSINESS

Steve

Hey Jeremy,

Just tried this out... orders seem to go through successfully... they're saved in the admin with the correct transaction ID... but cartridge's cart does not empty after the order has gone through... if a user then tries to purchase the item again... paypal then says the invoice has been paid...

Jaza

@Rad: thanks – I've moved the adding of 'payments.multipayments' and 'paypal.standard.ipn' to INSTALLED_APPS, to the bottom of the list in the 'configure settings' section.

@Steve: hmmm, sounds like the IPN callback isn't happening correctly, not sure what's going wrong for you… check that everything is configured correctly on your Django end, and on your PayPal end.

Sandeep kaur

Hey,

Nice share.

Orders are saved successfully. But when the payment is complete in url and I click on return to site, it gives error of 404 (Page not found). Please help get out of it, so that I can successfully download the invoice.

Thanks.

David

Hello Jeremy! Thanks so much for taking the time to share this. I encountered one problem at the end. When purchase is finished and I click "return to seller's shop" Paypal redirects me to http://127.0.0.1:8000/shop/checkout/complete/

Obviously return URL is wrong but I don't know where to correct it. I guess I have to change it here:

PAYPAL_RETURN_URL = lambda cart, uuid, order_form: ('shop_complete', None, None)

but I don't know how.

Would you help me, please?

Kartik

When i do migrate --all it shows following error

django.db.utils.OperationalError: table "pages_page" already exists

Kartik

I also get "Page not found error" when complete the order it save successfully but "unprocessed" so how we complete or process the order in cartridge

Kartik

Hi, i follow your Blog for paypal integration but i developed a digital download site so i want to return name of product from paypal in "complete.html" how we done that?

Please help me,

Thanks in advance

Jaza

@David: re: "Paypal redirects me to http://127.0.0.1:8000/shop/checkout/complete/". Please note that you can't test the checkout / payment process in your local environment (i.e. on 127.0.0.1), you must deploy it to an online server, that has an actual URL (preferably its own domain / subdomain, although just an IP should work too).

PayPal can't send the IPN back to your localhost. You don't need to configure your site's base URL (as far as I'm aware), django-paypal will work it out automatically based on your environment.

@Sandeep, @Kartik: sounds like you may be having the same problem - if you're only trying this locally, you'll need to deploy your code to a VPS or other web-facing server in order to test the process.

Also, please make sure you're using the exact versions of all dependencies, as I specified in my requirements.txt in the article. While I encourage you to use the latest stable version of all packages, I haven't tested all this using versions newer than what I've indicated here, and I cannot guarantee that this whole fragile setup will work with upgraded packages. Feel free to let me know if any changes are needed to the setup for use with different versions of any of the dependencies.

@Kartik: re: "i want to return name of product from paypal in complete.html". Sorry, not sure how you do that, please post to support forums elsewhere if you need further help. Also, can you please stop spamming - you also sent me your comments via the contact form, this is not necessary.

Kartik

Hi, Thanks for reply me, sorry for spamming,

In cartridge when perform transaction and I complete that but still cart is not empty previous item is as it is in cart so, why cart is not clear at transaction complete, and at some times transaction is complete but in admin side in order it makes no effect can't make entry of that why it happens,

Please help and quick reply I am in trouble!,

Thanks in advance

David

Thanks so much Jeremy, this is a greatly helpful article. I deployed to vps. it works now. I changed

order = Order.objects.get(transaction_id=ipn_obj.invoice)

in your payment_complete() function to

order = Order.objects.get(key=s_key)

This fixed the 404 issue

Kartik

Hello Jeremy! i search a lot but can't find solution in cartridge when in goes in complete.html it shows a "Http404- Page Not Found" Error order is also not save in database, I am search in code so I found that in shop/views.py in def complete(request, template="shop/complete.html"): method in this line

def complete(request, template="shop/complete.html"):
    try:
        order = Order.objects.from_request(request)
    except Order.DoesNotExist:
        raise Http404  # From this line it shows 404
    items = order.items.all()

in above line it not get order object so how i solve this problem,

Plase help!!

Thanks in advance!

Jim Smith

Having a bit of trouble with this guide not getting the button to appear at all just keep getting the original card payment screen I have followed your steps apart from mezzanine set-up which I followed the official guide as yours didn't give me a shop option. Any idea what might be the issue.

Ben Brown

Thanks dude this worked first time and saved me a load of time. For anyone else that's interested there is a good way to make these changes permanent. If you use app engine it makes you put all the modules you use in your project directory if you do this for these two it will make deployment easier. But it will make upgrading harder.