Skip to content

Instantly share code, notes, and snippets.

@johnmee
Last active August 29, 2015 14:03
Show Gist options
  • Save johnmee/2d538a4e8026725352c5 to your computer and use it in GitHub Desktop.
Save johnmee/2d538a4e8026725352c5 to your computer and use it in GitHub Desktop.
Dealing with the Paypal API - as made for CA.
# paypal result is handled by another request, we just set it up and redirect to paypal here
if 'pay-by-paypal' in request.POST:
# stuff everything so far into the session
request.session['payment'] = {
'name': contact_name,
'email': contact_email,
'phone': contact_phone,
'postcode': postcode,
'vehicle': vehicle,
'colour': colour,
'options': options,
'comments': contact_comments
}
# tell paypal here comes a customer and get their url
payment = PaypalPayment()
approval_url = payment.prepare(price_decimal(product.unit_price_incl_gst), description)
request.session['payment']['paypal'] = payment
# divert the user to paypal
logger.info("{} PAYPAL {}".format(get_session_id(request), approval_url))
return HttpResponseRedirect(approval_url)
class PaypalReturnView(View):
"""
Target when returning from a successful paypal transaction. Needs to validate the params, and 'execute' the
transaction via paypal api, then redirect as appropriate.
"""
def get(self, request, *args, **kwargs):
"""
The paypal user accepted the payment so execute it, create the submission, and send them to it.
"""
# requires a paypal token
token = request.GET.get('token') # not that we do anything with it :-(
payer_id = request.GET.get('PayerID')
if payer_id is None:
messages.add_message(request, messages.ERROR, 'Paypal payment requires a PayerID')
return HttpResponseRedirect(reverse("haggler"))
if token is None:
messages.add_message(request, messages.ERROR, 'Paypal payment requires a token')
return HttpResponseRedirect(reverse("haggler"))
if 'payment' not in request.session or 'paypal' not in request.session['payment']:
# we don't have any paypal submission in the session
messages.add_message(request, messages.ERROR, 'Paypal payment not found')
return HttpResponseRedirect(reverse("haggler"))
try:
# try to execute
paypal_payment = request.session['payment']['paypal']
payment = paypal_payment.execute(payer_id)
except PaypalException, ex:
# paypal REJECTED
messages.add_message(request, messages.ERROR, 'Paypal Payment failed to complete: {}'.format(ex.message))
return HttpResponseRedirect(reverse("haggler"))
# Paypal payment SUCCESSFUL
data = request.session['payment']
user = get_or_create_user(data['email'], data['name'], data['phone'])
login(request, user)
# create a submission, payment, order, orderitems etc etc etc etc
submission = create_submission(user, data['postcode'], data['vehicle'], data['colour'], data['options'],
data['comments'], Submission.PAYPAL, payment.get('id'), True)
# celebrate the news and send them on their way
if 'payment' in request.session:
del request.session['payment']
request.session['new_payment_made'] = True
messages.info(request, 'Your payment was approved.')
return HttpResponseRedirect(reverse('buyer-submission', kwargs={'pk': submission.pk}))
class PaypalCancelView(View):
"""
Target for a cancelled paypal transaction. Forget anything we know about a paypal transaction
and redirect to payment page.
"""
def get(self, request, *args, **kwargs):
"""
The paypal user declined the payment (presumably) so send them back to the payment form.
"""
messages.add_message(request, messages.WARNING, 'Paypal payment was cancelled')
try:
data = request.session['payment']
options = data['colour'].name
if len(data['options']):
options += '-' + ",".join([opt.code for opt in data['options']])
form_url = reverse('payment-view', kwargs={
'postcode': data['postcode'],
'nvic': data['vehicle'].nvic,
'opts': options
})
return HttpResponseRedirect("{}#paypal".format(form_url))
except KeyError:
# car description details are not in the session
return HttpResponseRedirect(reverse("haggler"))
Paypal API and python/django
import urllib2
import urllib
import json
import base64
import datetime
from django.conf import settings
from django.core.urlresolvers import reverse
CLIENT_ID = settings.PAYPAL_SETTINGS['CLIENT_ID']
SECRET = settings.PAYPAL_SETTINGS['SECRET']
ENDPOINT = settings.PAYPAL_SETTINGS['ENDPOINT']
RETURN_URL = "http://{}/paypal-return/".format(settings.HOST_DOMAIN)
CANCEL_URL = "http://{}/paypal-cancel/".format(settings.HOST_DOMAIN)
TOKEN_ENDPOINT = '{}/v1/oauth2/token'.format(ENDPOINT)
PAYMENT_ENDPOINT = '{}/v1/payments/payment'.format(ENDPOINT)
class PaypalException(Exception):
"""Raised whenever there is a problem talking to paypal"""
pass
class Payment(object):
"""
Get down and dirty with the PAYPAL.com API
It's a two-step process:
1. "create" the payment at paypal and send the client off to approve it
2. "execute" the payment when the client returns
Whilst the client is off at paypal they'll login and approve.
+ If they hit our return-url they're purported to have approved it. To confirm that and tell paypal they can't keep
that money for themselves we need to do an 'execute' submission and get a 200 response.
+ If they cancelled they should be redirected to our cancel-url, and if you try to 'execute' it will get a 400 response.
"""
def __init__(self):
"""
Run off to paypal and get an authorization token
"""
req = urllib2.Request(TOKEN_ENDPOINT)
req.add_header('Accept', 'application/json')
req.add_header('Accept-Language', 'en_US')
base64string = base64.encodestring('%s:%s' % (CLIENT_ID, SECRET)).replace('\n', '')
req.add_header("Authorization", "Basic %s" % base64string)
r = urllib2.urlopen(req, urllib.urlencode({'grant_type': 'client_credentials'}))
response = json.loads(r.read())
self.access_token = response['access_token']
time_to_live = response['expires_in']
self.expiry = datetime.datetime.now() + datetime.timedelta(seconds=time_to_live)
def prepare(self, amount, description):
"""
Start a payment.
Submit a payment request to paypal - so when the client arrives they know who we are, what they're buying
and how much we want.
"""
req = urllib2.Request(PAYMENT_ENDPOINT)
req.add_header('Content-Type', 'application/json')
req.add_header('Authorization', 'Bearer {}'.format(self.access_token))
data = {
'intent': 'sale',
'payer': {"payment_method": "paypal"},
'transactions': [{
'amount': {'total': str(amount), 'currency': 'AUD'},
'description': description,
'item_list': {
'items': [{
'quantity': "1",
'name': description,
'price': str(amount),
'currency': 'AUD'
}]
}
}],
'redirect_urls': {
'return_url': RETURN_URL,
'cancel_url': CANCEL_URL
}
}
r = urllib2.urlopen(req, json.dumps(data))
response = json.loads(r.read())
links = {}
for link in response['links']:
links[link['rel']] = link['href']
self.execute_url = links['execute']
return links['approval_url']
def execute(self, payer_id):
"""
Execute the payment
After the user has approved a payment we still need to go back to paypal and tell them
we actually want that payer's money put into our account. They call it an 'execute'.
:payer_id: a code paypal put into the GET params of the successful return redirect
Returns True if the payment execute went through
"""
req = urllib2.Request(self.execute_url)
req.add_header('Content-Type', 'application/json')
req.add_header('Authorization', 'Bearer {}'.format(self.access_token))
data = {'payer_id': payer_id}
data = json.dumps(data)
try:
raw_response = urllib2.urlopen(req, data)
return json.loads(raw_response.read())
except urllib2.HTTPError, err:
data = json.loads(err.read())
raise PaypalException('{} ERROR: {}'.format(err.code, data['name'], data['message']))
import haggler.lib.paypal as paypal
from haggler.lib.paypal import PaypalException
class TestPaypal(SimpleTestCase):
def test_payment(self):
#FIXME: this one is failing but paypal are in the middle of rolling out so many changes it wouldn't suprise
# me that it is their fault. Current response to the 'prepare' request is "400 BAD REQUEST"
# Can we get an access token?
payment = paypal.Payment()
self.assertIsNotNone(payment.access_token)
self.assertIsNotNone(payment.expiry)
print payment.access_token, payment.expiry
# Can we set up a payment?
payment_url = payment.prepare(price_decimal(Decimal('29.00')), "Testing - test_payment")
print payment_url, settings.PAYPAL_SETTINGS['ENDPOINT']
self.assertTrue(payment_url.startswith('https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='))
# Can we handle a failing execution?
self.assertRaisesMessage(PaypalException, "400 ERROR: INVALID_PAYER_ID", payment.execute, 'foobar')
# This is what happens when the payer didn't approve it (yet)(assuming this payer_id is vaguely valid)
self.assertRaisesMessage(PaypalException, "400 ERROR: PAYMENT_NOT_APPROVED_FOR_EXECUTION", payment.execute, '9T47P7EZ2UBCJ')
def test_basic(self):
"""
Basic path through a Paypal transaction using the API
- nothing tested in the application here; just working out how to use their API here.
"""
client_id = 'AYA1z-YOUR_CLIENT_ID'
secret = 'EF8fV-YOURSECRET'
payer_id = '9T47P7EZ2UBCJ' # "test_1335423699_per@caradvice.com.au"
# authenticate for an 'access_token'
req = urllib2.Request('https://api.sandbox.paypal.com/v1/oauth2/token')
req.add_header('Accept', 'application/json')
req.add_header('Accept-Language', 'en_US')
base64string = base64.encodestring('%s:%s' % (client_id, secret)).replace('\n', '')
req.add_header("Authorization", "Basic %s" % base64string)
r = urllib2.urlopen(req, urllib.urlencode({'grant_type': 'client_credentials'}))
response = json.loads(r.read())
token = response['access_token']
print token
# start a payment
req = urllib2.Request('https://api.sandbox.paypal.com/v1/payments/payment')
req.add_header('Content-Type', 'application/json')
req.add_header('Authorization', 'Bearer {}'.format(token))
data = {
'intent': 'sale',
'payer': {"payment_method": "paypal"},
'transactions': [{'amount': {'total': '29.00', 'currency': 'AUD'}, 'description': 'BestPrice Submission'}],
'redirect_urls': {
'return_url': 'http://127.0.0.1:8000/paypal-return/',
'cancel_url': 'http://127.0.0.1:8000/paypal-cancel/'
}
}
r = urllib2.urlopen(req, json.dumps(data))
response = json.loads(r.read())
links = {}
for link in response['links']:
links[link['rel']] = link['href']
print links['approval_url']
## Now 'Execute' the transaction
#print links['execute']
#payer_id = raw_input("Enter the Payer ID: ")
req = urllib2.Request(links['execute'])
req.add_header('Content-Type', 'application/json')
req.add_header('Authorization', 'Bearer {}'.format(token))
data = {'payer_id': payer_id}
data = json.dumps(data)
try:
resp = urllib2.urlopen(req, data)
print json.loads(resp.read())
except urllib2.HTTPError, err:
data = json.loads(err.read())
print '{} ERROR: {}'.format(err.code, data['name'], data['message'])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment