Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions pay_with_amazon/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pay_with_amazon.payment_request import PaymentRequest


class PayWithAmazonClient:
class PayWithAmazonClient(object):

"""This client allows you to make all the necessary API calls to
integrate with Login and Pay with Amazon.
Expand Down Expand Up @@ -75,7 +75,7 @@ def __init__(
try:
setattr(self, param, os.environ[env_param_map[param]])
except:
raise ValueError('Invalid {}.'.format(param))
raise ValueError('Invalid {0}.'.format(param))
else:
setattr(self, param, eval(param))

Expand All @@ -84,7 +84,7 @@ def __init__(
# used for Login with Amazon helper
self._region_code = self.region
except KeyError:
raise KeyError('Invalid region code ({})'.format(self.region))
raise KeyError('Invalid region code ({0})'.format(self.region))

self.mws_access_key = self.mws_access_key
self.mws_secret_key = self.mws_secret_key
Expand All @@ -111,12 +111,8 @@ def __init__(
application['ApplicationVersion'] = application_version

self._user_agent = '; '.join(
'{}={}'.format(
k,
v) for (
k,
v) in sorted(
application.items()))
'{0}={1}'.format(k, v) for (k, v) in sorted(application.items())
)

self._headers = {
'Content-Type': 'application/x-www-form-urlencoded',
Expand All @@ -136,11 +132,11 @@ def _set_endpoint(self):
"""Set endpoint for API calls"""
if self._sandbox:
self._mws_endpoint = \
'https://{}/OffAmazonPayments_Sandbox/{}'.format(
'https://{0}/OffAmazonPayments_Sandbox/{1}'.format(
self._region, self._api_version)
else:
self._mws_endpoint = \
'https://{}/OffAmazonPayments/{}'.format(
'https://{0}/OffAmazonPayments/{1}'.format(
self._region, self._api_version)

def get_login_profile(self, access_token, client_id):
Expand Down Expand Up @@ -779,9 +775,9 @@ def authorize(
funds prior to fulfilling the order. Default: False

soft_descriptor : string, optional
The description to be shown on the buyers payment instrument
The description to be shown on the buyer's payment instrument
statement if CaptureNow is set to true. The soft descriptor sent to
the payment processor is: AMZ* <soft descriptor specified here>.
the payment processor is: "AMZ* <soft descriptor specified here>".

merchant_id : string, required
Your merchant ID. If you are a marketplace enter the seller's merchant
Expand Down Expand Up @@ -867,9 +863,9 @@ def capture(
emails to the buyer. Maximum: 255 characters, Default: None

soft_descriptor : string, optional
The description to be shown on the buyers payment instrument
The description to be shown on the buyer's payment instrument
statement. The soft descriptor sent to the payment processor is:
AMZ* <soft descriptor specified here>.
"AMZ* <soft descriptor specified here>".

merchant_id : string, required
Your merchant ID. If you are a marketplace enter the seller's merchant
Expand Down Expand Up @@ -991,9 +987,9 @@ def refund(
buyer. Maximum: 255 characters, Default: None

soft_descriptor : string, optional
The description to be shown on the buyers payment instrument
The description to be shown on the buyer's payment instrument
statement. The soft descriptor sent to the payment processor is:
AMZ* <soft descriptor specified here>.
"AMZ* <soft descriptor specified here>".

merchant_id : string, required
Your merchant ID. If you are a marketplace enter the seller's merchant
Expand Down Expand Up @@ -1111,9 +1107,9 @@ def charge(
Your marketplace web service auth token. Default: None

soft_descriptor : string, optional
The description to be shown on the buyers payment instrument
The description to be shown on the buyer's payment instrument
statement if CaptureNow is set to true. The soft descriptor sent to
the payment processor is: AMZ* <soft descriptor specified here>.
the payment processor is: "AMZ* <soft descriptor specified here>".
"""

if self.is_order_reference_id(amazon_reference_id):
Expand Down
6 changes: 3 additions & 3 deletions pay_with_amazon/ipn_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pay_with_amazon.payment_response import PaymentResponse


class IpnHandler():
class IpnHandler(object):

