Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tax and tax_rate to forms.py and updated README with debug info. #18

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ Django PayPal
=============


Note
----

This is a fork of `dcramer/django-paypal`. This contains few of the fixes which are currently (as of 16Mar12) in the Pull Request queue of dcramer. I felt they were quite good changes, so I did not wait and created my own version; topped with some of my own fixes.

About
-----

Expand All @@ -15,13 +20,14 @@ PayPal Payments Pro allows you to accept payments on your website. It contains t

There is currently an active discussion over the handling of some of the finer points of the PayPal API and the evolution of this code base - check it out over at [Django PayPal on Google Groups](http://groups.google.com/group/django-paypal).

**Note:** When using this module for production code, then set `PAYPAL_TEST` to `False`. If you do not set this then it is assumed to be `True`! When this flag is enabled then all traffics are directed towards [Paypal Sandbox](https://developer.paypal.com). Make sure you have an account on that and have created some test accounts.

Using PayPal Payments Standard IPN:
-------------------------------

1. Download the code from GitHub:

git clone git://github.com/johnboxall/django-paypal.git paypal
git clone git://github.com/applegrew/django-paypal.git paypal

1. Edit `settings.py` and add `paypal.standard.ipn` to your `INSTALLED_APPS`
and `PAYPAL_RECEIVER_EMAIL`:
Expand Down Expand Up @@ -66,6 +72,8 @@ Using PayPal Payments Standard IPN:
<!-- writes out the form tag automatically -->
{{ form.render }}

**Note:** Do not use `PayPalPaymentsForm` for production code. Instead at least use `PayPalEncryptedPaymentsForm`. (See the section - Using PayPal Payments Standard with Encrypted Buttons). If that is not possible then generate a [hosted button from Paypal](https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ButtonMgrAPIIntro#id093VD0JE0Y4).

1. 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
Expand Down Expand Up @@ -128,6 +136,7 @@ Paypal Payment Data Transfer (PDT) allows you to display transaction details to
INSTALLED_APPS = (... 'paypal.standard.pdt', ...)

PAYPAL_IDENTITY_TOKEN = "xxx"
PAYPAL_RECEIVER_EMAIL = "[email protected]"

1. Create a view that uses `PayPalPaymentsForm` just like in PayPal IPN.

Expand All @@ -142,6 +151,19 @@ Paypal Payment Data Transfer (PDT) allows you to display transaction details to
(r'^paypal/pdt/', include('paypal.standard.pdt.urls')),
...
)
**Alternatively**, you can use the pdt decorator to work with PDT information in one of your own views.
To do this, add the decorator to one of your views.

# views.py
from paypal.standard.pdt.decorators import pdt

@pdt
def view_func(request, *args, **kwargs):
...
The decorator checks for any GET parameters corresponding to a PDT call and adds the keyword arguments `pdt_active`, `pdt_failed` and `pdt` to the view call.

1. Set `PAYPAL_IGNORE_INVALID_PDT` to `True` to stop saving data about failed transactions. This might save you from an attack of bad inserts.


Using PayPal Payments Standard with Subscriptions:
--------------------------------------------------
Expand Down Expand Up @@ -195,7 +217,7 @@ Use this method to encrypt your button so sneaky gits don't try to hack it. Than

[https://www.paypal.com/us/cgi-bin/webscr?cmd=_profile-website-cert](https://www.paypal.com/us/cgi-bin/webscr?cmd=_profile-website-cert)

[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)
[https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_profile-website-cert](https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_profile-website-cert)

1. Copy your `cert id` - you'll need it in two steps. It's on the screen where
you uploaded your public key.
Expand Down
5 changes: 2 additions & 3 deletions paypal/standard/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ class PayPalSettingsError(Exception):


TEST = getattr(settings, "PAYPAL_TEST", True)


IGNORE_INVALID_PDT = getattr(settings, "PAYPAL_IGNORE_INVALID_PDT", False)
RECEIVER_EMAIL = settings.PAYPAL_RECEIVER_EMAIL


Expand All @@ -15,7 +14,7 @@ class PayPalSettingsError(Exception):
SANDBOX_POSTBACK_ENDPOINT = "https://www.sandbox.paypal.com/cgi-bin/webscr"

# Images
IMAGE = getattr(settings, "PAYPAL_IMAGE", "http://images.paypal.com/images/x-click-but01.gif")
IMAGE = getattr(settings, "PAYPAL_IMAGE", "https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif")
SUBSCRIPTION_IMAGE = getattr(settings, "PAYPAL_SUBSCRIPTION_IMAGE", "https://www.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif")
DONATION_IMAGE = getattr(settings, "PAYPAL_DONATION_IMAGE", "https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif")
SANDBOX_IMAGE = getattr(settings, "PAYPAL_SANDBOX_IMAGE", "https://www.sandbox.paypal.com/en_US/i/btn/btn_buynowCC_LG.gif")
Expand Down
8 changes: 8 additions & 0 deletions paypal/standard/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class PayPalPaymentsForm(forms.Form):
item_name = forms.CharField(widget=ValueHiddenInput())
item_number = forms.CharField(widget=ValueHiddenInput())
quantity = forms.CharField(widget=ValueHiddenInput())
tax_rate = forms.FloatField(widget=ValueHiddenInput())
tax = forms.FloatField(widget=ValueHiddenInput())

# Subscription Related.
a1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Price
Expand Down Expand Up @@ -100,6 +102,12 @@ def __init__(self, button_type="buy", *args, **kwargs):
self.button_type = button_type

def render(self):
if TEST:
return self.sandbox();
else:
return self.renderProd();

def renderProd(self):
return mark_safe(u"""<form action="%s" method="post">
%s
<input type="image" src="%s" border="0" name="submit" alt="Buy it Now" />
Expand Down
11 changes: 7 additions & 4 deletions paypal/standard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.db import models
from django.conf import settings
from paypal.standard.helpers import duplicate_txn_id, check_secret
from paypal.standard.conf import RECEIVER_EMAIL, POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT
from paypal.standard.conf import RECEIVER_EMAIL, POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, IGNORE_INVALID_PDT

ST_PP_ACTIVE = 'Active'
ST_PP_CANCELLED = 'Cancelled'
Expand Down Expand Up @@ -245,8 +245,9 @@ def verify(self, item_check_callable=None):

"""
self.response = self._postback()
self._verify_postback()
if not self.flag:
self._verify_postback()
invalid_paypal_obj = self.flag
if not invalid_paypal_obj:
if self.is_transaction():
if self.payment_status not in self.PAYMENT_STATUS_CHOICES:
self.set_flag("Invalid payment_status. (%s)" % self.payment_status)
Expand All @@ -262,7 +263,9 @@ def verify(self, item_check_callable=None):
# @@@ Run a different series of checks on recurring payments.
pass

self.save()
if not (invalid_paypal_obj and IGNORE_INVALID_PDT):
self.save()

self.send_signals()

def verify_secret(self, form_instance, secret):
Expand Down
80 changes: 80 additions & 0 deletions paypal/standard/pdt/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- encoding: utf-8 -*-

from paypal.standard.pdt.models import PayPalPDT
from paypal.standard.pdt.forms import PayPalPDTForm
from django.views.decorators.http import require_GET

def pdt(dummy=None, item_check_callable=None):
"""Parses out GET parameters corresponding to a paypal PDT request and adds `pdt_active`, `pdt_failed` and `pdt` to the call **kwargs.

Payment data transfer implementation: http://tinyurl.com/c9jjmw

`item_check_callable` (Optional) is a callable that must take an instance of PayPalPDT
as a parameter and return a tuple (False, None) if the item is valid. Should return (True, "reason")
if the item isn't valid. This function should check that `mc_gross`, `mc_currency` `item_name` and
`item_number` are all correct.

`dummy` DO NOT set value for this. So when you want to set value for `item_check_callable` use named param.
So it would be @pdt(item_check_callable=func). When the `dummy` is a callable `f` then it behaves as just @pdt(f).
"""

def inner_pdt(f):
#{
@require_GET
def aux(request, *args, **kwargs):
pdt_obj = None
pdt_active = False
txn_id = request.GET.get('tx', None)
if txn_id is not None:
txn_id = txn_id.strip()
if not txn_id: #i.e. empty txn_id
txn_id = None

failed = False
pdt_duplicate = False
if txn_id is not None:
pdt_active = True
# If an existing transaction with the id tx exists: use it
try:
pdt_obj = PayPalPDT.objects.get(txn_id=txn_id)
pdt_duplicate = True
except PayPalPDT.DoesNotExist:
# This is a new transaction so we continue processing PDT request
pass

if pdt_obj is None:
form = PayPalPDTForm(request.GET)
if form.is_valid():
try:
pdt_obj = form.save(commit=False)
except Exception, e:
error = repr(e)
failed = True
else:
error = form.errors
failed = True

if failed:
pdt_obj = PayPalPDT()
pdt_obj.set_flag("Invalid form. %s" % error)

pdt_obj.initialize(request)

if not failed:
# The PDT object gets saved during verify
pdt_obj.verify(item_check_callable)
else:
pass # we ignore any PDT requests that don't have a transaction id

#pdt_active = True => txn_id was not None
#pdt_failed = True => pdt_obj has invalid data
#pdt_duplicate = True => txn_id is known and already processed. pdt_obj contains that data.
kwargs.update({'pdt_active': pdt_active, 'pdt_failed': failed, 'pdt_obj': pdt_obj, 'pdt_duplicate': pdt_duplicate})
return f(request, *args, **kwargs)

return aux
#}
if hasattr(dummy, '__call__'): #This is to make sure that we can call @pdt without any parenthesis.
return inner_pdt(dummy) #dummy is function now
else:
return inner_pdt
13 changes: 7 additions & 6 deletions paypal/standard/pdt/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.http import QueryDict
from django.utils.http import urlencode
from paypal.standard.models import PayPalStandardBase
from paypal.standard.conf import POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT
from paypal.standard.conf import POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, IGNORE_INVALID_PDT
from paypal.standard.pdt.signals import pdt_successful, pdt_failed

# ### Todo: Move this logic to conf.py:
Expand Down Expand Up @@ -76,11 +76,12 @@ def _verify_postback(self):
except ValueError, e:
pass

qd = QueryDict('', mutable=True)
qd.update(response_dict)
qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info))
pdt_form = PayPalPDTForm(qd, instance=self)
pdt_form.save(commit=False)
if not (self.flag and IGNORE_INVALID_PDT):
qd = QueryDict('', mutable=True)
qd.update(response_dict)
qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info))
pdt_form = PayPalPDTForm(qd, instance=self)
pdt_form.save(commit=False)

