From cf908a609581c292dfc3e12c89dfb4c9b8ef90cb Mon Sep 17 00:00:00 2001 From: Avi Das Date: Mon, 6 Apr 2015 13:50:42 -0500 Subject: [PATCH 1/7] Remove sh requirement, resolves issues on windows machine --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8b96e98d..04c782b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,4 @@ nose>=1.3.0 pyOpenSSL>=0.14 pycparser>=2.10 requests>=1.0.0 -sh>=1.09 six>=1.0.0 From 4ec68809ad04463b81e92abbf2bfd204fe3478ae Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 10 Apr 2015 14:27:14 -0700 Subject: [PATCH 2/7] Improved Resource syntax to be more Pythonic Simplified code using map, and dict iterators, so the code is more cleaner and readable. --- paypalrestsdk/resource.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/paypalrestsdk/resource.py b/paypalrestsdk/resource.py index e8bf3f96..b17ef110 100644 --- a/paypalrestsdk/resource.py +++ b/paypalrestsdk/resource.py @@ -5,7 +5,7 @@ class Resource(object): - """Base class for all REST services + """Base class to_dictall REST services """ convert_resources = {} @@ -85,17 +85,12 @@ def parse_object(value): if isinstance(value, Resource): return value.to_dict() elif isinstance(value, list): - new_list = [] - for obj in value: - new_list.append(parse_object(obj)) - return new_list + return map(parse_object, value) else: return value - data = {} - for key in self.__data__: - data[key] = parse_object(self.__data__[key]) - return data + return {key: parse_object(value) + for (key, value) in self.__data__.iteritems()} class Find(Resource): From f353d96a552f31ae005d1f61145b617a028e918e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 10 Apr 2015 14:29:35 -0700 Subject: [PATCH 3/7] Fixed docs in Resource class --- paypalrestsdk/resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paypalrestsdk/resource.py b/paypalrestsdk/resource.py index b17ef110..f4666ca7 100644 --- a/paypalrestsdk/resource.py +++ b/paypalrestsdk/resource.py @@ -5,7 +5,7 @@ class Resource(object): - """Base class to_dictall REST services + """Base class for all REST services """ convert_resources = {} From 2cf81709aa8dfd404dddaa04e98bd881c1f482a9 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 16 Apr 2015 11:21:15 -0700 Subject: [PATCH 4/7] Updated resource.py for support Python 2.6, 3+ --- paypalrestsdk/resource.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paypalrestsdk/resource.py b/paypalrestsdk/resource.py index f4666ca7..d0c3eb52 100644 --- a/paypalrestsdk/resource.py +++ b/paypalrestsdk/resource.py @@ -89,8 +89,8 @@ def parse_object(value): else: return value - return {key: parse_object(value) - for (key, value) in self.__data__.iteritems()} + return dict((key, parse_object(value)) + for (key, value) in self.__data__.items()) class Find(Resource): From 025643c44c292ad5068e670001f29e03faa5dea5 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 16 Apr 2015 11:28:44 -0700 Subject: [PATCH 5/7] Fixed map in resource in Python 3+ --- paypalrestsdk/resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paypalrestsdk/resource.py b/paypalrestsdk/resource.py index d0c3eb52..0d844edf 100644 --- a/paypalrestsdk/resource.py +++ b/paypalrestsdk/resource.py @@ -85,7 +85,7 @@ def parse_object(value): if isinstance(value, Resource): return value.to_dict() elif isinstance(value, list): - return map(parse_object, value) + return list(map(parse_object, value)) else: return value From e92ce398c81a8441b26c9e76f03008b1cb704657 Mon Sep 17 00:00:00 2001 From: Avi Das Date: Wed, 22 Apr 2015 11:07:49 -0500 Subject: [PATCH 6/7] Make endpoint configuration more flexible, and remove coverage/coveralls from travis --- .travis.yml | 2 -- paypalrestsdk/api.py | 7 ++----- paypalrestsdk/config.py | 1 + paypalrestsdk/resource.py | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index cffa4057..71100aa9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ python: - '3.4' install: - pip install -r requirements.txt --use-mirrors -- pip install coverage -- pip install coveralls script: - nosetests --with-coverage --cover-package=paypalrestsdk --include=paypalrestsdk/* --exclude=functional* after_success: diff --git a/paypalrestsdk/api.py b/paypalrestsdk/api.py index 7f3208ea..cbc0b6e1 100644 --- a/paypalrestsdk/api.py +++ b/paypalrestsdk/api.py @@ -10,7 +10,7 @@ import paypalrestsdk.util as util from paypalrestsdk import exceptions -from paypalrestsdk.config import __version__ +from paypalrestsdk.config import __version__, __endpoint_map__ class Api(object): @@ -49,10 +49,7 @@ def __init__(self, options=None, **kwargs): self.options = kwargs def default_endpoint(self): - if self.mode == "live": - return "https://api.paypal.com" - else: - return "https://api.sandbox.paypal.com" + return __endpoint_map__.get(self.mode) def basic_auth(self): """Find basic auth, and returns base64 encoded diff --git a/paypalrestsdk/config.py b/paypalrestsdk/config.py index 077ec64c..48946eb5 100644 --- a/paypalrestsdk/config.py +++ b/paypalrestsdk/config.py @@ -3,3 +3,4 @@ __pypi_packagename__ = "paypalrestsdk" __github_username__ = "paypal" __github_reponame__ = "PayPal-Python-SDK" +__endpoint_map__ = {"live": "https://api.paypal.com", "sandbox": "https://api.sandbox.paypal.com"} diff --git a/paypalrestsdk/resource.py b/paypalrestsdk/resource.py index 0d844edf..6b590f46 100644 --- a/paypalrestsdk/resource.py +++ b/paypalrestsdk/resource.py @@ -89,8 +89,7 @@ def parse_object(value): else: return value - return dict((key, parse_object(value)) - for (key, value) in self.__data__.items()) + return dict((key, parse_object(value)) for (key, value) in self.__data__.items()) class Find(Resource): From 2b2c6c097c831a01b1b383e4a8f689666b032cd3 Mon Sep 17 00:00:00 2001 From: Avi Das Date: Mon, 18 May 2015 12:41:49 -0500 Subject: [PATCH 7/7] update default webhook algorithm --- paypalrestsdk/config.py | 2 +- paypalrestsdk/notifications.py | 2 +- release_notes.md | 4 ++++ .../webhook-events/verify_webhook_events.py | 16 +++++++++------- test/unit_tests/notifications_test.py | 14 +++++++------- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/paypalrestsdk/config.py b/paypalrestsdk/config.py index 48946eb5..472bce84 100644 --- a/paypalrestsdk/config.py +++ b/paypalrestsdk/config.py @@ -1,4 +1,4 @@ -__version__ = "1.9.0" +__version__ = "1.9.1" __pypi_username__ = "paypal" __pypi_packagename__ = "paypalrestsdk" __github_username__ = "paypal" diff --git a/paypalrestsdk/notifications.py b/paypalrestsdk/notifications.py index a34337cb..6312f210 100644 --- a/paypalrestsdk/notifications.py +++ b/paypalrestsdk/notifications.py @@ -63,7 +63,7 @@ def _get_cert(cert_url): print(e) @classmethod - def verify(cls, transmission_id, timestamp, webhook_id, event_body, cert_url, actual_sig, auth_algo='sha1'): + def verify(cls, transmission_id, timestamp, webhook_id, event_body, cert_url, actual_sig, auth_algo='sha256'): """Verify that the webhook payload received is from PayPal, unaltered and targeted towards correct recipient """ diff --git a/release_notes.md b/release_notes.md index cc4b771e..ed47e3e3 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,6 +1,10 @@ PayPal Python SDK release notes ============================ +v1.9.1 +---- +* Webhook default algorithm update + v1.9.0 ---- * Add Tox for unit test automation for different Python versions diff --git a/samples/notification/webhook-events/verify_webhook_events.py b/samples/notification/webhook-events/verify_webhook_events.py index 8c6961ed..5bc5c5ba 100644 --- a/samples/notification/webhook-events/verify_webhook_events.py +++ b/samples/notification/webhook-events/verify_webhook_events.py @@ -9,18 +9,20 @@ logging.basicConfig(level=logging.INFO) # The payload body sent in the webhook event -event_body = "{\"id\":\"WH-8UH59159LY570081N-5FX3594634324213T\",\"create_time\":\"2014-10-10T17:36:15Z\",\"resource_type\":\"authorization\",\"event_type\":\"PAYMENT.AUTHORIZATION.CREATED\",\"summary\":\"A successful payment authorization was created for 0.60 USD\",\"resource\":{\"id\":\"2LP967258V024852T\",\"create_time\":\"2014-10-10T17:34:11Z\",\"update_time\":\"2014-10-10T17:35:16Z\",\"amount\":{\"total\":\"0.60\",\"currency\":\"USD\",\"details\":{\"subtotal\":\"0.60\"}},\"payment_mode\":\"INSTANT_TRANSFER\",\"state\":\"authorized\",\"protection_eligibility\":\"ELIGIBLE\",\"protection_eligibility_type\":\"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE\",\"parent_payment\":\"PAY-6FD94763FB485961SKQ4BREY\",\"valid_until\":\"2014-11-08T17:34:11Z\",\"links\":[{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T\",\"rel\":\"self\",\"method\":\"GET\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T/capture\",\"rel\":\"capture\",\"method\":\"POST\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T/void\",\"rel\":\"void\",\"method\":\"POST\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T/reauthorize\",\"rel\":\"reauthorize\",\"method\":\"POST\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/payment/PAY-6FD94763FB485961SKQ4BREY\",\"rel\":\"parent_payment\",\"method\":\"GET\"}]},\"links\":[{\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8UH59159LY570081N-5FX3594634324213T\",\"rel\":\"self\",\"method\":\"GET\"},{\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8UH59159LY570081N-5FX3594634324213T/resend\",\"rel\":\"resend\",\"method\":\"POST\"}]}" +event_body = '{"id":"WH-0G2756385H040842W-5Y612302CV158622M","create_time":"2015-05-18T15:45:13Z","resource_type":"sale","event_type":"PAYMENT.SALE.COMPLETED","summary":"Payment completed for $ 20.0 USD","resource":{"id":"4EU7004268015634R","create_time":"2015-05-18T15:44:02Z","update_time":"2015-05-18T15:44:21Z","amount":{"total":"20.00","currency":"USD"},"payment_mode":"INSTANT_TRANSFER","state":"completed","protection_eligibility":"ELIGIBLE","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","parent_payment":"PAY-86C81811X5228590KKVNARQQ","transaction_fee":{"value":"0.88","currency":"USD"},"links":[{"href":"https://api.sandbox.paypal.com/v1/payments/sale/4EU7004268015634R","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/sale/4EU7004268015634R/refund","rel":"refund","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-86C81811X5228590KKVNARQQ","rel":"parent_payment","method":"GET"}]},"links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0G2756385H040842W-5Y612302CV158622M","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0G2756385H040842W-5Y612302CV158622M/resend","rel":"resend","method":"POST"}]}' # Paypal-Transmission-Id in webhook payload header -transmission_id = "efcfecb0-50a3-11e4-acdb-8d0d8bca8f12" +transmission_id = "dfb3be50-fd74-11e4-8bf3-77339302725b" # Paypal-Transmission-Time in webhook payload header -timestamp = "2014-10-10T17:36:16Z" +timestamp = "2015-05-18T15:45:13Z" # Webhook id created -webhook_id = "40Y916089Y8324740" +webhook_id = "4JH86294D6297924G" # Paypal-Transmission-Sig in webhook payload header -actual_signature = "mcLeCd3PZXLR2DYFbcgf/Fzjk0wAaQ0+awY7en8J3w+UxlE5nzwIQIgHAup+x7cCrEWKzSLNSdAw9OCXb+0Pg030OEhP6iSEBr3XcTrfNXhrjz9Mbl35fe7qY6eOM4lJy2vRYAGGj9X2zXNI4Ag4wUIZlc03QRCkvAedGOkopuHXCepeVVgCEIaB4NCHgLKgjpmRaj6bRXdz1Odlm0BrG6pb7Fjw3cbhbBrw6twZugD8d/fj3juUU63UFGp77RGTxtMdnnAfHwlAQYSWRxiKxQbrE0PFZyICRcXd7hgluIv+ts/hqho4vVMi9UkRXfJCtaJ6o/tjDZjnO9rjMnu++g==" +actual_signature = "thy4/U002quzxFavHPwbfJGcc46E8rc5jzgyeafWm5mICTBdY/8rl7WJpn8JA0GKA+oDTPsSruqusw+XXg5RLAP7ip53Euh9Xu3UbUhQFX7UgwzE2FeYoY6lyRMiiiQLzy9BvHfIzNIVhPad4KnC339dr6y2l+mN8ALgI4GCdIh3/SoJO5wE64Bh/ueWtt8EVuvsvXfda2Le5a2TrOI9vLEzsm9GS79hAR/5oLexNz8UiZr045Mr5ObroH4w4oNfmkTaDk9Rj0G19uvISs5QzgmBpauKr7Nw++JI0pr/v5mFctQkoWJSGfBGzPRXawrvIIVHQ9Wer48GR2g9ZiApWg==" # Paypal-Cert-Url in webhook payload header -cert_url = 'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-35c2ed1e-21e9a5d6' +cert_url = 'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-a5cafa77' +# PayPal-Auth-Algo in webhook payload header +auth_algo = 'sha256' response = WebhookEvent.verify( - transmission_id, timestamp, webhook_id, event_body, cert_url, actual_signature) + transmission_id, timestamp, webhook_id, event_body, cert_url, actual_signature, auth_algo) print(response) diff --git a/test/unit_tests/notifications_test.py b/test/unit_tests/notifications_test.py index 411574bf..c0862b53 100644 --- a/test/unit_tests/notifications_test.py +++ b/test/unit_tests/notifications_test.py @@ -43,12 +43,12 @@ class TestWebhookEvents(unittest.TestCase): def setUp(self): self.webhook_event_id = 'WH-1S115631EN580315E-9KH94552VF7913711' - self.event_body = "{\"id\":\"WH-8UH59159LY570081N-5FX3594634324213T\",\"create_time\":\"2014-10-10T17:36:15Z\",\"resource_type\":\"authorization\",\"event_type\":\"PAYMENT.AUTHORIZATION.CREATED\",\"summary\":\"A successful payment authorization was created for 0.60 USD\",\"resource\":{\"id\":\"2LP967258V024852T\",\"create_time\":\"2014-10-10T17:34:11Z\",\"update_time\":\"2014-10-10T17:35:16Z\",\"amount\":{\"total\":\"0.60\",\"currency\":\"USD\",\"details\":{\"subtotal\":\"0.60\"}},\"payment_mode\":\"INSTANT_TRANSFER\",\"state\":\"authorized\",\"protection_eligibility\":\"ELIGIBLE\",\"protection_eligibility_type\":\"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE\",\"parent_payment\":\"PAY-6FD94763FB485961SKQ4BREY\",\"valid_until\":\"2014-11-08T17:34:11Z\",\"links\":[{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T\",\"rel\":\"self\",\"method\":\"GET\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T/capture\",\"rel\":\"capture\",\"method\":\"POST\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T/void\",\"rel\":\"void\",\"method\":\"POST\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/authorization/2LP967258V024852T/reauthorize\",\"rel\":\"reauthorize\",\"method\":\"POST\"},{\"href\":\"https://api.sandbox.paypal.com/v1/payments/payment/PAY-6FD94763FB485961SKQ4BREY\",\"rel\":\"parent_payment\",\"method\":\"GET\"}]},\"links\":[{\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8UH59159LY570081N-5FX3594634324213T\",\"rel\":\"self\",\"method\":\"GET\"},{\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8UH59159LY570081N-5FX3594634324213T/resend\",\"rel\":\"resend\",\"method\":\"POST\"}]}" - self.transmission_id = "efcfecb0-50a3-11e4-acdb-8d0d8bca8f12" - self.timestamp = "2014-10-10T17:36:16Z" - self.webhook_id = "40Y916089Y8324740" - self.actual_signature = "mcLeCd3PZXLR2DYFbcgf/Fzjk0wAaQ0+awY7en8J3w+UxlE5nzwIQIgHAup+x7cCrEWKzSLNSdAw9OCXb+0Pg030OEhP6iSEBr3XcTrfNXhrjz9Mbl35fe7qY6eOM4lJy2vRYAGGj9X2zXNI4Ag4wUIZlc03QRCkvAedGOkopuHXCepeVVgCEIaB4NCHgLKgjpmRaj6bRXdz1Odlm0BrG6pb7Fjw3cbhbBrw6twZugD8d/fj3juUU63UFGp77RGTxtMdnnAfHwlAQYSWRxiKxQbrE0PFZyICRcXd7hgluIv+ts/hqho4vVMi9UkRXfJCtaJ6o/tjDZjnO9rjMnu++g==" - self.cert_url = 'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-35c2ed1e-21e9a5d6' + self.event_body = '{"id":"WH-0G2756385H040842W-5Y612302CV158622M","create_time":"2015-05-18T15:45:13Z","resource_type":"sale","event_type":"PAYMENT.SALE.COMPLETED","summary":"Payment completed for $ 20.0 USD","resource":{"id":"4EU7004268015634R","create_time":"2015-05-18T15:44:02Z","update_time":"2015-05-18T15:44:21Z","amount":{"total":"20.00","currency":"USD"},"payment_mode":"INSTANT_TRANSFER","state":"completed","protection_eligibility":"ELIGIBLE","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","parent_payment":"PAY-86C81811X5228590KKVNARQQ","transaction_fee":{"value":"0.88","currency":"USD"},"links":[{"href":"https://api.sandbox.paypal.com/v1/payments/sale/4EU7004268015634R","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/sale/4EU7004268015634R/refund","rel":"refund","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-86C81811X5228590KKVNARQQ","rel":"parent_payment","method":"GET"}]},"links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0G2756385H040842W-5Y612302CV158622M","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0G2756385H040842W-5Y612302CV158622M/resend","rel":"resend","method":"POST"}]}' + self.transmission_id = "dfb3be50-fd74-11e4-8bf3-77339302725b" + self.timestamp = "2015-05-18T15:45:13Z" + self.webhook_id = "4JH86294D6297924G" + self.actual_signature = "thy4/U002quzxFavHPwbfJGcc46E8rc5jzgyeafWm5mICTBdY/8rl7WJpn8JA0GKA+oDTPsSruqusw+XXg5RLAP7ip53Euh9Xu3UbUhQFX7UgwzE2FeYoY6lyRMiiiQLzy9BvHfIzNIVhPad4KnC339dr6y2l+mN8ALgI4GCdIh3/SoJO5wE64Bh/ueWtt8EVuvsvXfda2Le5a2TrOI9vLEzsm9GS79hAR/5oLexNz8UiZr045Mr5ObroH4w4oNfmkTaDk9Rj0G19uvISs5QzgmBpauKr7Nw++JI0pr/v5mFctQkoWJSGfBGzPRXawrvIIVHQ9Wer48GR2g9ZiApWg==" + self.cert_url = 'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-a5cafa77' self.expected_signature = self.transmission_id + "|" + self.timestamp + "|" + \ self.webhook_id + "|" + \ str(zlib.crc32(self.event_body.encode('utf-8')) & 0xffffffff) @@ -80,7 +80,7 @@ def test_get_expected_sig(self): def test_get_resource(self): webhook_event = paypal.WebhookEvent(json.loads(self.event_body)) event_resource = webhook_event.get_resource() - self.assertTrue(isinstance(event_resource, paypal.Authorization)) + self.assertTrue(isinstance(event_resource, paypal.Sale)) def test_get_cert(self): cert = paypal.WebhookEvent._get_cert(self.cert_url)