De-vendorizing django-paypal, adding to requirements.txt.

This commit is contained in:
Samuel Clay 2020-06-24 16:59:43 -04:00
parent 540587e01e
commit cdda78d916
46 changed files with 9 additions and 2636 deletions

View file

@ -28,9 +28,8 @@ from utils import json_functions as json
from utils.user_functions import generate_secret_token
from utils.feed_functions import chunks
from vendor.timezones.fields import TimeZoneField
from vendor.paypal.standard.ipn.signals import subscription_signup, payment_was_successful, recurring_payment
from vendor.paypal.standard.ipn.signals import payment_was_flagged
from vendor.paypal.standard.ipn.models import PayPalIPN
from paypal.standard.ipn.signals import valid_ipn_received, invalid_ipn_received
from paypal.standard.ipn.models import PayPalIPN
from vendor.paypalapi.interface import PayPalInterface
from vendor.paypalapi.exceptions import PayPalAPIResponseError
from zebra.signals import zebra_webhook_customer_subscription_created
@ -1078,7 +1077,7 @@ def paypal_signup(sender, **kwargs):
user.profile.activate_premium()
user.profile.cancel_premium_stripe()
user.profile.cancel_premium_paypal(second_most_recent_only=True)
subscription_signup.connect(paypal_signup)
valid_ipn_received.connect(paypal_signup)
def paypal_payment_history_sync(sender, **kwargs):
ipn_obj = sender
@ -1091,7 +1090,7 @@ def paypal_payment_history_sync(sender, **kwargs):
user.profile.setup_premium_history()
except:
return {"code": -1, "message": "User doesn't exist."}
payment_was_successful.connect(paypal_payment_history_sync)
valid_ipn_received.connect(paypal_payment_history_sync)
def paypal_payment_was_flagged(sender, **kwargs):
ipn_obj = sender
@ -1105,7 +1104,7 @@ def paypal_payment_was_flagged(sender, **kwargs):
logging.user(user, "~BC~SB~FBPaypal subscription payment flagged")
except:
return {"code": -1, "message": "User doesn't exist."}
payment_was_flagged.connect(paypal_payment_was_flagged)
invalid_ipn_received.connect(paypal_payment_was_flagged)
def paypal_recurring_payment_history_sync(sender, **kwargs):
ipn_obj = sender
@ -1118,7 +1117,7 @@ def paypal_recurring_payment_history_sync(sender, **kwargs):
user.profile.setup_premium_history()
except:
return {"code": -1, "message": "User doesn't exist."}
recurring_payment.connect(paypal_recurring_payment_history_sync)
valid_ipn_received.connect(paypal_recurring_payment_history_sync)
def stripe_signup(sender, full_json, **kwargs):
stripe_id = full_json['data']['object']['customer']

View file

