Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 0b7980d8529f3e958c11e45122e3c6f0c7fef137 1 parent f55253a
@patcoll authored
View
70 README.md
@@ -0,0 +1,70 @@
+QUICKSTART
+----------
+
+To run test suite:
+
+ python paypal/test.py
+
+The meat is in `paypal.sdk.methods`. The docs are in the docstrings and tests.
+
+A set of credentials are included, but please don't use them. Honor system folks. Use the instructions in Addendum A to create your own test account and get API credentials.
+
+WHY?
+----
+
+The real value for me is having a working test suite with easy-to-understand and use methods on the APIs.
+
+TODO
+----
+
+See TODO
+
+Also, the following methods don't do much yet, because Express Checkout hasn't been implemented at all:
+
+ address_verify
+ do_authorization
+ get_express_checkout_details
+
+`set_express_checkout` technically works, but it needs `get_express_checkout_details` to do all of the work, and that method requires a PAYERID that you can only get if the user logs into PayPal.
+
+ADDENDUM A
+----------
+
+Instructions for setting up a Sandbox Website Payments Pro account. More detailed instructions can be found at [x.com](http://x.com) but this is what worked for me.
+
+ 1. Create Sandbox account. Don't use your live PayPal account email address.
+ 2. Login to Sandbox
+ 3. Test Accounts -> "Preconfigured" -- the manual process sucks.
+ 4. Make a "Seller" account
+ 5. Don't change "login email" at all -- it seems to truncate to 6 characters.
+ 6. I took the numeric password they gave as default and copy/pasted it into a plain text document so I could use it later to make all my test account passwords the same.
+ 7. I chose Visa as the credit card.
+ 8. Bank Account = "Yes" -- This is needed for a Verified account, which is needed for Website Payments Pro.
+ 9. Put $1,000 of fake $$ into the account. At one point I tried $5,000 but the test account I created wasn't Verified automatically? Not sure if the two are related.
+ 10. No notes.
+ 11. "Create Account"
+ 12. When it takes you back to the "Test Accounts" screen, it should say "Business" and "Verified"
+ 13. When you click on "API Credentials" you should see API credentials for the corresponding test account you just created. I copy/pasted them into the same text file used above.
+
+The next step was the tricky part, at least for me. I was getting `10501` errors which means the billing agreement wasn't agreed to. Apparently you need to accept the fake billing agreement that comes along with the fake account you just created, which semi-conveniently has come packaged with an automatically created and verified fake bank account and business account "verified" status. Why couldn't the billing agreement be automatically "agreed to" as well?
+
+Back on the "Test Accounts" page, choose the account you just created and click "Enter Sandbox Test Site." It should populate the fake email address, which should be `userna_XXXXXXXXXX_biz@domain.com`. Use the copy/pasted password from step #6 and paste it into the password field and login.
+
+Now go under Merchant Services -> Website Payments Pro. In the right column there should be a link to "agree" to the billing agreement. Click this link and agree to the agreement. Now your API calls will work as expected.
+
+LICENSE
+-------
+
+Copyright 2009 Pat Collins <pat@burned.com>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
View
1  TODO
@@ -0,0 +1 @@
+Search for "TODO" throughout this project. (Ctrl-Shift-T in TextMate)
View
1  paypal/__init__.py
@@ -0,0 +1 @@
+# coding=utf-8
View
4 paypal/sdk/__init__.py
@@ -0,0 +1,4 @@
+# coding=utf-8
+from exceptions import ApiError
+from settings import *
+from methods import *
View
4 paypal/sdk/exceptions.py
@@ -0,0 +1,4 @@
+# coding=utf-8
+
+class ApiError(Exception):
+ pass
View
193 paypal/sdk/methods.py
@@ -0,0 +1,193 @@
+# coding=utf-8
+
+"""
+Methods to take advantage of the Website Payments Pro and Express Checkout
+PayPal APIs.
+"""
+
+import socket
+import urllib
+import urllib2
+from urlparse import urlsplit, urlunsplit
+
+from settings import *
+from response import Response
+from exceptions import ApiError
+
+API_AUTHENTICATION_MODES = ("3TOKEN", "UNIPAY")
+
+def call(method, **kwargs):
+ """
+ Wrapper method for executing all API commands over HTTP. This method is
+ further used to implement wrapper methods listed here:
+
+ https://www.x.com/docs/DOC-1374
+
+ ``method`` must be a supported NVP method listed at the above address.
+
+ ``kwargs`` will be a hash of
+ """
+ socket.setdefaulttimeout(HTTP_TIMEOUT)
+
+ urlvalues = {
+ 'METHOD': method,
+ 'VERSION': VERSION
+ }
+
+ if API_AUTHENTICATION_MODE not in API_AUTHENTICATION_MODES:
+ raise ApiError("Not a supported auth mode. Use one of: %s" % \
+ ", ".join(API_AUTHENTICATION_MODES))
+ headers = {}
+ if(API_AUTHENTICATION_MODE == "3TOKEN"):
+ # headers['X-PAYPAL-SECURITY-USERID'] = API_USERNAME
+ # headers['X-PAYPAL-SECURITY-PASSWORD'] = API_PASSWORD
+ # headers['X-PAYPAL-SECURITY-SIGNATURE'] = API_SIGNATURE
+ urlvalues['USER'] = API_USERNAME
+ urlvalues['PWD'] = API_PASSWORD
+ urlvalues['SIGNATURE'] = API_SIGNATURE
+ elif(API_AUTHENTICATION_MODE == "UNIPAY"):
+ # headers['X-PAYPAL-SECURITY-SUBJECT'] = SUBJECT
+ urlvalues['SUBJECT'] = SUBJECT
+ # headers['X-PAYPAL-REQUEST-DATA-FORMAT'] = 'NV'
+ # headers['X-PAYPAL-RESPONSE-DATA-FORMAT'] = 'NV'
+ # print(headers)
+ for k,v in kwargs.iteritems():
+ urlvalues[k.upper()] = v
+
+ data = urllib.urlencode(urlvalues)
+ req = urllib2.Request(API_ENDPOINT, data, headers)
+ response = Response(urllib2.urlopen(req).read())
+
+ if not response.success:
+ raise ApiError(response)
+ return response
+
+def address_verify(email, street, zip):
+ """Shortcut for the AddressVerify method.
+
+ ``email``::
+ Email address of a PayPal member to verify.
+ Maximum string length: 255 single-byte characters
+ Input mask: ?@?.??
+ ``street``::
+ First line of the billing or shipping postal address to verify.
+
+ To pass verification, the value of Street must match the first three
+ single-byte characters of a postal address on file for the PayPal member.
+
+ Maximum string length: 35 single-byte characters.
+ Alphanumeric plus - , . ‘ # \
+ Whitespace and case of input value are ignored.
+ ``zip``::
+ Postal code to verify.
+
+ To pass verification, the value of Zip mustmatch the first five
+ single-byte characters of the postal code ofthe verified postal
+ address for the verified PayPal member.
+
+ Maximumstring length: 16 single-byte characters.
+ Whitespace and case of input value are ignored.
+ """
+ return call('AddressVerify', **locals())
+
+def do_authorization(transactionid, amt):
+ """Shortcut for the DoAuthorization method.
+
+ Use the TRANSACTIONID from DoExpressCheckoutPayment for the
+ ``transactionid``. The latest version of the API does not support the
+ creation of an Order from `DoDirectPayment`.
+
+ The `amt` should be the same as passed to `DoExpressCheckoutPayment`.
+
+ Flow for a payment involving a `DoAuthorization` call::
+
+ 1. One or many calls to `SetExpressCheckout` with pertinent order
+ details, returns `TOKEN`
+ 1. `DoExpressCheckoutPayment` with `TOKEN`, `PAYMENTACTION` set to
+ Order, `AMT` set to the amount of the transaction, returns
+ `TRANSACTIONID`
+ 1. `DoAuthorization` with `TRANSACTIONID` and `AMT` set to the
+ amount of the transaction.
+ 1. `DoCapture` with the `AUTHORIZATIONID` (the `TRANSACTIONID`
+ returned by `DoAuthorization`)
+
+ """
+ kwargs.update(locals())
+ return call('DoAuthorization', **kwargs)
+
+def do_direct_payment(paymentaction="Sale", **kwargs):
+ """Shortcut for the DoDirectPayment method.
+
+ ``paymentaction`` could be 'Authorization' or 'Sale'
+
+ To issue a Sale immediately::
+
+ charge = {
+ 'amt': '10.00',
+ 'creditcardtype': 'Visa',
+ 'acct': '4812177017895760',
+ 'expdate': '012010',
+ 'cvv2': '962',
+ 'firstname': 'John',
+ 'lastname': 'Doe',
+ 'street': '1 Main St',
+ 'city': 'San Jose',
+ 'state': 'CA',
+ 'zip': '95131',
+ 'countrycode': 'US',
+ 'currencycode': 'USD',
+ }
+ direct_payment("Sale", **charge)
+
+ Or, since "Sale" is the default:
+
+ direct_payment(**charge)
+
+ To issue an Authorization, simply pass "Authorization" instead of "Sale".
+
+ You may also explicitly set ``paymentaction`` as a keyword argument:
+
+ ...
+ direct_payment(paymentaction="Sale", **charge)
+ """
+ kwargs.update(locals())
+ return call('DoDirectPayment', **kwargs)
+
+def do_capture(authorizationid, amt, completetype='Complete', **kwargs):
+ """Shortcut for the DoCapture method.
+
+ Use the TRANSACTIONID from DoAuthorization, DoDirectPayment or
+ DoExpressCheckoutPayment for the ``authorizationid``.
+
+ The `amt` should be the same as the authorized transaction.
+ """
+ kwargs.update(locals())
+ return call('DoCapture', **kwargs)
+
+def get_transaction_details(transactionid):
+ """Shortcut for the GetTransactionDetails method.
+
+ Use the TRANSACTIONID from DoAuthorization, DoDirectPayment or
+ DoExpressCheckoutPayment for the ``transactionid``.
+ """
+ return call('GetTransactionDetails', **locals())
+
+def do_void(authorizationid, note=''):
+ """Shortcut for the DoVoid method.
+
+ Use the TRANSACTIONID from DoAuthorization, DoDirectPayment or
+ DoExpressCheckoutPayment for the ``authorizationid``.
+ """
+ return call('DoVoid', **locals())
+
+def set_express_checkout(amt, returnurl, cancelurl, token='', **kwargs):
+ """Shortcut for the SetExpressCheckout method.
+ """
+ kwargs.update(locals())
+ return call('SetExpressCheckout', **kwargs)
+
+def get_express_checkout_details(token):
+ """Shortcut for the GetExpressCheckoutDetails method.
+ """
+ return call('GetExpressCheckoutDetails', token=token)
+
View
25 paypal/sdk/response.py
@@ -0,0 +1,25 @@
+# coding=utf-8
+
+from cgi import parse_qs
+from settings import ACK_SUCCESS, ACK_SUCCESS_WITH_WARNING
+
+class Response(object):
+ def __init__(self, query_string):
+ self.raw = parse_qs(query_string)
+
+ def __str__(self):
+ return str(self.raw)
+
+ def __getattr__(self, key):
+ key = key.upper()
+ try:
+ value = self.raw[key]
+ if len(value) == 1:
+ return value[0]
+ return value
+ except KeyError:
+ raise AttributeError(self)
+
+ def success(self):
+ return self.ack.upper() in ACK_SUCCESS, ACK_SUCCESS_WITH_WARNING
+ success = property(success)
View
30 paypal/sdk/settings.py
@@ -0,0 +1,30 @@
+# coding=utf-8
+
+API_ENDPOINT = "https://api-3t.sandbox.paypal.com/nvp"
+
+# 3TOKEN or UNIPAY
+API_AUTHENTICATION_MODE = "3TOKEN"
+
+# 3TOKEN credentials
+API_USERNAME = "patcol_1257523559_biz_api1.gmail.com"
+API_PASSWORD = "1257523570"
+API_SIGNATURE = "AFcWxV21C7fd0v3bYYYRCpSSRl31AZEdoFKAMvYbAXdM9nKSDcaUlXDp"
+
+# UNIPAY credential
+# SUBJECT = "patcol_1257523559_biz@gmail.com"
+
+# TODO: implement use of API via http proxy
+USE_PROXY = False
+PROXY_HOST = "127.0.0.1"
+PROXY_PORT = "808"
+
+# in seconds
+HTTP_TIMEOUT = 15
+
+PAYPAL_URL = "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token="
+
+VERSION = "60.0"
+
+ACK_SUCCESS = "SUCCESS"
+ACK_SUCCESS_WITH_WARNING = "SUCCESSWITHWARNING"
+
View
123 paypal/test.py
@@ -0,0 +1,123 @@
+# coding=utf-8
+
+from sdk import *
+import unittest
+
+class TestDirectPayment(unittest.TestCase):
+ def setUp(self):
+ self.credit_card = {
+ 'amt': '10.00',
+ 'creditcardtype': 'Visa',
+ 'acct': '4812177017895760',
+ 'expdate': '012010',
+ 'cvv2': '962',
+ 'firstname': 'John',
+ 'lastname': 'Doe',
+ 'street': '1 Main St',
+ 'city': 'San Jose',
+ 'state': 'CA',
+ 'zip': '95131',
+ 'countrycode': 'US',
+ 'currencycode': 'USD',
+ }
+
+ # def test_address_verify(self):
+ # print(address_verify("patcol_1257523559_biz@gmail.com", "1 Main St", "95131"))
+
+ def test_sale(self):
+ sale = do_direct_payment('Sale', **self.credit_card)
+ self.assertTrue(sale.success)
+
+ details = get_transaction_details(sale.TRANSACTIONID)
+ self.assertTrue(details.success)
+ self.assertEqual(details.PAYMENTSTATUS.upper(), 'COMPLETED')
+ self.assertEqual(details.REASONCODE.upper(), 'NONE')
+
+ def test_abbreviated_sale(self):
+ sale = do_direct_payment(**self.credit_card)
+ self.assertTrue(sale.success)
+
+ details = get_transaction_details(sale.TRANSACTIONID)
+ self.assertTrue(details.success)
+ self.assertEqual(details.PAYMENTSTATUS.upper(), 'COMPLETED')
+ self.assertEqual(details.REASONCODE.upper(), 'NONE')
+
+ def test_authorize_and_delayed_capture(self):
+ # authorize payment
+ auth = do_direct_payment('Authorization', **self.credit_card)
+ self.assertTrue(auth.success)
+ self.assertEqual(auth.AMT, self.credit_card['amt'])
+
+ # capture payment
+ captured = do_capture(auth.TRANSACTIONID, auth.AMT)
+ self.assertTrue(captured.success)
+ self.assertEqual(auth.TRANSACTIONID, captured.PARENTTRANSACTIONID)
+ self.assertEqual(captured.PAYMENTSTATUS.upper(), 'COMPLETED')
+ self.assertEqual(captured.REASONCODE.upper(), 'NONE')
+
+ def test_authorize_and_void(self):
+ # authorize payment
+ auth = do_direct_payment('Authorization', **self.credit_card)
+ self.assertTrue(auth.success)
+ self.assertEqual(auth.AMT, self.credit_card['amt'])
+
+ # void payment
+ note = 'Voided the authorization.'
+ void = do_void(auth.TRANSACTIONID, note)
+ self.assertTrue(void.success)
+ self.assertEqual(auth.TRANSACTIONID, void.AUTHORIZATIONID)
+
+ details = get_transaction_details(auth.TRANSACTIONID)
+ self.assertTrue(details.success)
+ self.assertEqual(details.PAYMENTSTATUS.upper(), 'VOIDED')
+
+# TODO: implement the paypal account log-in as web-based? somehow implement with a bare-bones python web client so it's programmable?
+class TestExpressCheckout(unittest.TestCase):
+ def setUp(self):
+ self.returnurl = 'http://www.paypal.com'
+ self.cancelurl = 'http://www.ebay.com'
+
+ def test_sale(self):
+ pass
+
+ def test_authorize_and_delayed_capture(self):
+ """
+ Tests a four-step checkout process involving the following flow::
+
+ One or more calls to `SetExpressCheckout`.
+ --- User goes to PayPal, logs in, and confirms shipping, taxes,
+ and total amount. ---
+ A call to `GetExpressCheckoutDetails`.
+ A call to `DoExpressCheckoutPayment`.
+ A call to `DoAuthorization`.
+ A call to `DoCapture`.
+ """
+ setexp = set_express_checkout(amt='10.00', returnurl=self.returnurl, \
+ cancelurl=self.cancelurl, paymentaction='Order', \
+ email='patcol_1257541103_per@gmail.com')
+ self.assertTrue(setexp.success)
+ # print(setexp)
+ # getexp = get_express_checkout_details(token=setexp.token)
+ # print(getexp)
+
+ def test_authorize_and_void(self):
+ """
+ Tests a four-step checkout process involving the following flow::
+
+ One or more calls to `SetExpressCheckout`.
+ --- User goes to PayPal, logs in, and confirms shipping, taxes,
+ and total amount. ---
+ A call to `GetExpressCheckoutDetails`.
+ A call to `DoExpressCheckoutPayment`.
+ A call to `DoAuthorization`.
+ A call to `DoVoid`.
+ """
+ pass
+
+if __name__ == "__main__":
+ cases = [
+ TestDirectPayment,
+ TestExpressCheckout,
+ ]
+ suite = unittest.TestSuite(map(unittest.TestLoader().loadTestsFromTestCase, cases))
+ unittest.TextTestRunner(verbosity=2).run(suite)
Please sign in to comment.
Something went wrong with that request. Please try again.