def send_signals(self):
# Send the PDT signals...
Expand Down
48 changes: 6 additions & 42 deletions paypal/standard/pdt/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,12 @@
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.views.decorators.http import require_GET
from paypal.standard.pdt.models import PayPalPDT
from paypal.standard.pdt.forms import PayPalPDTForm

from paypal.standard.pdt.decorators import pdt

@require_GET
def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None):
@pdt
def pdt(request, pdt_active=True, pdt_failed=False, pdt_obj=None, template="pdt/pdt.html", context=None):
"""Payment data transfer implementation: http://tinyurl.com/c9jjmw"""
context = context or {}
pdt_obj = None
txn_id = request.GET.get('tx')
failed = False
if txn_id is not None:
# If an existing transaction with the id tx exists: use it
try:
pdt_obj = PayPalPDT.objects.get(txn_id=txn_id)
except PayPalPDT.DoesNotExist:
# This is a new transaction so we continue processing PDT request
pass

if pdt_obj is None:
form = PayPalPDTForm(request.GET)
if form.is_valid():
try:
pdt_obj = form.save(commit=False)
except Exception, e:
error = repr(e)
failed = True
else:
error = form.errors
failed = True

if failed:
pdt_obj = PayPalPDT()
pdt_obj.set_flag("Invalid form. %s" % error)

pdt_obj.initialize(request)

if not failed:
# The PDT object gets saved during verify
pdt_obj.verify(item_check_callable)
else:
pass # we ignore any PDT requests that don't have a transaction id

context.update({"failed":failed, "pdt_obj":pdt_obj})
return render_to_response(template, context, RequestContext(request))
context = context or {}
context.update({"failed":pdt_failed, "pdt_obj":pdt_obj})
return render_to_response(template, context, RequestContext(request))