"""Instant Payment Notifications (IPN) can be used to monitor the state
transition of payment objects.
Expand Down Expand Up @@ -119,15 +119,15 @@ def _get_cert(self):
except HTTPError as ex:
self.error = 'Error retrieving certificate.'
raise ValueError(
'Error retrieving certificate. {}'.format(
'Error retrieving certificate. {0}'.format(
ex.reason))

self._pem = str(cert_req.read(), encoding='utf-8')
return True

def _validate_signature(self):
"""Generate signing string and validate signature"""
signing_string = '{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n'.format(
signing_string = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n'.format(
'Message',
self._message_encoded,
'MessageId',
Expand Down
12 changes: 6 additions & 6 deletions pay_with_amazon/login_with_amazon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pay_with_amazon.lwa_region as lwa_region


class LoginWithAmazon:
class LoginWithAmazon(object):

"""Login with Amazon class to wrap the get login profile method"""

Expand All @@ -24,17 +24,17 @@ def __init__(self, client_id, region, sandbox=False):
try:
self.region = lwa_region.regions[region]
except KeyError:
raise KeyError('Invalid region code ({}).'.format(region))
raise KeyError('Invalid region code ({0}).'.format(region))

self._sandbox_str = 'api.sandbox' if sandbox else 'api'
self._endpoint = 'https://{}.{}'.format(
self._endpoint = 'https://{0}.{1}'.format(
self._sandbox_str,
self.region)

def get_login_profile(self, access_token):
"""Get profile associated with LWA user."""
token_info = requests.get(
url='{}/auth/o2/tokeninfo'.format(self._endpoint),
url='{0}/auth/o2/tokeninfo'.format(self._endpoint),
headers=None,
params={'access_token': access_token},
verify=True)
Expand All @@ -51,8 +51,8 @@ def get_login_profile(self, access_token):
raise ValueError('Invalid client Id.')

profile = requests.get(
url='{}/user/profile'.format(self._endpoint),
headers={'Authorization': 'bearer {}'.format(access_token)},
url='{0}/user/profile'.format(self._endpoint),
headers={'Authorization': 'bearer {0}'.format(access_token)},
params=None,
verify=True)

Expand Down
36 changes: 22 additions & 14 deletions pay_with_amazon/payment_request.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import hmac
import time
try:
from urllib.parse import urlparse, urlencode
except ImportError: # Python 2
from urlparse import urlparse
from urllib import urlencode

import base64
import hashlib
import datetime
import requests
from urllib import parse
import hashlib
import hmac
import time
from collections import OrderedDict
from pay_with_amazon.payment_response import PaymentResponse, PaymentErrorResponse

import requests
from pay_with_amazon.payment_response import (
PaymentErrorResponse, PaymentResponse
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parentheses here is not necessarily, just to make your code more clear



class PaymentRequest:
class PaymentRequest(object):

"""Parses request, generates signature and parameter string, posts
request to Amazon, and returns result.
Expand Down Expand Up @@ -89,21 +97,21 @@ def _querystring(self, params):
if 'SellerId' not in params:
parameters['SellerId'] = self.merchant_id

parameters.update({k: v for (k, v) in params.items()})
parse_results = parse.urlparse(self._mws_endpoint)
parameters.update(dict((k, v) for k, v in params.items()))
parse_results = urlparse(self._mws_endpoint)

string_to_sign = "POST\n{}\n{}\n{}".format(
string_to_sign = "POST\n{0}\n{1}\n{2}".format(
parse_results[1],
parse_results[2],
parse.urlencode(
urlencode(
sorted(parameters.items())).replace(
'+', '%20').replace('*', '%2A').replace('%7E', '~'))

parameters['Signature'] = self._sign(string_to_sign)

ordered_parameters = OrderedDict(sorted(parameters.items()))
ordered_parameters.move_to_end('Signature')
return parse.urlencode(ordered_parameters).encode(encoding='utf_8')
ordered_parameters['Signature'] = ordered_parameters.pop('Signature')
return urlencode(ordered_parameters).encode('utf_8')

def _request(self, retry_time):
time.sleep(retry_time)
Expand All @@ -124,7 +132,7 @@ def _request(self, retry_time):
503) and self.handle_throttle:
self._should_throttle = True
self.response = PaymentErrorResponse(
'<error>{}</error>'.format(r.status_code))
'<error>{0}</error>'.format(r.status_code))
else:
self.response = PaymentErrorResponse(r.text)

