Skip to content

Commit

Permalink
condensed item/recurring into one parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
John Boxall committed Feb 2, 2009
1 parent e2580b0 commit cce14ce
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 44 deletions.
62 changes: 60 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ it. Thanks to [Jon Atkinson](http://jonatkinson.co.uk/) for the [tutorial](http:


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:
Expand Down Expand Up @@ -150,6 +150,36 @@ Use postbacks for validation if:
1. Verify that your IPN endpoint is running on SSL - request.is_secure() should return True!


Using PayPal Payments Pro
-------------------------

PayPal Payments Pro is the more awesome version of PayPal that lets you accept payments on your site.

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 PayPalPro endpoint and go crazy...

# views.py
...
from paypal.pro.views import PayPalPro
# See source for all required fields.
pro = PayPalPro(item={'amt':1000.00, ...})
# urls.py
urlpatterns = ('',
...
(r'^payment-url/$', 'myproject.views.pro')
...
)

PayPal Initial Data:
--------------------

Expand All @@ -171,4 +201,32 @@ ToDo:

* 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...
* 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?

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.
8 changes: 8 additions & 0 deletions pro/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import time

from django.conf import settings

Expand All @@ -16,6 +17,13 @@
SANBOX_ENDPOINT = "https://api-3t.sandbox.paypal.com/nvp"


def paypal_time(time_obj=None):
"""Returns a time suitable for `profilestartdate` or other PayPal time fields."""
if time_obj is None:
time_obj = time.gmtime()
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time_obj)


class PayPalError(Exception):
pass

Expand Down
23 changes: 9 additions & 14 deletions pro/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,40 +109,35 @@ class PayPalPaymentInfo(PaymentInfo):
class Meta:
db_table = "paypal_paymentinfo"

def process(self, request, item_data=None, recurring_data=None):
def process(self, request, item):
"""
Do a direct payment.
"""
from paypal.pro.helpers import PayPalWPP
wpp = PayPalWPP(request)

# Change the model information into a dict that PayPal can understand.
params = model_to_dict(self, exclude=self.ADMIN_FIELDS)
params['ipaddress'] = self.ipaddress # These were stashed in form.save.
params['acct'] = self.acct
params['creditcardtype'] = self.creditcardtype
params['expdate'] = self.expdate
params['cvv2'] = self.cvv2

params.update(item)

# Create single payment:
if item_data is not None:
params.update(item_data)
if 'billingperiod' not in params:
response = wpp.doDirectPayment(params)

# Create recurring payment:
elif recurring_data is not None:
print 'recurring_data creating.'
params.update(recurring_data)
print params
response = wpp.createRecurringPaymentsProfile(params, direct=True)

else:
raise PayPalError("Must specified one or the other.")
response = wpp.createRecurringPaymentsProfile(params, direct=True)

# Store the response.
self.response = repr(response)