@ -31,7 +31,7 @@ from utils.view_functions import render_to, is_true
from utils.user_functions import get_user
from utils import log as logging
from vendor.paypalapi.exceptions import PayPalAPIResponseError
from vendor.paypal.standard.forms import PayPalPaymentsForm
from paypal.standard.forms import PayPalPaymentsForm
SINGLE_FIELD_PREFS = ('timezone','feed_pane_size','hide_mobile','send_emails',
'hide_getting_started', 'has_setup_feeds', 'has_found_friends',

View file

@ -13,6 +13,7 @@ django-extensions==1.6.7
django-mailgun==0.9.1
django-oauth-toolkit==0.7.2
django-qurl==0.1.1
django-paypal==1.0
django-redis-cache==1.5.5
django-redis-sessions==0.6.1
django-ses==0.7.1

View file

@ -316,7 +316,7 @@ INSTALLED_APPS = (
'django_extensions',
'djcelery',
# 'kombu.transport.django',
'vendor.paypal.standard.ipn',
'paypal.standard.ipn',
'apps.rss_feeds',
'apps.reader',
'apps.analyzer',

View file

@ -1,354 +0,0 @@
Django PayPal
=============
About
-----
Django PayPal is a pluggable application that implements with PayPal Payments
Standard and Payments Pro.
Before diving in, a quick review of PayPal's payment methods is in order! [PayPal Payments Standard](https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_WebsitePaymentsStandard_IntegrationGuide.pdf) is the "Buy it Now" buttons you may have
seen floating around the internets. Buyers click on the button and are taken to PayPal's website where they can pay for the product. After completing the purchase PayPal makes an HTTP POST to your `notify_url`. PayPal calls this process [Instant Payment Notification](https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_OrderMgmt_IntegrationGuide.pdf) (IPN) but you may know it as [webhooks](http://blog.webhooks.org). This method kinda sucks because it drops your customers off at PayPal's website but it's easy to implement and doesn't require SSL.
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.
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).
Using PayPal Payments Standard IPN:
-------------------------------
1. Download the code from GitHub:
git clone git://github.com/johnboxall/django-paypal.git paypal
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"
1. 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 vendor.paypal.standard.forms import PayPalPaymentsForm
def view_that_asks_for_money(request):
# What you want the button to do.
paypal_dict = {
"business": "yourpaypalemail@example.com",
"amount": "10000000.00",
"item_name": "name of the item",
"invoice": "unique-invoice-id",
"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)
context = {"form": form}
return render_to_response("payment.html", context)
<!-- payment.html -->
...
<h1>Show me the money!</h1>
<!-- writes out the form tag automatically -->
{{ form.render }}
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
correct `notify_url` add the following to your `urls.py`:
# urls.py
...
urlpatterns = patterns('',
(r'^something/hard/to/guess/', include('paypal.standard.ipn.urls')),
)
1. 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 recieved.
There are two signals for basic transactions:
- `payment_was_succesful`
- `payment_was_flagged`
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.
Connect to these signals and update your data accordingly. [Django Signals Documentation](http://docs.djangoproject.com/en/dev/topics/signals/).
# models.py
...
from vendor.paypal.standard.ipn.signals import payment_was_successful
def show_me_the_money(sender, **kwargs):
ipn_obj = sender
# Undertake some action depending upon `ipn_obj`.
if ipn_obj.custom == "Upgrade all users!":
Users.objects.update(paid=True)
payment_was_successful.connect(show_me_the_money)
Using PayPal Payments 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.your PayPal account to use it](https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/howto_html_paymentdatatransfer).
1. Download the code from GitHub:
git clone git://github.com/johnboxall/django-paypal.git paypal
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"
1. Create a view that uses `PayPalPaymentsForm` just like in PayPal IPN.
1. 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. PayPal calls this Payment Data Transfer (PDT).
The view `paypal.standard.pdt.views.pdt` handles PDT processing. to specify the correct
`return_url` add the following to your `urls.py`:
# urls.py
...
urlpatterns = patterns('',
(r'^paypal/pdt/', include('paypal.standard.pdt.urls')),
...
)
Using PayPal Payments Standard with Subscriptions:
-------------------------------
1. 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 Payments Standard with Encrypted Buttons:
------------------------------------------------------
Use this method to encrypt your button so sneaky gits don't try to hack it. Thanks to [Jon Atkinson](http://jonatkinson.co.uk/) for the [tutorial](http://jonatkinson.co.uk/paypal-encrypted-buttons-django/).
1. Encrypted buttons require the `M2Crypto` library:
easy_install M2Crypto
1. Encrypted buttons require certificates. Create a private key:
openssl genrsa -out paypal.pem 1024
1. Create a public key:
openssl req -new -key paypal.pem -x509 -days 365 -out pubpaypal.pem
1. 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.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)
1. Copy your `cert id` - you'll need it in two steps. It's on the screen where
you uploaded your public key.
1. Download PayPal's public certificate - it's also on that screen.
1. Edit your `settings.py` to include cert information:
# settings.py
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'
1. Swap out your unencrypted button for a `PayPalEncryptedPaymentsForm`:
# views.py
from vendor.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`:
# views.py
from vendor.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()
1. Verify that your IPN endpoint is running on SSL - `request.is_secure()` should return `True`!
Using PayPal Payments Pro (WPP)
-------------------------------
WPP is the more awesome version of PayPal that lets you accept payments on your
site. WPP reuses code from `paypal.standard` so you'll need to include both
apps. [There is an explanation of WPP in the PayPal Forums](http://www.pdncommunity.com/pdn/board/message?board.id=wppro&thread.id=192).
1. Edit `settings.py` and add `paypal.standard` and `paypal.pro` to your
`INSTALLED_APPS`, also set your PayPal settings:
# settings.py
...
INSTALLED_APPS = (... 'paypal.standard', 'paypal.pro', ...)
PAYPAL_TEST = True # Testing mode on
PAYPAL_WPP_USER = "???" # Get from vendor.paypal
PAYPAL_WPP_PASSWORD = "???"
PAYPAL_WPP_SIGNATURE = "???"
1. Run `python manage.py migrate` to add the required tables.
1. Write a wrapper view for `paypal.pro.views.PayPalPro`:
# views.py
from vendor.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)
1. 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 -->
<h1>Are you sure you want to buy this thing?</h1>
<form method="post" action="">
{{ form }}
<input type="submit" value="Yes I Yams">
</form>
1. Add your view to `urls.py`, and add the IPN endpoint to receive callbacks
from vendor.paypal:
# urls.py
...
urlpatterns = ('',
...
(r'^payment-url/$', 'myproject.views.buy_my_item')
(r'^some/obscure/name/', include('paypal.standard.ipn.urls')),
)
1. Profit.
Links:
------
1. [Set your IPN Endpoint on the PayPal Sandbox](https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_profile-ipn-notify)
2. [Django PayPal on Google Groups](http://groups.google.com/group/django-paypal)
License (MIT)
=============
Copyright (c) 2009 Handi Mobility Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,2 +0,0 @@
VERSION = (0, 1, 4, 'a', 0)
__version__ = ".".join(map(str, VERSION[0:3])) + "".join(map(str, VERSION[3:]))

View file

View file

@ -1,26 +0,0 @@
from django.conf import settings
class PayPalSettingsError(Exception):
"""Raised when settings be bad."""
RECEIVER_EMAIL = settings.PAYPAL_RECEIVER_EMAIL
# API Endpoints.
POSTBACK_ENDPOINT = "https://www.paypal.com/cgi-bin/webscr"
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")
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")
SUBSCRIPTION_SANDBOX_IMAGE = getattr(settings, "PAYPAL_SUBSCRIPTION_SANDBOX_IMAGE",
"https://www.sandbox.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif")
DONATION_SANDBOX_IMAGE = getattr(settings, "PAYPAL_DONATION_SANDBOX_IMAGE",
"https://www.sandbox.paypal.com/en_US/i/btn/btn_donateCC_LG.gif")

View file

@ -1,242 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django import forms
from django.utils.safestring import mark_safe
from paypal.standard.widgets import ValueHiddenInput, ReservedValueHiddenInput
from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT,
RECEIVER_EMAIL,
IMAGE, SUBSCRIPTION_IMAGE, DONATION_IMAGE,
SANDBOX_IMAGE, SUBSCRIPTION_SANDBOX_IMAGE, DONATION_SANDBOX_IMAGE)
from django.conf import settings
# 20:18:05 Jan 30, 2009 PST - PST timezone support is not included out of the box.
# PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", "%H:%M:%S %b %d, %Y PST",)
# PayPal dates have been spotted in the wild with these formats, beware!
PAYPAL_DATE_FORMAT = ("%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",)
class PayPalPaymentsForm(forms.Form):
"""
Creates a PayPal Payments Standard "Buy It Now" button, configured for a
selling a single item with no shipping.
For a full overview of all the fields you can set (there is a lot!) see:
http://tinyurl.com/pps-integration
Usage:
>>> f = PayPalPaymentsForm(initial={'item_name':'Widget 001', ...})
>>> f.render()
u'<form action="https://www.paypal.com/cgi-bin/webscr" method="post"> ...'
"""
CMD_CHOICES = (
("_xclick", "Buy now or Donations"),
("_donations", "Donations"),
("_cart", "Shopping cart"),
("_xclick-subscriptions", "Subscribe")
)
SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping"))
NO_NOTE_CHOICES = ((1, "No Note"), (0, "Include Note"))
RECURRING_PAYMENT_CHOICES = (
(1, "Subscription Payments Recur"),
(0, "Subscription payments do not recur")
)
REATTEMPT_ON_FAIL_CHOICES = (
(1, "reattempt billing on Failure"),
(0, "Do Not reattempt on failure")
)
BUY = 'buy'
SUBSCRIBE = 'subscribe'
DONATE = 'donate'
# Where the money goes.
business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL)
# Item information.
amount = forms.IntegerField(widget=ValueHiddenInput())
item_name = forms.CharField(widget=ValueHiddenInput())
item_number = forms.CharField(widget=ValueHiddenInput())
quantity = forms.CharField(widget=ValueHiddenInput())
# Subscription Related.
a1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Price
p1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Duration
t1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 unit of Duration, default to Month
a2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Price
p2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Duration
t2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 unit of Duration, default to Month
a3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Price
p3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Duration
t3 = forms.CharField(widget=ValueHiddenInput()) # Subscription unit of Duration, default to Month
src = forms.CharField(widget=ValueHiddenInput()) # Is billing recurring? default to yes
sra = forms.CharField(widget=ValueHiddenInput()) # Reattempt billing on failed cc transaction
no_note = forms.CharField(widget=ValueHiddenInput())
# Can be either 1 or 2. 1 = modify or allow new subscription creation, 2 = modify only
modify = forms.IntegerField(widget=ValueHiddenInput()) # Are we modifying an existing subscription?
# Localization / PayPal Setup
lc = forms.CharField(widget=ValueHiddenInput())
page_style = forms.CharField(widget=ValueHiddenInput())
cbt = forms.CharField(widget=ValueHiddenInput())
# IPN control.
notify_url = forms.CharField(widget=ValueHiddenInput())
cancel_return = forms.CharField(widget=ValueHiddenInput())
return_url = forms.CharField(widget=ReservedValueHiddenInput(attrs={"name": "return"}))
custom = forms.CharField(widget=ValueHiddenInput())
invoice = forms.CharField(widget=ValueHiddenInput())
# Default fields.
cmd = forms.ChoiceField(widget=forms.HiddenInput(), initial=CMD_CHOICES[0][0])
charset = forms.CharField(widget=forms.HiddenInput(), initial="utf-8")
currency_code = forms.CharField(widget=forms.HiddenInput(), initial="USD")
no_shipping = forms.ChoiceField(widget=forms.HiddenInput(), choices=SHIPPING_CHOICES,
initial=SHIPPING_CHOICES[0][0])
def __init__(self, button_type="buy", *args, **kwargs):
super(PayPalPaymentsForm, self).__init__(*args, **kwargs)
self.button_type = button_type
if 'initial' in kwargs:
# Dynamically create, so we can support everything PayPal does.
for k, v in kwargs['initial'].items():
if k not in self.base_fields:
self.fields[k] = forms.CharField(label=k, widget=ValueHiddenInput(), initial=v)
def test_mode(self):
return getattr(settings, 'PAYPAL_TEST', True)
def get_endpoint(self):
"Returns the endpoint url for the form."
if self.test_mode():
return SANDBOX_POSTBACK_ENDPOINT
else:
return POSTBACK_ENDPOINT
def render(self):
return mark_safe(u"""<form action="%s" method="post">
%s
<input type="image" src="%s" border="0" name="submit" alt="Buy it Now" />
</form>""" % (self.get_endpoint(), self.as_p(), self.get_image()))
def sandbox(self):
"Deprecated. Use self.render() instead."
import warnings
warnings.warn("""PaypalPaymentsForm.sandbox() is deprecated.
Use the render() method instead.""", DeprecationWarning)
return self.render()
def get_image(self):
return {
(True, self.SUBSCRIBE): SUBSCRIPTION_SANDBOX_IMAGE,
(True, self.BUY): SANDBOX_IMAGE,
(True, self.DONATE): DONATION_SANDBOX_IMAGE,
(False, self.SUBSCRIBE): SUBSCRIPTION_IMAGE,
(False, self.BUY): IMAGE,
(False, self.DONATE): DONATION_IMAGE,
}[self.test_mode(), self.button_type]
def is_transaction(self):
return not self.is_subscription()
def is_donation(self):
return self.button_type == self.DONATE
def is_subscription(self):
return self.button_type == self.SUBSCRIBE
class PayPalEncryptedPaymentsForm(PayPalPaymentsForm):
"""
Creates a PayPal Encrypted Payments "Buy It Now" button.
Requires the M2Crypto package.
Based on example at:
http://blog.mauveweb.co.uk/2007/10/10/paypal-with-django/
"""
def _encrypt(self):
"""Use your key thing to encrypt things."""
from M2Crypto import BIO, SMIME, X509
# @@@ Could we move this to conf.py?
CERT = settings.PAYPAL_PRIVATE_CERT
PUB_CERT = settings.PAYPAL_PUBLIC_CERT
PAYPAL_CERT = settings.PAYPAL_CERT
CERT_ID = settings.PAYPAL_CERT_ID
# Iterate through the fields and pull out the ones that have a value.
plaintext = 'cert_id=%s\n' % CERT_ID
for name, field in self.fields.items():
value = None
if name in self.initial:
value = self.initial[name]
elif field.initial is not None:
value = field.initial
if value is not None:
# @@@ Make this less hackish and put it in the widget.
if name == "return_url":
name = "return"
plaintext += u'%s=%s\n' % (name, value)
plaintext = plaintext.encode('utf-8')
# Begin crypto weirdness.
s = SMIME.SMIME()
s.load_key_bio(BIO.openfile(CERT), BIO.openfile(PUB_CERT))
p7 = s.sign(BIO.MemoryBuffer(plaintext), flags=SMIME.PKCS7_BINARY)
x509 = X509.load_cert_bio(BIO.openfile(settings.PAYPAL_CERT))
sk = X509.X509_Stack()
sk.push(x509)
s.set_x509_stack(sk)
s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
tmp = BIO.MemoryBuffer()
p7.write_der(tmp)
p7 = s.encrypt(tmp, flags=SMIME.PKCS7_BINARY)
out = BIO.MemoryBuffer()
p7.write(out)
return out.read()
def as_p(self):
return mark_safe(u"""
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="encrypted" value="%s" />
""" % self._encrypt())
class PayPalSharedSecretEncryptedPaymentsForm(PayPalEncryptedPaymentsForm):
"""
Creates a PayPal Encrypted Payments "Buy It Now" button with a Shared Secret.
Shared secrets should only be used when your IPN endpoint is on HTTPS.
Adds a secret to the notify_url based on the contents of the form.
"""
def __init__(self, *args, **kwargs):
"Make the secret from the form initial data and slip it into the form."
from paypal.standard.helpers import make_secret
super(PayPalSharedSecretEncryptedPaymentsForm, self).__init__(*args, **kwargs)
# @@@ Attach the secret parameter in a way that is safe for other query params.
secret_param = "?secret=%s" % make_secret(self)
# Initial data used in form construction overrides defaults
if 'notify_url' in self.initial:
self.initial['notify_url'] += secret_param
else:
self.fields['notify_url'].initial += secret_param
class PayPalStandardBaseForm(forms.ModelForm):
"""Form used to receive and record PayPal IPN/PDT."""
# PayPal dates have non-standard formats.
time_created = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT)
payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT)
next_payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT)
subscr_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT)
subscr_effective = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT)

View file

@ -1,72 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import hashlib
from django.conf import settings
from django.utils.encoding import smart_str
def get_sha1_hexdigest(salt, raw_password):
return hashlib.sha1(smart_str(salt) + smart_str(raw_password)).hexdigest()
def duplicate_txn_id(ipn_obj):
"""
Returns True if a record with this transaction id exists and its
payment_status has not changed.
This function has been completely changed from its previous implementation
where it used to specifically only check for a Pending->Completed
transition.
"""
# get latest similar transaction(s)
similars = ipn_obj._default_manager.filter(txn_id=ipn_obj.txn_id).order_by('-created_at')[:1]
if len(similars) > 0:
# we have a similar transaction, has the payment_status changed?
return similars[0].payment_status == ipn_obj.payment_status
return False
def make_secret(form_instance, secret_fields=None):
"""
Returns a secret for use in a EWP form or an IPN verification based on a
selection of variables in params. Should only be used with SSL.
"""
# @@@ Moved here as temporary fix to avoid dependancy on auth.models.
# @@@ amount is mc_gross on the IPN - where should mapping logic go?
# @@@ amount / mc_gross is not nessecarily returned as it was sent - how to use it? 10.00 vs. 10.0
# @@@ the secret should be based on the invoice or custom fields as well - otherwise its always the same.
# Build the secret with fields availible in both PaymentForm and the IPN. Order matters.
if secret_fields is None:
secret_fields = ['business', 'item_name']
data = ""
for name in secret_fields:
if hasattr(form_instance, 'cleaned_data'):
if name in form_instance.cleaned_data:
data += unicode(form_instance.cleaned_data[name])
else:
# Initial data passed into the constructor overrides defaults.
if name in form_instance.initial:
data += unicode(form_instance.initial[name])
elif name in form_instance.fields and form_instance.fields[name].initial is not None:
data += unicode(form_instance.fields[name].initial)
secret = get_sha1_hexdigest(settings.SECRET_KEY, data)
return secret
def check_secret(form_instance, secret):
"""
Returns true if received `secret` matches expected secret for form_instance.
Used to verify IPN.
"""
# @@@ add invoice & custom
# secret_fields = ['business', 'item_name']
return make_secret(form_instance) == secret

View file

@ -1,69 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.contrib import admin
from vendor.paypal.standard.ipn.models import PayPalIPN
class PayPalIPNAdmin(admin.ModelAdmin):
date_hierarchy = 'payment_date'
fieldsets = (
(None, {
"fields": [
"flag", "txn_id", "txn_type", "payment_status", "payment_date",
"transaction_entity", "reason_code", "pending_reason",
"mc_currency", "mc_gross", "mc_fee", "mc_handling", "mc_shipping",
"auth_status", "auth_amount", "auth_exp", "auth_id"
]
}),
("Address", {
"description": "The address of the Buyer.",
'classes': ('collapse',),
"fields": [
"address_city", "address_country", "address_country_code",
"address_name", "address_state", "address_status",
"address_street", "address_zip"
]
}),
("Buyer", {
"description": "The information about the Buyer.",
'classes': ('collapse',),
"fields": [
"first_name", "last_name", "payer_business_name", "payer_email",
"payer_id", "payer_status", "contact_phone", "residence_country"
]
}),
("Seller", {
"description": "The information about the Seller.",
'classes': ('collapse',),
"fields": [
"business", "item_name", "item_number", "quantity",
"receiver_email", "receiver_id", "custom", "invoice", "memo"
]
}),
("Recurring", {
"description": "Information about recurring Payments.",
"classes": ("collapse",),
"fields": [
"profile_status", "initial_payment_amount", "amount_per_cycle",
"outstanding_balance", "period_type", "product_name",
"product_type", "recurring_payment_id", "receipt_id",
"next_payment_date"
]
}),
("Admin", {
"description": "Additional Info.",
"classes": ('collapse',),
"fields": [
"test_ipn", "ipaddress", "query", "response", "flag_code",
"flag_info"
]
}),
)
list_display = [
"__unicode__", "flag", "flag_info", "invoice", "custom",
"payment_status", "created_at"
]
search_fields = ["txn_id", "recurring_payment_id"]
admin.site.register(PayPalIPN, PayPalIPNAdmin)

View file

@ -1,134 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='PayPalIPN',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('business', models.CharField(help_text=b'Email where the money was sent.', max_length=127, blank=True)),
('charset', models.CharField(max_length=32, blank=True)),
('custom', models.CharField(max_length=255, blank=True)),
('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('parent_txn_id', models.CharField(max_length=19, verbose_name=b'Parent Transaction ID', blank=True)),
('receiver_email', models.EmailField(max_length=127, blank=True)),
('receiver_id', models.CharField(max_length=127, blank=True)),
('residence_country', models.CharField(max_length=2, blank=True)),
('test_ipn', models.BooleanField(default=False)),
('txn_id', models.CharField(help_text=b'PayPal transaction ID.', max_length=19, verbose_name=b'Transaction ID', db_index=True, blank=True)),
('txn_type', models.CharField(help_text=b'PayPal transaction type.', max_length=128, verbose_name=b'Transaction Type', blank=True)),
('verify_sign', models.CharField(max_length=255, blank=True)),
('address_country', models.CharField(max_length=64, blank=True)),
('address_city', models.CharField(max_length=40, blank=True)),
('address_country_code', models.CharField(help_text=b'ISO 3166', max_length=64, blank=True)),
('address_name', models.CharField(max_length=128, blank=True)),
('address_state', models.CharField(max_length=40, blank=True)),
('address_status', models.CharField(max_length=11, blank=True)),
('address_street', models.CharField(max_length=200, blank=True)),
('address_zip', models.CharField(max_length=20, blank=True)),
('contact_phone', models.CharField(max_length=20, blank=True)),
('first_name', models.CharField(max_length=64, blank=True)),
('last_name', models.CharField(max_length=64, blank=True)),
('payer_business_name', models.CharField(max_length=127, blank=True)),
('payer_email', models.CharField(max_length=127, blank=True)),
('payer_id', models.CharField(max_length=13, blank=True)),
('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('auth_exp', models.CharField(max_length=28, blank=True)),
('auth_id', models.CharField(max_length=19, blank=True)),
('auth_status', models.CharField(max_length=9, blank=True)),
('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)),
('invoice', models.CharField(max_length=127, blank=True)),
('item_name', models.CharField(max_length=127, blank=True)),
('item_number', models.CharField(max_length=127, blank=True)),
('mc_currency', models.CharField(default=b'USD', max_length=32, blank=True)),
('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('memo', models.CharField(max_length=255, blank=True)),
('num_cart_items', models.IntegerField(default=0, null=True, blank=True)),
('option_name1', models.CharField(max_length=64, blank=True)),
('option_name2', models.CharField(max_length=64, blank=True)),
('payer_status', models.CharField(max_length=10, blank=True)),
('payment_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('payment_status', models.CharField(max_length=17, blank=True)),
('payment_type', models.CharField(max_length=7, blank=True)),
('pending_reason', models.CharField(max_length=14, blank=True)),
('protection_eligibility', models.CharField(max_length=32, blank=True)),
('quantity', models.IntegerField(default=1, null=True, blank=True)),
('reason_code', models.CharField(max_length=15, blank=True)),
('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('settle_currency', models.CharField(max_length=32, blank=True)),
('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('shipping_method', models.CharField(max_length=255, blank=True)),
('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('transaction_entity', models.CharField(max_length=7, blank=True)),
('auction_buyer_id', models.CharField(max_length=64, blank=True)),
('auction_closing_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)),
('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('next_payment_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('payment_cycle', models.CharField(max_length=32, blank=True)),
('period_type', models.CharField(max_length=32, blank=True)),
('product_name', models.CharField(max_length=128, blank=True)),
('product_type', models.CharField(max_length=128, blank=True)),
('profile_status', models.CharField(max_length=32, blank=True)),
('recurring_payment_id', models.CharField(max_length=128, blank=True)),
('rp_invoice_id', models.CharField(max_length=127, blank=True)),
('time_created', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('password', models.CharField(max_length=24, blank=True)),
('period1', models.CharField(max_length=32, blank=True)),
('period2', models.CharField(max_length=32, blank=True)),
('period3', models.CharField(max_length=32, blank=True)),
('reattempt', models.CharField(max_length=1, blank=True)),
('recur_times', models.IntegerField(default=0, null=True, blank=True)),
('recurring', models.CharField(max_length=1, blank=True)),
('retry_at', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('subscr_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('subscr_effective', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('subscr_id', models.CharField(max_length=19, blank=True)),
('username', models.CharField(max_length=64, blank=True)),
('case_creation_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('case_id', models.CharField(max_length=14, blank=True)),
('case_type', models.CharField(max_length=24, blank=True)),
('receipt_id', models.CharField(max_length=64, blank=True)),
('currency_code', models.CharField(default=b'USD', max_length=32, blank=True)),
('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('transaction_subject', models.CharField(max_length=255, blank=True)),
('ipaddress', models.IPAddressField(blank=True)),
('flag', models.BooleanField(default=False)),
('flag_code', models.CharField(max_length=16, blank=True)),
('flag_info', models.TextField(blank=True)),
('query', models.TextField(blank=True)),
('response', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('from_view', models.CharField(max_length=6, null=True, blank=True)),
],
options={
'db_table': 'paypal_ipn',
'verbose_name': 'PayPal IPN',
},
bases=(models.Model,),
),
]

View file

@ -1,17 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from vendor.paypal.standard.forms import PayPalStandardBaseForm
from vendor.paypal.standard.ipn.models import PayPalIPN
class PayPalIPNForm(PayPalStandardBaseForm):
"""
Form used to receive and record PayPal IPN notifications.
PayPal IPN test tool:
https://developer.paypal.com/us/cgi-bin/devscr?cmd=_tools-session
"""
class Meta:
model = PayPalIPN
# fields = "__all__"

View file

@ -1,133 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='PayPalIPN',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('business', models.CharField(help_text=b'Email where the money was sent.', max_length=127, blank=True)),
('charset', models.CharField(max_length=32, blank=True)),
('custom', models.CharField(max_length=255, blank=True)),
('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('parent_txn_id', models.CharField(max_length=19, verbose_name=b'Parent Transaction ID', blank=True)),
('receiver_email', models.EmailField(max_length=127, blank=True)),
('receiver_id', models.CharField(max_length=127, blank=True)),
('residence_country', models.CharField(max_length=2, blank=True)),
('test_ipn', models.BooleanField(default=False)),
('txn_id', models.CharField(help_text=b'PayPal transaction ID.', max_length=19, verbose_name=b'Transaction ID', db_index=True, blank=True)),
('txn_type', models.CharField(help_text=b'PayPal transaction type.', max_length=128, verbose_name=b'Transaction Type', blank=True)),
('verify_sign', models.CharField(max_length=255, blank=True)),
('address_country', models.CharField(max_length=64, blank=True)),
('address_city', models.CharField(max_length=40, blank=True)),
('address_country_code', models.CharField(help_text=b'ISO 3166', max_length=64, blank=True)),
('address_name', models.CharField(max_length=128, blank=True)),
('address_state', models.CharField(max_length=40, blank=True)),
('address_status', models.CharField(max_length=11, blank=True)),
('address_street', models.CharField(max_length=200, blank=True)),
('address_zip', models.CharField(max_length=20, blank=True)),
('contact_phone', models.CharField(max_length=20, blank=True)),
('first_name', models.CharField(max_length=64, blank=True)),
('last_name', models.CharField(max_length=64, blank=True)),
('payer_business_name', models.CharField(max_length=127, blank=True)),
('payer_email', models.CharField(max_length=127, blank=True)),
('payer_id', models.CharField(max_length=13, blank=True)),
('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('auth_exp', models.CharField(max_length=28, blank=True)),
('auth_id', models.CharField(max_length=19, blank=True)),
('auth_status', models.CharField(max_length=9, blank=True)),
('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)),
('invoice', models.CharField(max_length=127, blank=True)),
('item_name', models.CharField(max_length=127, blank=True)),
('item_number', models.CharField(max_length=127, blank=True)),
('mc_currency', models.CharField(default=b'USD', max_length=32, blank=True)),
('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('memo', models.CharField(max_length=255, blank=True)),
('num_cart_items', models.IntegerField(default=0, null=True, blank=True)),
('option_name1', models.CharField(max_length=64, blank=True)),
('option_name2', models.CharField(max_length=64, blank=True)),
('payer_status', models.CharField(max_length=10, blank=True)),
('payment_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('payment_status', models.CharField(max_length=17, blank=True)),
('payment_type', models.CharField(max_length=7, blank=True)),
('pending_reason', models.CharField(max_length=14, blank=True)),
('protection_eligibility', models.CharField(max_length=32, blank=True)),
('quantity', models.IntegerField(default=1, null=True, blank=True)),
('reason_code', models.CharField(max_length=15, blank=True)),
('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('settle_currency', models.CharField(max_length=32, blank=True)),
('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('shipping_method', models.CharField(max_length=255, blank=True)),
('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('transaction_entity', models.CharField(max_length=7, blank=True)),
('auction_buyer_id', models.CharField(max_length=64, blank=True)),
('auction_closing_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)),
('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('next_payment_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('payment_cycle', models.CharField(max_length=32, blank=True)),
('period_type', models.CharField(max_length=32, blank=True)),
('product_name', models.CharField(max_length=128, blank=True)),
('product_type', models.CharField(max_length=128, blank=True)),
('profile_status', models.CharField(max_length=32, blank=True)),
('recurring_payment_id', models.CharField(max_length=128, blank=True)),
('rp_invoice_id', models.CharField(max_length=127, blank=True)),
('time_created', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('password', models.CharField(max_length=24, blank=True)),
('period1', models.CharField(max_length=32, blank=True)),
('period2', models.CharField(max_length=32, blank=True)),
('period3', models.CharField(max_length=32, blank=True)),
('reattempt', models.CharField(max_length=1, blank=True)),
('recur_times', models.IntegerField(default=0, null=True, blank=True)),
('recurring', models.CharField(max_length=1, blank=True)),
('retry_at', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('subscr_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('subscr_effective', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('subscr_id', models.CharField(max_length=19, blank=True)),
('username', models.CharField(max_length=64, blank=True)),
('case_creation_date', models.DateTimeField(help_text=b'HH:MM:SS DD Mmm YY, YYYY PST', null=True, blank=True)),
('case_id', models.CharField(max_length=14, blank=True)),
('case_type', models.CharField(max_length=24, blank=True)),
('receipt_id', models.CharField(max_length=64, blank=True)),
('currency_code', models.CharField(default=b'USD', max_length=32, blank=True)),
('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)),
('transaction_subject', models.CharField(max_length=255, blank=True)),
('ipaddress', models.IPAddressField(blank=True)),
('flag', models.BooleanField(default=False)),
('flag_code', models.CharField(max_length=16, blank=True)),
('flag_info', models.TextField(blank=True)),
('query', models.TextField(blank=True)),
('response', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'paypal_ipn',
'verbose_name': 'PayPal IPN',
},
bases=(models.Model,),
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('ipn', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='paypalipn',
name='ipaddress',
field=models.GenericIPAddressField(null=True, blank=True),
),
]

View file

@ -1,63 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from six import b
from six.moves.urllib.request import urlopen
from paypal.standard.models import PayPalStandardBase
from vendor.paypal.standard.ipn.signals import payment_was_flagged, payment_was_refunded, payment_was_reversed, payment_was_successful, recurring_create, recurring_payment, recurring_cancel, recurring_skipped, recurring_failed, subscription_cancel, subscription_signup, subscription_eot, subscription_modify
class PayPalIPN(PayPalStandardBase):
"""Logs PayPal IPN interactions."""
format = u"<IPN: %s %s>"
class Meta:
db_table = "paypal_ipn"
verbose_name = "PayPal IPN"
def _postback(self):
"""Perform PayPal Postback validation."""
return urlopen(self.get_endpoint(), b("cmd=_notify-validate&%s" % self.query)).read()
def _verify_postback(self):
if self.response != "VERIFIED":
self.set_flag("Invalid postback. ({0})".format(self.response))
def send_signals(self):
"""Shout for the world to hear whether a txn was successful."""
if self.flag:
payment_was_flagged.send(sender=self)
# Transaction signals:
if self.is_transaction():
if self.is_refund():
payment_was_refunded.send(sender=self)
elif self.is_reversed():
payment_was_reversed.send(sender=self)
else:
payment_was_successful.send(sender=self)
# Recurring payment signals:
# XXX: Should these be merged with subscriptions?
elif self.is_recurring():
if self.is_recurring_create():
recurring_create.send(sender=self)
elif self.is_recurring_payment():
recurring_payment.send(sender=self)
elif self.is_recurring_cancel():
recurring_cancel.send(sender=self)
elif self.is_recurring_skipped():
recurring_skipped.send(sender=self)
elif self.is_recurring_failed():
recurring_failed.send(sender=self)
# Subscription signals:
else:
if self.is_subscription_cancellation():
subscription_cancel.send(sender=self)
elif self.is_subscription_signup():
subscription_signup.send(sender=self)
elif self.is_subscription_end_of_term():
subscription_eot.send(sender=self)
elif self.is_subscription_modified():
subscription_modify.send(sender=self)

View file

@ -1,43 +0,0 @@
"""
Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems.
If you do encounter this, you will need to add the "dispatch_uid" to your connect handlers:
http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave
"""
from django.dispatch import Signal
# Sent when a payment is successfully processed.
payment_was_successful = Signal()
# Sent when a payment is flagged.
payment_was_flagged = Signal()
# Sent when a payment was refunded by the seller.
payment_was_refunded = Signal()
# Sent when a payment was reversed by the buyer.
payment_was_reversed = Signal()
# Sent when a subscription was cancelled.
subscription_cancel = Signal()
# Sent when a subscription expires.
subscription_eot = Signal()
# Sent when a subscription was modified.
subscription_modify = Signal()
# Sent when a subscription is created.
subscription_signup = Signal()
# recurring_payment_profile_created
recurring_create = Signal()
# recurring_payment
recurring_payment = Signal()
recurring_cancel = Signal()
recurring_skipped = Signal()
recurring_failed = Signal()

View file

@ -1,26 +0,0 @@
<html>
<head>
<title></title>
</head>
<body>
<form action="" method="post">
<table>
{{ form.as_p }}
<tr>
<td colspan="2" align="right">
<input type="submit"/>
</td>
</tr>
</table>
</form>
</body>
</html>

View file

@ -1,44 +0,0 @@
<html>
<head>
<title></title>
<head>
<body>
<form action="http://216.19.180.83:8000/ipn/" method="post">
<input type="text" name="protection_eligibility" value="Ineligible"/>
<input type="text" name="last_name" value="User"/>
<input type="text" name="txn_id" value="51403485VH153354B"/>
<input type="text" name="receiver_email" value="bishan_1233270560_biz@gmail.com"/>
<input type="text" name="payment_status" value="Completed"/>
<input type="text" name="payment_gross" value="10.00"/>
<input type="text" name="tax" value="0.00"/>
<input type="text" name="residence_country" value="US"/>
<input type="text" name="invoice" value="0004"/>
<input type="text" name="payer_status" value="verified"/>
<input type="text" name="txn_type" value="express_checkout"/>
<input type="text" name="handling_amount" value="0.00"/>
<input type="text" name="payment_date" value="23:04:06 Feb 02, 2009 PST"/>
<input type="text" name="first_name" value="Test"/>
<input type="text" name="item_name" value=""/>
<input type="text" name="charset" value="windows-1252"/>
<input type="text" name="custom" value="website_id=13&user_id=21"/>
<input type="text" name="notify_version" value="2.6"/>
<input type="text" name="transaction_subject" value=""/>
<input type="text" name="test_ipn" value="1"/>
<input type="text" name="item_number" value=""/>
<input type="text" name="receiver_id" value="258DLEHY2BDK6"/>
<input type="text" name="payer_id" value="BN5JZ2V7MLEV4"/>
<input type="text" name="verify_sign" value="An5ns1Kso7MWUdW4ErQKJJJ4qi4-AqdZy6dD.sGO3sDhTf1wAbuO2IZ7"/>
<input type="text" name="payment_fee" value="0.59"/>
<input type="text" name="mc_fee" value="0.59"/>
<input type="text" name="mc_currency" value="USD"/>
<input type="text" name="shipping" value="0.00"/>
<input type="text" name="payer_email" value="bishan_1233269544_per@gmail.com"/>
<input type="text" name="payment_type" value="instant"/>
<input type="text" name="mc_gross" value="10.00"/>
<input type="text" name="quantity" value="1"/>
<input type="submit"/>
</form>
</body>
</html>

View file

@ -1,17 +0,0 @@
<html>
<head>
<title></title>
</head>
<body>
{{ form.sandbox }}
</body>
</html>

View file

@ -1,2 +0,0 @@
from .test_ipn import *
from .test_forms import *

View file

@ -1,22 +0,0 @@
from django.test import TestCase
from paypal.standard.forms import PayPalPaymentsForm
class PaymentsFormTest(TestCase):
def test_form_render(self):
f = PayPalPaymentsForm(initial={'business':'me@mybusiness.com',
'amount': '10.50',
'shipping': '2.00',
})
rendered = f.render()
self.assertIn('''action="https://www.sandbox.paypal.com/cgi-bin/webscr"''', rendered)
self.assertIn('''value="me@mybusiness.com"''', rendered)
self.assertIn('''value="2.00"''', rendered)
self.assertIn('''value="10.50"''', rendered)
self.assertIn('''buynowCC''', rendered)
def test_form_endpont(self):
with self.settings(PAYPAL_TEST=False):
f = PayPalPaymentsForm(initial={})
self.assertNotIn('sandbox', f.render())

View file

@ -1,332 +0,0 @@
from django.conf import settings
from django.test import TestCase
from six import b
from six.moves.urllib.parse import urlencode
from paypal.standard.models import ST_PP_CANCELLED
from paypal.standard.ipn.models import PayPalIPN
from paypal.standard.ipn.signals import (payment_was_successful,
payment_was_flagged, payment_was_refunded, payment_was_reversed,
recurring_skipped, recurring_failed,
recurring_create, recurring_payment, recurring_cancel)
# Parameters are all bytestrings, so we can construct a bytestring
# request the same way that Paypal does.
IPN_POST_PARAMS = {
"protection_eligibility": b("Ineligible"),
"last_name": b("User"),
"txn_id": b("51403485VH153354B"),
"receiver_email": b(settings.PAYPAL_RECEIVER_EMAIL),
"payment_status": b("Completed"),
"payment_gross": b("10.00"),
"tax": b("0.00"),
"residence_country": b("US"),
"invoice": b("0004"),
"payer_status": b("verified"),
"txn_type": b("express_checkout"),
"handling_amount": b("0.00"),
"payment_date": b("23:04:06 Feb 02, 2009 PST"),
"first_name": b("J\xF6rg"),
"item_name": b(""),
"charset": b("windows-1252"),
"custom": b("website_id=13&user_id=21"),
"notify_version": b("2.6"),
"transaction_subject": b(""),
"test_ipn": b("1"),
"item_number": b(""),
"receiver_id": b("258DLEHY2BDK6"),
"payer_id": b("BN5JZ2V7MLEV4"),
"verify_sign": b("An5ns1Kso7MWUdW4ErQKJJJ4qi4-AqdZy6dD.sGO3sDhTf1wAbuO2IZ7"),
"payment_fee": b("0.59"),
"mc_fee": b("0.59"),
"mc_currency": b("USD"),
"shipping": b("0.00"),
"payer_email": b("bishan_1233269544_per@gmail.com"),
"payment_type": b("instant"),
"mc_gross": b("10.00"),
"quantity": b("1"),
}
class IPNTestBase(TestCase):
urls = 'paypal.standard.ipn.tests.test_urls'
def setUp(self):
self.payment_was_successful_receivers = payment_was_successful.receivers
self.payment_was_flagged_receivers = payment_was_flagged.receivers
self.payment_was_refunded_receivers = payment_was_refunded.receivers
self.payment_was_reversed_receivers = payment_was_reversed.receivers
self.recurring_skipped_receivers = recurring_skipped.receivers
self.recurring_failed_receivers = recurring_failed.receivers
self.recurring_create_receivers = recurring_create.receivers
self.recurring_payment_receivers = recurring_payment.receivers
self.recurring_cancel_receivers = recurring_cancel.receivers
payment_was_successful.receivers = []
payment_was_flagged.receivers = []
payment_was_refunded.receivers = []
payment_was_reversed.receivers = []
recurring_skipped.receivers = []
recurring_failed.receivers = []
recurring_create.receivers = []
recurring_payment.receivers = []
recurring_cancel.receivers = []
def tearDown(self):
payment_was_successful.receivers = self.payment_was_successful_receivers
payment_was_flagged.receivers = self.payment_was_flagged_receivers
payment_was_refunded.receivers = self.payment_was_refunded_receivers
payment_was_reversed.receivers = self.payment_was_reversed_receivers
recurring_skipped.receivers = self.recurring_skipped_receivers
recurring_failed.receivers = self.recurring_failed_receivers
recurring_create.receivers = self.recurring_create_receivers
recurring_payment.receivers = self.recurring_payment_receivers
recurring_cancel.receivers = self.recurring_cancel_receivers
def paypal_post(self, params):
"""
Does an HTTP POST the way that PayPal does, using the params given.
"""
# We build params into a bytestring ourselves, to avoid some encoding
# processing that is done by the test client.
post_data = urlencode(params)
return self.client.post("/ipn/", post_data, content_type='application/x-www-form-urlencoded')
def assertGotSignal(self, signal, flagged, params=IPN_POST_PARAMS):
# Check the signal was sent. These get lost if they don't reference self.
self.got_signal = False
self.signal_obj = None
def handle_signal(sender, **kwargs):
self.got_signal = True
self.signal_obj = sender
signal.connect(handle_signal)
response = self.paypal_post(params)
self.assertEqual(response.status_code, 200)
ipns = PayPalIPN.objects.all()
self.assertEqual(len(ipns), 1)
ipn_obj = ipns[0]
self.assertEqual(ipn_obj.flag, flagged)
self.assertTrue(self.got_signal)
self.assertEqual(self.signal_obj, ipn_obj)
return ipn_obj
def assertFlagged(self, updates, flag_info):
params = IPN_POST_PARAMS.copy()
params.update(updates)
response = self.paypal_post(params)
self.assertEqual(response.status_code, 200)
ipn_obj = PayPalIPN.objects.all()[0]
self.assertEqual(ipn_obj.flag, True)
self.assertEqual(ipn_obj.flag_info, flag_info)
return ipn_obj
class IPNTest(IPNTestBase):
def setUp(self):
# Monkey patch over PayPalIPN to make it get a VERFIED response.
self.old_postback = PayPalIPN._postback
PayPalIPN._postback = lambda self: b("VERIFIED")
def tearDown(self):
PayPalIPN._postback = self.old_postback
def test_correct_ipn(self):
ipn_obj = self.assertGotSignal(payment_was_successful, False)
# Check some encoding issues:
self.assertEqual(ipn_obj.first_name, u"J\u00f6rg")
def test_failed_ipn(self):
PayPalIPN._postback = lambda self: b("INVALID")
self.assertGotSignal(payment_was_flagged, True)
def test_ipn_missing_charset(self):
params = IPN_POST_PARAMS.copy()
del params['charset']
self.assertGotSignal(payment_was_flagged, True, params=params)
def test_refunded_ipn(self):
update = {
"payment_status": "Refunded"
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(payment_was_refunded, False, params)
def test_with_na_date(self):
update = {
"payment_status": "Refunded",
"time_created": "N/A"
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(payment_was_refunded, False, params)
def test_reversed_ipn(self):
update = {
"payment_status": "Reversed"
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(payment_was_reversed, False, params)
def test_incorrect_receiver_email(self):
update = {"receiver_email": "incorrect_email@someotherbusiness.com"}
flag_info = "Invalid receiver_email. (incorrect_email@someotherbusiness.com)"
self.assertFlagged(update, flag_info)
def test_invalid_payment_status(self):
update = {"payment_status": "Failure"}
flag_info = u"Invalid payment_status. (Failure)"
self.assertFlagged(update, flag_info)
def test_vaid_payment_status_cancelled(self):
update = {"payment_status": ST_PP_CANCELLED}
params = IPN_POST_PARAMS.copy()
params.update(update)
response = self.paypal_post(params)
self.assertEqual(response.status_code, 200)
ipn_obj = PayPalIPN.objects.all()[0]
self.assertEqual(ipn_obj.flag, False)
def test_duplicate_txn_id(self):
self.paypal_post(IPN_POST_PARAMS)
self.paypal_post(IPN_POST_PARAMS)
self.assertEqual(len(PayPalIPN.objects.all()), 2)
ipn_obj = PayPalIPN.objects.order_by('-created_at', '-pk')[0]
self.assertEqual(ipn_obj.flag, True)
self.assertEqual(ipn_obj.flag_info, "Duplicate txn_id. (51403485VH153354B)")
def test_recurring_payment_skipped_ipn(self):
update = {
"recurring_payment_id": "BN5JZ2V7MLEV4",
"txn_type": "recurring_payment_skipped",
"txn_id": ""
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(recurring_skipped, False, params)
def test_recurring_payment_failed_ipn(self):
update = {
"recurring_payment_id": "BN5JZ2V7MLEV4",
"txn_type": "recurring_payment_failed",
"txn_id": ""
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(recurring_failed, False, params)
def test_recurring_payment_create_ipn(self):
update = {
"recurring_payment_id": "BN5JZ2V7MLEV4",
"txn_type": "recurring_payment_profile_created",
"txn_id": ""
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(recurring_create, False, params)
def test_recurring_payment_cancel_ipn(self):
update = {
"recurring_payment_id": "BN5JZ2V7MLEV4",
"txn_type": "recurring_payment_profile_cancel",
"txn_id": ""
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.assertGotSignal(recurring_cancel, False, params)
def test_recurring_payment_ipn(self):
"""
The wat the code is written in
PayPalIPN.send_signals the recurring_payment
will never be sent because the paypal ipn
contains a txn_id, if this test failes you
might break some compatibility
"""
update = {
"recurring_payment_id": "BN5JZ2V7MLEV4",
"txn_type": "recurring_payment",
}
params = IPN_POST_PARAMS.copy()
params.update(update)
self.got_signal = False
self.signal_obj = None
def handle_signal(sender, **kwargs):
self.got_signal = True
self.signal_obj = sender
recurring_payment.connect(handle_signal)
response = self.paypal_post(params)
self.assertEqual(response.status_code, 200)
ipns = PayPalIPN.objects.all()
self.assertEqual(len(ipns), 1)
self.assertFalse(self.got_signal)
def test_posted_params_attribute(self):
params = {'btn_id1': b('3453595'),
'business': b('email-facilitator@gmail.com'),
'charset': b('windows-1252'),
'custom': b('blahblah'),
"first_name": b("J\xF6rg"),
'ipn_track_id': b('a48170aadb705'),
'item_name1': b('Romanescoins'),
'item_number1': b(''),
'last_name': b('LASTNAME'),
'mc_currency': b('EUR'),
'mc_fee': b('0.35'),
'mc_gross': b('3.00'),
'mc_gross_1': b('3.00'),
'mc_handling': b('0.00'),
'mc_handling1': b('0.00'),
'mc_shipping': b('0.00'),
'mc_shipping1': b('0.00'),
'notify_version': b('3.8'),
'num_cart_items': b('1'),
'payer_email': b('email@gmail.com'),
'payer_id': b('6EQ6SKDFMPU36'),
'payer_status': b('verified'),
'payment_date': b('03:06:57 Jun 27, 2014 PDT'),
'payment_fee': b(''),
'payment_gross': b(''),
'payment_status': b('Completed'),
'payment_type': b('instant'),
'protection_eligibility': b('Ineligible'),
'quantity1': b('3'),
'receiver_email': b('email-facilitator@gmail.com'),
'receiver_id': b('UCWM6R2TARF36'),
'residence_country': b('FR'),
'tax': b('0.00'),
'tax1': b('0.00'),
'test_ipn': b('1'),
'transaction_subject': b('blahblah'),
'txn_id': b('KW31266C37C2593K4'),
'txn_type': b('cart'),
'verify_sign': b('A_SECRET_CODE')}
self.paypal_post(params)
ipn = PayPalIPN.objects.get()
self.assertEqual(ipn.posted_data_dict['quantity1'], '3')
self.assertEqual(ipn.posted_data_dict['first_name'], u"J\u00f6rg")
class IPNPostbackTest(IPNTestBase):
"""
Tests an actual postback to PayPal server.
"""
def test_postback(self):
# Incorrect signature means we will always get failure
self.assertFlagged({}, u'Invalid postback. (INVALID)')

View file

@ -1,5 +0,0 @@
from django.conf.urls import patterns
urlpatterns = patterns('paypal.standard.ipn.views',
(r'^ipn/$', 'ipn'),
)

View file

@ -1,5 +0,0 @@
from django.conf.urls import patterns, url
urlpatterns = patterns('paypal.standard.ipn.views',
url(r'^$', 'ipn', name="paypal-ipn"),
)

View file

@ -1,78 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.http import HttpResponse, QueryDict
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from vendor.paypal.standard.ipn.forms import PayPalIPNForm
from vendor.paypal.standard.ipn.models import PayPalIPN
@require_POST
@csrf_exempt
def ipn(request, item_check_callable=None):
"""
PayPal IPN endpoint (notify_url).
Used by both PayPal Payments Pro and Payments Standard to confirm transactions.
http://tinyurl.com/d9vu9d
PayPal IPN Simulator:
https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session
"""
#TODO: Clean up code so that we don't need to set None here and have a lot
# of if checks just to determine if flag is set.
flag = None
ipn_obj = None
# Clean up the data as PayPal sends some weird values such as "N/A"
# Also, need to cope with custom encoding, which is stored in the body (!).
# Assuming the tolerant parsing of QueryDict and an ASCII-like encoding,
# such as windows-1252, latin1 or UTF8, the following will work:
encoding = request.REQUEST.get('charset', None)
if encoding is None:
flag = "Invalid form - no charset passed, can't decode"
data = None
else:
try:
data = QueryDict(request.body, encoding=encoding).copy()
except LookupError:
data = None
flag = "Invalid form - invalid charset"
if data is not None:
date_fields = ('time_created', 'payment_date', 'next_payment_date',
'subscr_date', 'subscr_effective')
for date_field in date_fields:
if data.get(date_field) == 'N/A':
del data[date_field]
form = PayPalIPNForm(data)
if form.is_valid():
try:
#When commit = False, object is returned without saving to DB.
ipn_obj = form.save(commit=False)
except Exception as e:
flag = "Exception while processing. (%s)" % e
else:
flag = "Invalid form. (%s)" % form.errors
if ipn_obj is None:
ipn_obj = PayPalIPN()
#Set query params and sender's IP address
ipn_obj.initialize(request)
if flag is not None:
#We save errors in the flag field
ipn_obj.set_flag(flag)
else:
# Secrets should only be used over SSL.
if request.is_secure() and 'secret' in request.GET:
ipn_obj.verify_secret(form, request.GET['secret'])
else:
ipn_obj.verify(item_check_callable)
ipn_obj.save()
ipn_obj.send_signals()
return HttpResponse("OKAY")

View file

@ -1,337 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.functional import cached_property
from paypal.standard.helpers import duplicate_txn_id, check_secret
from paypal.standard.conf import RECEIVER_EMAIL, POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT
ST_PP_ACTIVE = 'Active'
ST_PP_CANCELLED = 'Cancelled'
ST_PP_CANCELED_REVERSAL = 'Canceled_Reversal'
ST_PP_CLEARED = 'Cleared'
ST_PP_COMPLETED = 'Completed'
ST_PP_CREATED = 'Created'
ST_PP_DENIED = 'Denied'
ST_PP_EXPIRED = 'Expired'
ST_PP_FAILED = 'Failed'
ST_PP_PAID = 'Paid'
ST_PP_PENDING = 'Pending'
ST_PP_PROCESSED = 'Processed'
ST_PP_REFUNDED = 'Refunded'
ST_PP_REFUSED = 'Refused'
ST_PP_REVERSED = 'Reversed'
ST_PP_REWARDED = 'Rewarded'
ST_PP_UNCLAIMED = 'Unclaimed'
ST_PP_UNCLEARED = 'Uncleared'
ST_PP_VOIDED = 'Voided'
try:
from idmapper.models import SharedMemoryModel as Model
except ImportError:
Model = models.Model
class PayPalStandardBase(Model):
"""Meta class for common variables shared by IPN and PDT: http://tinyurl.com/cuq6sj"""
# @@@ Might want to add all these one distant day.
# FLAG_CODE_CHOICES = (
# PAYMENT_STATUS_CHOICES = "Canceled_ Reversal Completed Denied Expired Failed Pending Processed Refunded Reversed Voided".split()
PAYMENT_STATUS_CHOICES = (ST_PP_ACTIVE, ST_PP_CANCELLED, ST_PP_CANCELED_REVERSAL,
ST_PP_CLEARED,
ST_PP_COMPLETED, ST_PP_CREATED, ST_PP_DENIED,
ST_PP_EXPIRED, ST_PP_FAILED, ST_PP_PAID,
ST_PP_PENDING, ST_PP_PROCESSED, ST_PP_REFUNDED,
ST_PP_REFUSED, ST_PP_REVERSED, ST_PP_REWARDED,
ST_PP_UNCLAIMED, ST_PP_UNCLEARED, ST_PP_VOIDED,)
# AUTH_STATUS_CHOICES = "Completed Pending Voided".split()
# ADDRESS_STATUS_CHOICES = "confirmed unconfirmed".split()
# PAYER_STATUS_CHOICES = "verified / unverified".split()
# PAYMENT_TYPE_CHOICES = "echeck / instant.split()
# PENDING_REASON = "address authorization echeck intl multi-currency unilateral upgrade verify other".split()
# REASON_CODE = "chargeback guarantee buyer_complaint refund other".split()
# TRANSACTION_ENTITY_CHOICES = "auth reauth order payment".split()
# Transaction and Notification-Related Variables
business = models.CharField(max_length=127, blank=True, help_text="Email where the money was sent.")
charset = models.CharField(max_length=32, blank=True)
custom = models.CharField(max_length=255, blank=True)
notify_version = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
parent_txn_id = models.CharField("Parent Transaction ID", max_length=19, blank=True)
receiver_email = models.EmailField(max_length=127, blank=True)
receiver_id = models.CharField(max_length=127, blank=True) # 258DLEHY2BDK6
residence_country = models.CharField(max_length=2, blank=True)
test_ipn = models.BooleanField(default=False, blank=True)
txn_id = models.CharField("Transaction ID", max_length=19, blank=True, help_text="PayPal transaction ID.",
db_index=True)
txn_type = models.CharField("Transaction Type", max_length=128, blank=True, help_text="PayPal transaction type.")
verify_sign = models.CharField(max_length=255, blank=True)
# Buyer Information Variables
address_country = models.CharField(max_length=64, blank=True)
address_city = models.CharField(max_length=40, blank=True)
address_country_code = models.CharField(max_length=64, blank=True, help_text="ISO 3166")
address_name = models.CharField(max_length=128, blank=True)
address_state = models.CharField(max_length=40, blank=True)
address_status = models.CharField(max_length=11, blank=True)
address_street = models.CharField(max_length=200, blank=True)
address_zip = models.CharField(max_length=20, blank=True)
contact_phone = models.CharField(max_length=20, blank=True)
first_name = models.CharField(max_length=64, blank=True)
last_name = models.CharField(max_length=64, blank=True)
payer_business_name = models.CharField(max_length=127, blank=True)
payer_email = models.CharField(max_length=127, blank=True)
payer_id = models.CharField(max_length=13, blank=True)
# Payment Information Variables
auth_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
auth_exp = models.CharField(max_length=28, blank=True)
auth_id = models.CharField(max_length=19, blank=True)
auth_status = models.CharField(max_length=9, blank=True)
exchange_rate = models.DecimalField(max_digits=64, decimal_places=16, default=0, blank=True, null=True)
invoice = models.CharField(max_length=127, blank=True)
item_name = models.CharField(max_length=127, blank=True)
item_number = models.CharField(max_length=127, blank=True)
mc_currency = models.CharField(max_length=32, default="USD", blank=True)
mc_fee = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
mc_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
mc_handling = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
mc_shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
memo = models.CharField(max_length=255, blank=True)
num_cart_items = models.IntegerField(blank=True, default=0, null=True)
option_name1 = models.CharField(max_length=64, blank=True)
option_name2 = models.CharField(max_length=64, blank=True)
payer_status = models.CharField(max_length=10, blank=True)
payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
payment_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
payment_status = models.CharField(max_length=17, blank=True)
payment_type = models.CharField(max_length=7, blank=True)
pending_reason = models.CharField(max_length=14, blank=True)
protection_eligibility = models.CharField(max_length=32, blank=True)
quantity = models.IntegerField(blank=True, default=1, null=True)
reason_code = models.CharField(max_length=15, blank=True)
remaining_settle = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
settle_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
settle_currency = models.CharField(max_length=32, blank=True)
shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
shipping_method = models.CharField(max_length=255, blank=True)
tax = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
transaction_entity = models.CharField(max_length=7, blank=True)
# Auction Variables
auction_buyer_id = models.CharField(max_length=64, blank=True)
auction_closing_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
auction_multi_item = models.IntegerField(blank=True, default=0, null=True)
for_auction = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
# Recurring Payments Variables
amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
amount_per_cycle = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
initial_payment_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
next_payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
outstanding_balance = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
payment_cycle = models.CharField(max_length=32, blank=True) #Monthly
period_type = models.CharField(max_length=32, blank=True)
product_name = models.CharField(max_length=128, blank=True)
product_type = models.CharField(max_length=128, blank=True)
profile_status = models.CharField(max_length=32, blank=True)
recurring_payment_id = models.CharField(max_length=128, blank=True) # I-FA4XVST722B9
rp_invoice_id = models.CharField(max_length=127, blank=True) # 1335-7816-2936-1451
time_created = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
# Subscription Variables
amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
mc_amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
mc_amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
mc_amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
password = models.CharField(max_length=24, blank=True)
period1 = models.CharField(max_length=32, blank=True)
period2 = models.CharField(max_length=32, blank=True)
period3 = models.CharField(max_length=32, blank=True)
reattempt = models.CharField(max_length=1, blank=True)
recur_times = models.IntegerField(blank=True, default=0, null=True)
recurring = models.CharField(max_length=1, blank=True)
retry_at = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
subscr_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
subscr_effective = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
subscr_id = models.CharField(max_length=19, blank=True)
username = models.CharField(max_length=64, blank=True)
# Dispute Resolution Variables
case_creation_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
case_id = models.CharField(max_length=14, blank=True)
case_type = models.CharField(max_length=24, blank=True)
# Variables not categorized
receipt_id = models.CharField(max_length=64, blank=True) # 1335-7816-2936-1451
currency_code = models.CharField(max_length=32, default="USD", blank=True)
handling_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
transaction_subject = models.CharField(max_length=255, blank=True)
# @@@ Mass Pay Variables (Not Implemented, needs a separate model, for each transaction x)
# fraud_managment_pending_filters_x = models.CharField(max_length=255, blank=True)
# option_selection1_x = models.CharField(max_length=200, blank=True)
# option_selection2_x = models.CharField(max_length=200, blank=True)
# masspay_txn_id_x = models.CharField(max_length=19, blank=True)
# mc_currency_x = models.CharField(max_length=32, default="USD", blank=True)
# mc_fee_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
# mc_gross_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
# mc_handlingx = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
# payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
# payment_status = models.CharField(max_length=9, blank=True)
# reason_code = models.CharField(max_length=15, blank=True)
# receiver_email_x = models.EmailField(max_length=127, blank=True)
# status_x = models.CharField(max_length=9, blank=True)
# unique_id_x = models.CharField(max_length=13, blank=True)
# Non-PayPal Variables - full IPN/PDT query and time fields.
ipaddress = models.GenericIPAddressField(blank=True, null=True)
flag = models.BooleanField(default=False, blank=True)
flag_code = models.CharField(max_length=16, blank=True)
flag_info = models.TextField(blank=True)
query = models.TextField(blank=True) # What Paypal sent to us initially
response = models.TextField(blank=True) # What we got back from our request
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Where did it come from?
# from_view = models.CharField(max_length=6, null=True, blank=True)
class Meta:
abstract = True
def __unicode__(self):
if self.is_transaction():
return self.format % ("Transaction", self.txn_id)
else:
return self.format % ("Recurring", self.recurring_payment_id)
@cached_property
def posted_data_dict(self):
"""
All the data that PayPal posted to us, as a correctly parsed dictionary of values.
"""
if not self.query:
return None
from django.http import QueryDict
roughdecode = dict(item.split('=', 1) for item in self.query.split('&'))
encoding = roughdecode.get('charset', None)
if encoding is None:
return None
query = self.query.encode('ascii')
data = QueryDict(query, encoding=encoding)
return data.dict()
def is_transaction(self):
return len(self.txn_id) > 0
def is_refund(self):
return self.payment_status == ST_PP_REFUNDED
def is_reversed(self):
return self.payment_status == ST_PP_REVERSED
def is_recurring(self):
return len(self.recurring_payment_id) > 0
def is_subscription_cancellation(self):
return self.txn_type == "subscr_cancel"
def is_subscription_end_of_term(self):
return self.txn_type == "subscr_eot"
def is_subscription_modified(self):
return self.txn_type == "subscr_modify"
def is_subscription_signup(self):
return self.txn_type == "subscr_signup"
def is_recurring_create(self):
return self.txn_type == "recurring_payment_profile_created"
def is_recurring_payment(self):
return self.txn_type == "recurring_payment"
def is_recurring_cancel(self):
return self.txn_type == "recurring_payment_profile_cancel"
def is_recurring_skipped(self):
return self.txn_type == "recurring_payment_skipped"
def is_recurring_failed(self):
return self.txn_type == "recurring_payment_failed"
def set_flag(self, info, code=None):
"""Sets a flag on the transaction and also sets a reason."""
self.flag = True
self.flag_info += info
if code is not None:
self.flag_code = code
def verify(self, item_check_callable=None):
"""
Verifies an IPN and a PDT.
Checks for obvious signs of weirdness in the payment and flags appropriately.
Provide a callable that takes an instance of this class as a parameter and returns
a tuple (False, None) if the item is valid. Should return (True, "reason") if the
item isn't valid. Strange but backward compatible :) This function should check
that `mc_gross`, `mc_currency` `item_name` and `item_number` are all correct.
"""
self.response = self._postback().decode('ascii')
self._verify_postback()
if not self.flag:
if self.is_transaction():
if self.payment_status not in self.PAYMENT_STATUS_CHOICES:
self.set_flag("Invalid payment_status. (%s)" % self.payment_status)
if duplicate_txn_id(self):
self.set_flag("Duplicate txn_id. (%s)" % self.txn_id)
if self.receiver_email != RECEIVER_EMAIL:
self.set_flag("Invalid receiver_email. (%s)" % self.receiver_email)
if callable(item_check_callable):
flag, reason = item_check_callable(self)
if flag:
self.set_flag(reason)
else:
# @@@ Run a different series of checks on recurring payments.
pass
self.save()
def verify_secret(self, form_instance, secret):
"""Verifies an IPN payment over SSL using EWP."""
if not check_secret(form_instance, secret):
self.set_flag("Invalid secret. (%s)") % secret
self.save()
def get_endpoint(self):
"""Set Sandbox endpoint if the test variable is present."""
if self.test_ipn:
return SANDBOX_POSTBACK_ENDPOINT
else:
return POSTBACK_ENDPOINT
def send_signals(self):
"""Shout for the world to hear whether a txn was successful."""
raise NotImplementedError
def initialize(self, request):
"""Store the data we'll need to make the postback from the request object."""
if request.method == 'GET':
# PDT only - this data is currently unused
self.query = request.META.get('QUERY_STRING', '')
elif request.method == 'POST':
# The following works if paypal sends an ASCII bytestring, which it does.
self.query = request.body.decode('ascii')
self.ipaddress = request.META.get('REMOTE_ADDR', '')
def _postback(self):
"""Perform postback to PayPal and store the response in self.response."""
raise NotImplementedError
def _verify_postback(self):
"""Check self.response is valid andcall self.set_flag if there is an error."""
raise NotImplementedError

View file

@ -1,121 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.contrib import admin
from paypal.standard.pdt.models import PayPalPDT
# ToDo: How similiar is this to PayPalIPNAdmin? Could we just inherit off one common admin model?
class PayPalPDTAdmin(admin.ModelAdmin):
date_hierarchy = 'payment_date'
fieldsets = (
(None, {
"fields":
['flag',
'txn_id',
'txn_type',
'payment_status',
'payment_date',
'transaction_entity',
'reason_code',
'pending_reason',
'mc_gross',
'mc_fee',
'auth_status',
'auth_amount',
'auth_exp',
'auth_id',
],
}),
("Address", {
"description": "The address of the Buyer.",
'classes': ('collapse',),
"fields":
['address_city',
'address_country',
'address_country_code',
'address_name',
'address_state',
'address_status',
'address_street',
'address_zip',
],
}),
("Buyer", {
"description": "The information about the Buyer.",
'classes': ('collapse',),
"fields":
['first_name',
'last_name',
'payer_business_name',
'payer_email',
'payer_id',
'payer_status',
'contact_phone',
'residence_country'
],
}),
("Seller", {
"description": "The information about the Seller.",
'classes': ('collapse',),
"fields":
['business',
'item_name',
'item_number',
'quantity',
'receiver_email',
'receiver_id',
'custom',
'invoice',
'memo',
],
}),
("Subscriber", {
"description": "The information about the Subscription.",
'classes': ('collapse',),
"fields":
['subscr_id',
'subscr_date',
'subscr_effective',
],
}),
("Recurring", {
"description": "Information about recurring Payments.",
"classes": ("collapse",),
"fields":
['profile_status',
'initial_payment_amount',
'amount_per_cycle',
'outstanding_balance',
'period_type',
'product_name',
'product_type',
'recurring_payment_id',
'receipt_id',
'next_payment_date',
],
}),
("Admin", {
"description": "Additional Info.",
"classes": ('collapse',),
"fields":
['test_ipn',
'ipaddress',
'query',
'flag_code',
'flag_info',
],
}),
)
list_display = ["__unicode__",
"flag",
"invoice",
"custom",
"payment_status",
"created_at",
]
search_fields = ["txn_id",
"recurring_payment_id",
]
admin.site.register(PayPalPDT, PayPalPDTAdmin)

View file

@ -1,9 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from paypal.standard.forms import PayPalStandardBaseForm
from paypal.standard.pdt.models import PayPalPDT
class PayPalPDTForm(PayPalStandardBaseForm):
class Meta:
model = PayPalPDT

View file

@ -1,95 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.db import models
from django.conf import settings
from django.http import QueryDict
from django.utils.http import urlencode
from six.moves.urllib.request import urlopen
from six.moves.urllib.parse import unquote_plus
from paypal.standard.models import PayPalStandardBase
from paypal.standard.conf import POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT
from paypal.standard.pdt.signals import pdt_successful, pdt_failed
# ### Todo: Move this logic to conf.py:
# if paypal.standard.pdt is in installed apps
# ... then check for this setting in conf.py
class PayPalSettingsError(Exception):
"""Raised when settings are incorrect."""
try:
IDENTITY_TOKEN = settings.PAYPAL_IDENTITY_TOKEN
except:
raise PayPalSettingsError(
"You must set PAYPAL_IDENTITY_TOKEN in settings.py. Get this token by enabling PDT in your PayPal account.")
class PayPalPDT(PayPalStandardBase):
format = u"<PDT: %s %s>"
amt = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
cm = models.CharField(max_length=255, blank=True)
sig = models.CharField(max_length=255, blank=True)
tx = models.CharField(max_length=255, blank=True)
st = models.CharField(max_length=32, blank=True)
class Meta:
db_table = "paypal_pdt"
verbose_name = "PayPal PDT"
def _postback(self):
"""
Perform PayPal PDT Postback validation.
Sends the transaction ID and business token to PayPal which responses with
SUCCESS or FAILED.
"""
postback_dict = dict(cmd="_notify-synch", at=IDENTITY_TOKEN, tx=self.tx)
postback_params = urlencode(postback_dict)
return urlopen(self.get_endpoint(), postback_params).read()
def get_endpoint(self):
if getattr(settings, 'PAYPAL_TEST', True):
return SANDBOX_POSTBACK_ENDPOINT
else:
return POSTBACK_ENDPOINT
def _verify_postback(self):
# ### Now we don't really care what result was, just whether a flag was set or not.
from paypal.standard.pdt.forms import PayPalPDTForm
# TODO: this needs testing and probably fixing under Python 3
result = False
response_list = self.response.split('\n')
response_dict = {}
for i, line in enumerate(response_list):
unquoted_line = unquote_plus(line).strip()
if i == 0:
self.st = unquoted_line
if self.st == "SUCCESS":
result = True
else:
if self.st != "SUCCESS":
self.set_flag(line)
break
try:
if not unquoted_line.startswith(' -'):
k, v = unquoted_line.split('=')
response_dict[k.strip()] = v.strip()
except ValueError:
pass
qd = QueryDict('', mutable=True)
qd.update(response_dict)
qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info, flag=self.flag,
flag_code=self.flag_code))
pdt_form = PayPalPDTForm(qd, instance=self)
pdt_form.save(commit=False)
def send_signals(self):
# Send the PDT signals...
if self.flag:
pdt_failed.send(sender=self)
else:
pdt_successful.send(sender=self)

View file

@ -1,25 +0,0 @@
"""
Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems.
If you do encounter this, you will need to add the "dispatch_uid" to your connect handlers:
http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave
"""
from django.dispatch import Signal
# Sent when a payment is successfully processed.
pdt_successful = Signal()
# Sent when a payment is flagged.
pdt_failed = Signal()
# # Sent when a subscription was cancelled.
# subscription_cancel = Signal()
#
# # Sent when a subscription expires.
# subscription_eot = Signal()
#
# # Sent when a subscription was modified.
# subscription_modify = Signal()
#
# # Sent when a subscription ends.
# subscription_signup = Signal()

View file

@ -1,38 +0,0 @@
{% extends "base.html" %}
{% block content %}
{% ifequal pdt_obj.st 'SUCCESS' %}
<h1>Transaction complete</h1>
<p>Thank you for your payment</p>
<p>Please print this page for your records</p>
<div>
<table>
<tr>
<td>Payer:</td>
<td>{{ pdt_obj.first_name }} {{ pdt_obj.last_name }}</td>
</tr>
<tr>
<td>Payer Email:</td>
<td>{{ pdt_obj.payer_email }}</td>
</tr>
<tr>
<td>Amount:</td>
<td>{{ pdt_obj.mc_currency }} {{ pdt_obj.mc_gross }}</td>
</tr>
<tr>
<td>Reference:</td>
<td>{{ pdt_obj.txn_id }}</td>
</tr>
</table>
</div>
{% else %}
<h1>Transaction Failed</h1>
<p>Sorry transaction failed, please try a different form of payment</p>
{% endifequal %}
{% endblock %}

View file

@ -1,35 +0,0 @@
{{st}}
mc_gross={{mc_gross}}
invoice=66
settle_amount=289.83
protection_eligibility=Ineligible
payer_id=8MZ9FQTSAMUPJ
tax=0.00
payment_date=04%3A53%3A52+Apr+12%2C+2009+PDT
payment_status=Completed
charset=windows-1252
first_name=Test
mc_fee=6.88
exchange_rate=1.32876
settle_currency=USD
custom={{custom}}
payer_status=verified
business={{business}}
quantity=1
payer_email=buyer_1239119200_per%40yoursite.com
txn_id={{txn_id}}
payment_type=instant
last_name=User
receiver_email={{business}}
payment_fee=
receiver_id=746LDC2EQAP4W
txn_type=web_accept
item_name=Advertising+Campaign%3A+1+Month+%28225.00%29
mc_currency=EUR
item_number=
residence_country=US
handling_amount=0.00
transaction_subject={{custom}}
payment_gross=
shipping=0.00
-

View file

@ -1 +0,0 @@
from test_pdt import *

View file

@ -1,31 +0,0 @@
{% ifequal pdt_obj.st 'SUCCESS' %}
<h1>Transaction complete</h1>
<p>Thank you for your payment</p>
<p>Please print this page for your records</p>
<div>
<table>
<tr>
<td>Payer:</td>
<td>{{ pdt_obj.first_name }} {{ pdt_obj.last_name }}</td>
</tr>
<tr>
<td>Payer Email:</td>
<td>{{ pdt_obj.payer_email }}</td>
</tr>
<tr>
<td>Amount:</td>
<td>{{ pdt_obj.mc_currency }} {{ pdt_obj.mc_gross }}</td>
</tr>
<tr>
<td>Reference:</td>
<td>{{ pdt_obj.txn_id }}</td>
</tr>
</table>
</div>
{% else %}
<h1>Transaction Failed</h1>
<p>Sorry transaction failed, please try a different form of payment</p>
{% endifequal %}

View file

@ -1,120 +0,0 @@
"""
run this with ./manage.py test website
see http://www.djangoproject.com/documentation/testing/ for details
"""
import os
from django.conf import settings
from django.shortcuts import render_to_response
from django.test import TestCase
from paypal.standard.pdt.models import PayPalPDT
from paypal.standard.pdt.signals import pdt_successful, pdt_failed
class DummyPayPalPDT(object):
def __init__(self, update_context_dict={}):
self.context_dict = {'st': 'SUCCESS', 'custom': 'cb736658-3aad-4694-956f-d0aeade80194',
'txn_id': '1ED550410S3402306', 'mc_gross': '225.00',
'business': settings.PAYPAL_RECEIVER_EMAIL, 'error': 'Error code: 1234'}
self.context_dict.update(update_context_dict)
self.response = ''
def update_with_get_params(self, get_params):
if get_params.has_key('tx'):
self.context_dict['txn_id'] = get_params.get('tx')
if get_params.has_key('amt'):
self.context_dict['mc_gross'] = get_params.get('amt')
if get_params.has_key('cm'):
self.context_dict['custom'] = get_params.get('cm')
def _postback(self, test=True):
"""Perform a Fake PayPal PDT Postback request."""
# @@@ would be cool if this could live in the test templates dir...
return render_to_response("pdt/test_pdt_response.html", self.context_dict).content
class PDTTest(TestCase):
urls = "paypal.standard.pdt.tests.test_urls"
template_dirs = [os.path.join(os.path.dirname(__file__), 'templates'), ]
def setUp(self):
# set up some dummy PDT get parameters
self.get_params = {"tx": "4WJ86550014687441", "st": "Completed", "amt": "225.00", "cc": "EUR",
"cm": "a3e192b8-8fea-4a86-b2e8-d5bf502e36be", "item_number": "",
"sig": "blahblahblah"}
# monkey patch the PayPalPDT._postback function
self.dpppdt = DummyPayPalPDT()
self.dpppdt.update_with_get_params(self.get_params)
PayPalPDT._postback = self.dpppdt._postback
def test_verify_postback(self):
dpppdt = DummyPayPalPDT()
paypal_response = dpppdt._postback()
assert ('SUCCESS' in paypal_response)
self.assertEqual(len(PayPalPDT.objects.all()), 0)
pdt_obj = PayPalPDT()
pdt_obj.ipaddress = '127.0.0.1'
pdt_obj.response = paypal_response
pdt_obj._verify_postback()
self.assertEqual(len(PayPalPDT.objects.all()), 0)
self.assertEqual(pdt_obj.txn_id, '1ED550410S3402306')
def test_pdt(self):
self.assertEqual(len(PayPalPDT.objects.all()), 0)
self.dpppdt.update_with_get_params(self.get_params)
paypal_response = self.client.get("/pdt/", self.get_params)
self.assertContains(paypal_response, 'Transaction complete', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 1)
def test_pdt_signals(self):
self.successful_pdt_fired = False
self.failed_pdt_fired = False
def successful_pdt(sender, **kwargs):
self.successful_pdt_fired = True
pdt_successful.connect(successful_pdt)
def failed_pdt(sender, **kwargs):
self.failed_pdt_fired = True
pdt_failed.connect(failed_pdt)
self.assertEqual(len(PayPalPDT.objects.all()), 0)
paypal_response = self.client.get("/pdt/", self.get_params)
self.assertContains(paypal_response, 'Transaction complete', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 1)
self.assertTrue(self.successful_pdt_fired)
self.assertFalse(self.failed_pdt_fired)
pdt_obj = PayPalPDT.objects.all()[0]
self.assertEqual(pdt_obj.flag, False)
def test_double_pdt_get(self):
self.assertEqual(len(PayPalPDT.objects.all()), 0)
paypal_response = self.client.get("/pdt/", self.get_params)
self.assertContains(paypal_response, 'Transaction complete', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 1)
pdt_obj = PayPalPDT.objects.all()[0]
self.assertEqual(pdt_obj.flag, False)
paypal_response = self.client.get("/pdt/", self.get_params)
self.assertContains(paypal_response, 'Transaction complete', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 1) # we don't create a new pdt
pdt_obj = PayPalPDT.objects.all()[0]
self.assertEqual(pdt_obj.flag, False)
def test_no_txn_id_in_pdt(self):
self.dpppdt.context_dict.pop('txn_id')
self.get_params = {}
paypal_response = self.client.get("/pdt/", self.get_params)
self.assertContains(paypal_response, 'Transaction Failed', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 0)
def test_custom_passthrough(self):
self.assertEqual(len(PayPalPDT.objects.all()), 0)
self.dpppdt.update_with_get_params(self.get_params)
paypal_response = self.client.get("/pdt/", self.get_params)
self.assertContains(paypal_response, 'Transaction complete', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 1)
pdt_obj = PayPalPDT.objects.all()[0]
self.assertEqual(pdt_obj.custom, self.get_params['cm'])

View file

@ -1,5 +0,0 @@
from django.conf.urls import patterns
urlpatterns = patterns('paypal.standard.pdt.views',
(r'^pdt/$', 'pdt'),
)

View file

@ -1,5 +0,0 @@
from django.conf.urls import patterns, url
urlpatterns = patterns('paypal.standard.pdt.views',
url(r'^$', 'pdt', name="paypal-pdt"),
)

View file

@ -1,66 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
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
@require_GET
def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None):
"""Standard implementation of a view that processes PDT and then renders a template
For more advanced uses, create your own view and call process_pdt.
"""
pdt_obj, failed = process_pdt(request, item_check_callable)
context = context or {}
context.update({"failed": failed, "pdt_obj": pdt_obj})
return render_to_response(template, context, RequestContext(request))
def process_pdt(request, item_check_callable=None):
"""
Payment data transfer implementation: http://tinyurl.com/c9jjmw
This function returns a tuple of pdt_obj and failed
pdt_obj is an object of type PayPalPDT
failed is a flag that indeicates whether the transaction was processed successfully
"""
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 as 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
return (pdt_obj, failed)

View file

@ -1,34 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django import forms
from django.forms.utils import flatatt # Django 1.7 and later
from django.utils.safestring import mark_safe
from django.utils.encoding import force_text
class ValueHiddenInput(forms.HiddenInput):
"""
Widget that renders only if it has a value.
Used to remove unused fields from PayPal buttons.
"""
def render(self, name, value, attrs=None):
if value is None:
return u''
else:
return super(ValueHiddenInput, self).render(name, value, attrs)
class ReservedValueHiddenInput(ValueHiddenInput):
"""
Overrides the default name attribute of the form.
Used for the PayPal `return` field.
"""
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type)
if value != '':
final_attrs['value'] = force_text(value)
return mark_safe(u'<input%s />' % flatatt(final_attrs))