Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added a few templates to help start getting the tests together. Updat…

…es to the readme to include signaling docs.
  • Loading branch information...
commit 31f7f6e06c9c83e55697f9e6cd2504b5a89b21ab 1 parent ead3da6
@johnboxall johnboxall authored
View
29 README.md
@@ -69,6 +69,22 @@ Using PayPal Payments Standard:
...
)
+1. Connect actions to the signals generated when PayPal talks to your `notify_url`.
+ Currently there are two signals `payment_was_succesful` and `payment_was_flagged`.
+ Both live in `paypal.standard.signals`. You can connect to either of these signals
+ and update your data accordingly when payments are processed. [Django Signals Documentation](http://docs.djangoproject.com/en/dev/topics/signals/).
+
+ # models.py (or somewhere)
+
+ from paypal.standard.signals import payment_was_successful
+
+ payment_was_successful.connect(show_me_the_money)
+
+ def show_me_the_money(sender, **kwargs):
+ ipn_obj = sender
+ if ipn_obj.cust == "Upgrade all users!"
+ Users.objects.update(paid=True)
+
Using PayPal Payments Standard with Encrypted Buttons:
------------------------------------------------------
@@ -153,14 +169,16 @@ Use postbacks for validation if:
Using PayPal Payments Pro
-------------------------
-PayPal Payments Pro is the more awesome version of PayPal that lets you accept payments on your site.
+PayPal Payments Pro is the more awesome version of PayPal that lets you accept payments on your site. Note that PayPal Pro uses a lot of the code from `paypal.standard` so you'll need to include both apps. Specifically IPN is still used for payment confirmation.
1. Edit `settings.py` and add `paypal.standard` and `paypal.pro` to your `INSTALLED_APPS`:
# settings.py
...
INSTALLED_APPS = (... 'paypal.standard', 'paypal.pro', ...)
-
+
+1. Grab the PayPalIPN url
+
1. Grab PayPalPro endpoint and go crazy...
# views.py
@@ -179,6 +197,9 @@ PayPal Payments Pro is the more awesome version of PayPal that lets you accept p
)
+
+
+
PayPal Initial Data:
--------------------
@@ -196,8 +217,6 @@ ToDo:
* Scattered throughout the code are triple hash ### ToDo comments with little actionable items.
-* IPN created should probably emit signals so that other objects can update themselves on the correct conditions.
-
* TESTS. Yah, this needs some test scripts bad...
* IPN / NVP / PaymentInfo - there are three models running around there probably only need to be two. Do direct payments send an IPN postback?
@@ -206,6 +225,8 @@ ToDo:
* Express Checkout flow with recurring payments doesn't like the tokens its getting...
+* Would also be awesome to have a feed of successful payments so you keep up to date with how rich you're getting.
+
License (MIT)
=============
View
4 pro/helpers.py
@@ -161,10 +161,6 @@ def _check_and_update_params(self, params, required, defaults):
def _fetch(self, params):
params_string = self.signature + urllib.urlencode(params)
-
- print self.endpoint
- print params_string
-
response = urllib.urlopen(self.endpoint, params_string).read()
tok = self._parse_response(response)
print tok
View
6 pro/models.py
@@ -6,6 +6,8 @@
from paypal.pro.fields import CountryField
from paypal.pro.signals import payment_was_successful, payment_was_flagged
+# ### ToDo: Move all signalling to IPN.
+
# ### ToDo: A lot of these common fields could be moved to mixins.
# ### there is a lot of non-dry stuff going on between the models here.
@@ -144,8 +146,8 @@ def process(self, request, item):
# ### ToDo: Can these signals instead be sent out by the IPN ???
if response['ACK'] != "Success":
self.set_flag(response.get('L_LONGMESSAGE0', ''), response.get('L_ERRORCODE0', ''))
- payment_was_flagged.send(sender=self, response=response, request=request)
+# payment_was_flagged.send(sender=self, response=response, request=request)
return False
else:
- payment_was_successful.send(sender=self, response=response, request=request)
+# payment_was_successful.send(sender=self, response=response, request=request)
return True
View
53 pro/tests.py
@@ -0,0 +1,53 @@
+IPN_TEST = {
+ 'notify_version ': '2.4',
+ 'last_name': 'Smith',
+ 'receiver_email': 'seller@paypalsandbox.com',
+ 'payment_status': '2',
+ 'mc_fee': '0.44',
+ 'tax': '2.02',
+ 'parent_txn_id ': '',
+ 'item_name1': 'something',
+ 'residence_country': '182',
+ 'invoice': 'abc1234',
+ 'address_state': 'CA',
+ 'payer_status': '0',
+ 'txn_type': 'cart',
+ 'address_street': '123, any street',
+ 'quantity1': '1',
+ 'payment_date': '22:56:14 Feb. 02, 2009 PST',
+ 'first_name': 'John',
+ 'item_number1': 'AK-1234',
+ 'item_name': '',
+ 'address_country': '182',
+ 'ipn_type': '4',
+ 'mc_gross1': '9.34',
+ 'custom': 'xyz123',
+ 'for_auction': '',
+ 'address_name': 'John Smith',
+ 'pending_reason': '',
+ 'item_number': '',
+ 'receiver_id': 'TESTSELLERID1',
+ 'reason_code': '',
+ 'business': '',
+ 'txn_id ': '1423656',
+ 'payer_id': 'TESTBUYERID01',
+ 'mc_handling1': '1.67',
+ 'notify_url': 'http://216.19.180.83:8000/ipn/',
+ 'auction_closing_date': '',
+ 'mc_handling': '2.06',
+ 'auction_buyer_id': '',
+ 'address_zip': '95131',
+ 'address_country_code': '182',
+ 'address_city': 'San Jose',
+ 'address_status': '1',
+ 'mc_shipping': '3.02',
+ 'cmd': '_send_ipn-session',
+ 'mc_currency': '15',
+ 'shipping': '',
+ 'payer_email': 'buyer@paypalsandbox.com',
+ 'payment_type': '1',
+ 'receipt_ID': '',
+ 'mc_gross': '',
+ 'mc_shipping1': '1.02',
+ 'quantity': ''
+}
View
20 standard/admin.py
@@ -1,5 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
+import string
+
from django.contrib import admin
from paypal.standard.models import PayPalIPN
@@ -10,38 +12,40 @@
# ### ToDo: Maybe move these `fields` into the model as BUYER_FIELDS = "..."
# ### so they can be accessed by forms etc.
+L = string.split
+
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_gross mc_fee auth_status auth_amount auth_exp auth_id".split()
+ "fields": L("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".split()
+ "fields": L("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".split()
+ "fields": L("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".split()
+ "fields": L("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".split()
+ "fields": L("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".split()
+ "fields": L("test_ipn ipaddress query flag_code flag_info")
}),
)
- list_display = "__unicode__ flag payment_status payment_date next_payment_date mc_gross".split()
- search_fields = "txn_id recurring_payment_id".split()
+ list_display = L("__unicode__ flag payment_status payment_date next_payment_date mc_gross")
+ search_fields = L("txn_id recurring_payment_id")
admin.site.register(PayPalIPN, PayPalIPNAdmin)
View
49 standard/models.py
@@ -3,9 +3,17 @@
from django.db import models
from django.conf import settings
+from paypal.standard.signals import payment_was_successful, payment_was_flagged
+
# ### ToDo: would be cool if PayPalIPN.query was a JSON field
# ### or something else that let you get at the data better.
+# ### ToDo: Should the signal be in `set_flag` or `verify`?
+
+# ### ToDo: There are a # of fields that appear to be duplicates from PayPal
+# ### can we sort them out?
+
+# ### Todo: PayPalIPN choices fields? in or out?
POSTBACK_ENDPOINT = "https://www.paypal.com/cgi-bin/webscr"
SANDBOX_POSTBACK_ENDPOINT = "https://www.sandbox.paypal.com/cgi-bin/webscr"
@@ -15,24 +23,19 @@ class PayPalIPN(models.Model):
"""
Logs PayPal IPN interactions.
- """
- # ### ToDo: There are a # of fields that appear to be duplicates from PayPal
- # ### can we sort them out?
-
+ """
# 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",)
- # ### Todo: Choices fields? in or out?
# FLAG_CODE_CHOICES = (
- # PAYMENT_STATUS_CHOICES = (Canceled_ Reversal Completed Denied Expired
- # Failed Pending Processed Refunded Reversed Voided
- # AUTH_STATUS_CHOICES = (Completed Pending Voided)
- # ADDRESS_STATUS_CHOICES = (confirmed / unconfirmed)
- # PAYER_STATUS_CHOICES = (verified / unverified)
- # PAYMENT_TYPE_CHOICES = echeck / instant
- # PENDING_REASON = address authorization echeck intl multi-currency unilateral upgrade verify other
- # REASON_CODE = chargeback guarantee buyer_complaint refund other
- # TRANSACTION_ENTITY_CHOICES = auth reauth order payment
+ # PAYMENT_STATUS_CHOICES = "Canceled_ Reversal Completed Denied Expired Failed Pending Processed Refunded Reversed Voided".split()
+ # 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()
# Buyer information.
address_city = models.CharField(max_length=40, blank=True)
@@ -146,12 +149,10 @@ def _postback(self, test=True):
"""
import urllib2
-
if test:
endpoint = SANDBOX_POSTBACK_ENDPOINT
else:
endpoint = POSTBACK_ENDPOINT
-
response = urllib2.urlopen(endpoint, "cmd=_notify-validate&%s" % self.query).read()
if response == "VERIFIED":
return True
@@ -173,7 +174,7 @@ def verify(self, item_check_callable=None, test=True):
from paypal.standard.helpers import duplicate_txn_id
if self._postback(test):
-
+
if self.is_transaction():
if self.payment_status != "Completed":
self.set_flag("Invalid payment_status.")
@@ -184,12 +185,19 @@ def verify(self, item_check_callable=None, test=True):
if callable(item_check_callable):
flag, reason = item_check_callable(self)
if flag:
- self.set_flag(reason)
-
+ self.set_flag(reason)
+
else:
# ### To-Do: Need to run a different series of checks on recurring payments.
pass
-
+
+ print 'VERIFY!'
+
+# if self.flag:
+# payment_was_flagged.send(sender=self) #, response=response, request=request)
+# else:
+ payment_was_successful.send(sender=self) #, response=response, request=request)
+
def verify_secret(self, form_instance, secret):
"""
Verifies an IPN payment over SSL using EWP.
@@ -203,7 +211,6 @@ def init(self, request):
self.query = request.POST.urlencode()
self.ipaddress = request.META.get('REMOTE_ADDR', '')
-
def set_flag(self, info, code=None):
"""
Sets a flag on the transaction and also sets a reason.
View
44 standard/templates/standard/ipn_test.html
@@ -0,0 +1,44 @@
+<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
64 standard/views.py
@@ -8,48 +8,44 @@
from paypal.standard.forms import *
from paypal.standard.models import PayPalIPN
+# PayPal IPN Simulator:
+# https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session
@require_POST
def ipn(request, item_check_callable=None):
"""
PayPal IPN endpoint (notify_url).
+ Used by both PayPal Payments Pro and Payments Standard to confirm transactions.
"""
- print request.POST
- try:
-
-
- form = PayPalIPNForm(request.POST)
- failed = False
- if form.is_valid():
- try:
- ipn_obj = form.save(commit=False)
- except Exception, e:
- failed = True
- else:
+ form = PayPalIPNForm(request.POST)
+ failed = False
+ if form.is_valid():
+ try:
+ ipn_obj = form.save(commit=False)
+ except Exception, e:
+ error = repr(e)
failed = True
-
- if failed:
- ipn_obj = PayPalIPN()
- ipn_obj.set_flag("Invalid form. %s" % form.errors)
+ else:
+ error = form.errors
+ failed = True
- ipn_obj.init(request)
- print ipn_obj.ipaddress
+ if failed:
+ ipn_obj = PayPalIPN()
+ ipn_obj.set_flag("Invalid form. %s" % error)
- if not failed:
- # Secrets should only be used over SSL.
- if request.is_secure() and 'secret' in request.GET:
- ipn_obj.verify_secret(form, request.GET['secret'])
+ ipn_obj.init(request)
+
+ if not failed:
+ # 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:
+ print 'err?'
+ if ipn_obj.test_ipn:
+ ipn_obj.verify(item_check_callable)
else:
- if ipn_obj.test_ipn:
- print 'going here!'
- ipn_obj.verify(item_check_callable)
- else:
- print 'somehow here'
- ipn_obj.verify(item_check_callable, test=False)
-
- ipn_obj.save()
- return HttpResponse("OKAY")
-
- except Exception, e:
- print e
+ ipn_obj.verify(item_check_callable, test=False)
+
+ ipn_obj.save()
+ return HttpResponse("OKAY")
Please sign in to comment.
Something went wrong with that request. Please try again.