Expand Down
16 changes: 8 additions & 8 deletions pay_with_amazon/payment_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections import defaultdict


class PaymentResponse:
class PaymentResponse(object):

"""Base class for all OffAmazonPayments responses

Expand Down Expand Up @@ -47,12 +47,12 @@ def __init__(self, xml):
'RequestID' with capital 'ID'. 'na' endpoint returns 'RequestId'
"""
try:
if self._root.find('.//{}RequestId'.format(self._ns)) is None:
if self._root.find('.//{0}RequestId'.format(self._ns)) is None:
self.request_id = self._root.find(
'.//{}RequestID'.format(self._ns)).text
'.//{0}RequestID'.format(self._ns)).text
else:
self.request_id = self._root.find(
'.//{}RequestId'.format(self._ns)).text
'.//{0}RequestId'.format(self._ns)).text
except:
self.request_id = None

Expand Down Expand Up @@ -82,10 +82,10 @@ def _etree_to_dict(self, t):
for dc in map(self._etree_to_dict, children):
for k, v in dc.items():
dd[k].append(v)
d = {
t.tag.replace(self._ns, ''): {
k: v[0] if len(v) == 1 else v for k,
v in dd.items()}}
d = {t.tag.replace(self._ns, ''): dict(
(k, v[0] if len(v) == 1 else v)
for k, v in dd.items()
)}
if t.attrib:
d[t.tag.replace(self._ns, '')].update(('@' + k, v)
for k, v in t.attrib.items())
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
version=pwa_version.versions['application_version'],
description='Login and Pay with Amazon Python SDK',
url='https://github.com/amzn/login-and-pay-with-amazon-sdk-python',
download_url='https://github.com/amzn/login-and-pay-with-amazon-sdk-python/tarball/{}'.format(
download_url='https://github.com/amzn/login-and-pay-with-amazon-sdk-python/tarball/{0}'.format(
pwa_version.versions['application_version']),
author='EPS-DSE',
author_email='pay-with-amazon-sdk@amazon.com',
license='Apache License version 2.0, January 2004',
install_requires=['pyOpenSSL >= 0.11',
'requests >= 2.6.0'],
'requests >= 2.6.0',
'mock'],
keywords=['Amazon', 'Payments', 'Login', 'Python', 'API', 'SDK'],
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down
9 changes: 8 additions & 1 deletion tests/test_ipn.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from future.standard_library import install_aliases
install_aliases()

import os
import unittest
from pay_with_amazon.ipn_handler import IpnHandler
Expand All @@ -9,7 +12,7 @@ def setUp(self):
self.maxDiff = None

with open(
'{}/test.pem'.format(os.path.dirname(os.path.realpath(__file__)))) as pemfile:
'{0}/test.pem'.format(os.path.dirname(os.path.realpath(__file__)))) as pemfile:
self.pem = pemfile.read()