# ### ToDo: This duplicates the new PayPalNVP class - remove the data here, or elsewhere.
# ### 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)
Expand Down
52 changes: 24 additions & 28 deletions pro/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ class PayPalPro(object):
This class-based view takes care of Pay Pal Website Payments Pro. It's a monster.
PayPalPro has two flows - the checkout on your website and checkout on PayPal...
`item_data` is a dictionary that holds information about a single
item purchase.
`item` is a dictionary that holds information about the item.
Require Keys:
For single item purchase:
Required Keys:
* amt: Float amount of the item.
Optional Keys:
* custom:
* invnum: Unique ID that identifies this transaction.
`reccuring_data` is a dictionary which holds information about
setting a recurring billing cycle.
For recurring billing:
Required Keys:
* amt: Float amount for each billing cycle.
* billingperiod: String unit of measure for the billing cycle (Day|Week|SemiMonth|Month|Year)
* billingfrequency: Integer number of periods that make up a cycle.
* amt: Float amount for each billing cycle.
* profilestartdate: The date to begin billing. "2008-08-05T17:00:00Z" UTC/GMT
* desc: Description of what you're billing for.
Expand Down Expand Up @@ -76,14 +76,13 @@ class PayPalPro(object):
"""

# ### Todo: Work item / recurring data into one parameter?
def __init__(self, item_data=None, recurring_data=None,
payment_form_cls=PaymentForm,
payment_template="pro/payment.html",
confirm_form_cls=ConfirmForm,
def __init__(self, item=None,
payment_form_cls=PaymentForm,
payment_template="pro/payment.html",
confirm_form_cls=ConfirmForm,
confirm_template="pro/confirm.html",
success_url="", fail_url=None, test=True):
self.item_data = item_data
self.recurring_data = recurring_data
success_url="?success", fail_url=None, test=True):
self.item = item
self.payment_form_cls = payment_form_cls
self.payment_template = payment_template
self.confirm_form_cls = confirm_form_cls
Expand All @@ -107,11 +106,10 @@ def __call__(self, request):
elif 'token' in request.GET and 'PayerID' in request.GET:
return self.render_confirm_form()
else:
return self.render_payment_form()

return self.render_payment_form()
else:
if 'token' in request.POST and 'PayerID' in request.POST:
return self.express_confirmed()
return self.validate_confirm_form()
else:
return self.validate_payment_form()

Expand Down Expand Up @@ -142,7 +140,7 @@ def validate_payment_form(self, context=None):

payment_obj.init(self.request)
if not failed:
success = payment_obj.process(self.request, self.item_data, self.recurring_data)
success = payment_obj.process(self.request, self.item)
payment_obj.save()
if success:
return HttpResponseRedirect(self.success_url)
Expand All @@ -162,12 +160,12 @@ def redirect_to_express(self):
"""
wpp = PayPalWPP()
response = wpp.setExpressCheckout(self.item_data)
response = wpp.setExpressCheckout(self.item)
if response.get('ACK') == 'Success' and 'TOKEN' in response:
pp_params = dict(token=response['TOKEN'],
AMT=self.item_data['amt'],
RETURNURL=self.item_data['returnurl'],
CANCELURL=self.item_data['cancelurl'])
AMT=self.item['amt'],
RETURNURL=self.item['returnurl'],
CANCELURL=self.item['cancelurl'])
pp_url = SANDBOX_EXPRESS_ENDPOINT % urllib.urlencode(pp_params)
return HttpResponseRedirect(pp_url)
else:
Expand All @@ -185,7 +183,7 @@ def render_confirm_form(self, context=None):
context['form'] = self.confirm_form_cls(initial=initial)
return render_to_response(self.confirm_template, context, RequestContext(self.request))

def express_confirmed(self):
def validate_confirm_form(self):
"""
Final express flow step.
User has pressed the confirm button and now we send it off to PayPal.
Expand All @@ -194,7 +192,7 @@ def express_confirmed(self):
wpp = PayPalWPP()
pp_data = dict(token=self.request.POST['token'], payerid=self.request.POST['PayerID'])
self.item_data.update(pp_data)
response = wpp.doExpressCheckoutPayment(self.item_data)
response = wpp.doExpressCheckoutPayment(self.item)
if response.get('ACK') == 'Success':
return HttpResponseRedirect(self.success_url)
else:
Expand All @@ -205,18 +203,16 @@ def express_confirmed(self):



pro = PayPalPro()
# dict(custom='cust', invnum='inve3', amt=10.0, returnurl=RETURN_URL, cancelurl=CANCEL_URL)
item = dict(custom='cust', invnum='inve4', amt=10.0, returnurl=RETURN_URL, cancelurl=CANCEL_URL)
pro = PayPalPro(item=item)


# ### Todo: Rework `payment` parameters. and the name.
# ### ToDo: Could `express` be a class based view to be a little less confusing?

# def paypalpro(request, item_data=None, reccuring_data=None, template="pro/payment.html", context=None, success_url="", fail_url=None):
# context = context or {}
#
# # profilestartdate
# # from time import gmtime, strftime
# # strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())
#
# # item_data = item_data or dict(custom='cust', invnum='inve', amt=10.0)
# reccuring_data = dict(desc="Mobify.Me Premium", billingperiod="Month", billingfrequency=1, amt=10.0, profilestartdate='2009-02-02T19:11:01Z')
Expand Down

0 comments on commit cce14ce

Please sign in to comment.