Last active
August 29, 2015 14:03
-
-
Save johnmee/2d538a4e8026725352c5 to your computer and use it in GitHub Desktop.
Dealing with the Paypal API - as made for CA.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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")) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Paypal API and python/django |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' # "[email protected]" | |
# 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