self.body_valid = b'{\n "Type" : "Notification",\n "MessageId" : "15e7412b-e9ac-5f6a-b6df-0c909df567a0",\n "TopicArn" : "arn:aws:sns:us-east-1:291180941288:A3BXB0YN3XH17HAQR8184NJXADU",\n "Message" : "{\\"NotificationReferenceId\\":\\"1111111-1111-11111-1111-11111EXAMPLE\\",\\"MarketplaceID\\":\\"A3BXB0YN3XH17H\\",\\"NotificationType\\":\\"OrderReferenceNotification\\",\\"IsSample\\":true,\\"SellerId\\":\\"AQR8184NJXADU\\",\\"ReleaseEnvironment\\":\\"Sandbox\\",\\"Version\\":\\"2013-01-01\\",\\"NotificationData\\":\\"<?xml version=\\\\\\"1.0\\\\\\" encoding=\\\\\\"UTF-8\\\\\\"?>\\\\n <OrderReferenceNotification xmlns=\\\\\\"https://mws.amazonservices.com/ipn/OffAmazonPayments/2013-01-01\\\\\\">\\\\n <OrderReference>\\\\n <AmazonOrderReferenceId>P01-0000000-0000000-000000<\\\\/AmazonOrderReferenceId>\\\\n <OrderTotal>\\\\n <Amount>0.0<\\\\/Amount>\\\\n <CurrencyCode>USD<\\\\/CurrencyCode>\\\\n <\\\\/OrderTotal>\\\\n <SellerOrderAttributes />\\\\n <OrderReferenceStatus>\\\\n <State>Closed<\\\\/State> \\\\n <LastUpdateTimestamp>2013-01-01T01:01:01.001Z<\\\\/LastUpdateTimestamp>\\\\n <ReasonCode>AmazonClosed<\\\\/ReasonCode>\\\\n <\\\\/OrderReferenceStatus>\\\\n <CreationTimestamp>2013-01-01T01:01:01.001Z<\\\\/CreationTimestamp> \\\\n <ExpirationTimestamp>2013-01-01T01:01:01.001Z<\\\\/ExpirationTimestamp>\\\\n <\\\\/OrderReference>\\\\n <\\\\/OrderReferenceNotification>\\",\\"Timestamp\\":\\"2015-04-30T00:06:49.370Z\\"}",\n "Timestamp" : "2015-04-30T00:06:49.434Z",\n "SignatureVersion" : "1",\n "Signature" : "FltJb7WvAGpFayYBgzO5RMd5FoiGizURv+TdPnm/tLXE/E3ndwvLa08hYD3tvmggKSX7Qc0a4mSty9EjZFtTgRVT93jEGuXVBT/WjO5s0lD+7AnuWslxzuVtzLLuMTOnfFUIeoXX2V1bpGwNXPxGfRxLcqz7v41ZdvJvAauoIhjo4oAHF4nZOo2MBd6HY7LMIhJPHS0xmbyQ9Z4QFm5iDaDoSyZ5Q2hCM1RJ1Uv5MQMpNjTXdX4cX81C8lis4nMar/ejDJ8cOwiEweUl5F+y7jxI1uc8AgXNoMGXSwNvdVqoj4zgHVKPkb0Oz7HHY0c4LP9s0FMYkhLBmEGFZVKGKA==",\n "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem",\n "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:291180941288:A3BXB0YN3XH17HAQR8184NJXADU:6cab6de5-c2c7-4ef0-9d4f-d6a5db8b1636"\n}'
Expand Down Expand Up @@ -68,3 +71,7 @@ def test_validate_signature(self):
headers=self.headers)
ipn_handler._pem = self.pem
ipn_handler._validate_signature()


if __name__ == "__main__":
unittest.main()
7 changes: 5 additions & 2 deletions tests/test_lwa.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from unittest.mock import Mock, patch
from mock import Mock, patch
from pay_with_amazon.login_with_amazon import LoginWithAmazon


Expand Down Expand Up @@ -50,8 +50,11 @@ def test_get_login_profile_success(self, mock_urlopen):
mock_urlopen.side_effect = self.mock_get_success
res = self.lwa_client.get_login_profile(
access_token='access_token')
print(res)

def test_invalid_region(self):
with self.assertRaises(KeyError):
LoginWithAmazon(client_id='test', region='xx', sandbox=True)


if __name__ == "__main__":
unittest.main()
6 changes: 3 additions & 3 deletions tests/test_pwa.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
import unittest
import xml.etree.ElementTree as et
from unittest.mock import Mock, patch
from mock import Mock, patch
from pay_with_amazon.client import PayWithAmazonClient
from pay_with_amazon.payment_request import PaymentRequest
from pay_with_amazon.payment_response import PaymentResponse, PaymentErrorResponse
Expand All @@ -18,7 +18,7 @@ def setUp(self):
self.merchant_id = 'merchant_id'
self.service_version = '2013-01-01'
self.mws_endpoint = \
'https://mws.amazonservices.com/OffAmazonPayments_Sandbox/{}'.format(
'https://mws.amazonservices.com/OffAmazonPayments_Sandbox/{0}'.format(
self.service_version)

self.client = PayWithAmazonClient(
Expand Down Expand Up @@ -164,7 +164,7 @@ def test_headers(self, mock_urlopen):
mock_urlopen.side_effect = self.mock_requests_post
self.client.get_service_status()
header_expected = {
'User-Agent': 'Language=Python; MWSClientVersion=2013-01-01; Platform={}'.format(sys.platform),
'User-Agent': 'Language=Python; MWSClientVersion=2013-01-01; Platform={0}'.format(sys.platform),
'Content-Type': 'application/x-www-form-urlencoded'}
self.assertEqual(mock_urlopen.call_args[1]['headers'], header_expected)

Expand Down