├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGES.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── amazon_pay ├── __init__.py ├── ap_region.py ├── client.py ├── ipn_handler.py ├── login_with_amazon.py ├── lwa_region.py ├── payment_request.py ├── payment_response.py └── version.py ├── pytest.ini ├── setup.cfg ├── setup.py └── test ├── log.txt ├── sanlog.txt ├── test.pem ├── test_ap.py ├── test_ipn.py └── test_lwa.py /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.class 4 | *~ 5 | *# 6 | /doc/generated/* 7 | /runpy 8 | /build 9 | .coverage -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Version 2.7.1 - March 2021 2 | - Fixed security risk - Buyer Access token is passed as HTTP header instead of query parameter in URL for get_login_profile API 3 | 4 | Version 2.7.0 - March 2021 5 | - Add new field in ConfirmOrderReference call: expect_immediate_authorization. If this value is set to true, the ORO will auto-close in case no authorize is triggered within 60 minutes after the confirm. Default: None 6 | 7 | Version 2.6.0 - February 2019 8 | - Added new supplementary_data attribute for AuthorizeOnBillingAgreement and CreateOrderReferenceForId. 9 | - Added additional attributes (success_url, failure_url, authorization_amount, currency_code) to ConfirmOrderReference. For usage instructions, please consult the Amazon Pay Strong Customer Authentication (SCA) Upgrade Integration GuideAmazon Pay Strong Customer Authentication (SCA) Upgrade Integration Guide 10 | 11 | Version 2.5.2 September 2018 12 | - Bug Fix XML, JSON and Dictionary responses not returning the propper UTF-8 encoding 13 | 14 | Version 2.5.1 September 2018 15 | - Add support for SupplementaryData attribute in set_order_reference_details, set_order_attributes and get_order_reference_details. 16 | - Refactor unit tests. 17 | 18 | Version 2.5.0 April 2018 19 | - Add support for get_merchant_account_status call. 20 | 21 | Version 2.3.0 November 2017 22 | - Add support for set_order_attributes call. 23 | 24 | Version 2.2.0 August 2017 25 | - Add support for list_order_reference and list_order_reference_by_next_token calls. 26 | 27 | Version 2.1.0 June 2017 28 | - Add access_token variable to get_order_reference_details function. 29 | 30 | Version 2.0.0 February 2017 31 | - Rebrand and User-Agent. 32 | 33 | Version 1.0.2 - Jan 2017 34 | - Enable logging. 35 | 36 | Version 1.0.1 - August 2016 37 | - Add support for IPN handling. 38 | 39 | Version 1.0.0 - May 2015 40 | - Initial Release -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/amzn/amazon-pay-sdk-csharp/issues), or [recently closed](https://github.com/amzn/amazon-pay-sdk-csharp/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/amzn/amazon-pay-sdk-csharp/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/amzn/amazon-pay-sdk-csharp/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use 4 | this file except in compliance with the License. A copy of the License is 5 | located at http://aws.amazon.com/apache2.0/ or in the "license" file 6 | accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT 7 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 8 | License for the specific language governing permissions and limitations under 9 | the License. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Important:** This SDK has been deprecated. Amazon Pay will continue to support this version but it will not be updated with new features. New integrations should refer this [documentation](https://developer.amazon.com/docs/amazon-pay/intro.html) for more details. 2 | 3 | ## Synopsis 4 | 5 | The official Amazon Pay Python SDK. 6 | 7 | ## Requirements 8 | 9 | Python >= 3.2
10 | pyOpenSSL >= 0.11
11 | Requests >= 2.6.0
12 | 13 | ## Documentation 14 | 15 | * The Integration steps can be found [here](https://pay.amazon.com/developer/documentation) 16 | 17 | ## Sample 18 | 19 | * View the sample integration demo [here](https://amzn.github.io/amazon-pay-sdk-samples/) 20 | 21 | ## Installation 22 | 23 | ``` 24 | $ git clone https://github.com/amzn/amazon-pay-sdk-python.git 25 | $ cd amazon-pay-sdk-python 26 | $ sudo python3 setup.py install 27 | ``` 28 | 29 | PyPI 30 | ``` 31 | $ sudo pip3 install amazon_pay 32 | ``` 33 | 34 | Test it. 35 | ``` 36 | $ python3 37 | Python 3.4.0 (default, Apr 11 2014, 13:05:11) 38 | [GCC 4.8.2] on linux 39 | Type "help", "copyright", "credits" or "license" for more information. 40 | >>> from amazon_pay.client import AmazonPayClient 41 | >>> 42 | ``` 43 | 44 | If you run into problems related to 'IncompleteRead' try the following. 45 | ``` 46 | $ sudo easy_install3 -U pip 47 | ``` 48 | 49 | ## Client Code Examples 50 | *This is only a subset of calls. All MWS Amazon Pay API calls are supported.* 51 | 52 | Instantiate the client. The required parameters are mws_access_key, 53 | mws_secret key, merchant_id, region, and currency_code. 54 | *sandbox* sets up if it is in Sandbox or Production mode. If you do not pass 55 | in the required parameters you must set the corresponding environment variable. 56 | See the [client](https://github.com/amzn/amazon-pay-sdk-python/blob/master/amazon_pay/client.py#L31-L67) 57 | documentation for more information. 58 | ```python 59 | from amazon_pay.client import AmazonPayClient 60 | 61 | client = AmazonPayClient( 62 | mws_access_key='YOUR_ACCESS_KEY', 63 | mws_secret_key='YOUR_SECRET_KEY', 64 | merchant_id='YOUR_MERCHANT_ID', 65 | region='na', 66 | currency_code='USD', 67 | sandbox=True) 68 | ``` 69 | 70 | GetOrderReferenceDetails 71 | ```python 72 | ret = client.get_order_reference_details( 73 | amazon_order_reference_id='AMAZON_ORDER_REFERENCE_ID', 74 | address_consent_token='ADDRESS_CONSENT_TOKEN') 75 | print(ret.to_json()) # to_xml and to_dict are also valid 76 | ``` 77 | 78 | SetOrderReferenceDetails 79 | ```python 80 | ret = client.set_order_reference_details( 81 | amazon_order_reference_id='AMAZON_ORDER_REFERENCE_ID', 82 | order_total='1.00', 83 | seller_note='My seller note.', 84 | seller_order_id='MY_UNIQUE_ORDER_ID', 85 | store_name='My store name.', 86 | custom_information='My custom information.') 87 | print(ret.to_json()) # to_xml and to_dict are also valid 88 | ``` 89 | 90 | ConfirmOrderReference 91 | ```python 92 | ret = client.confirm_order_reference( 93 | amazon_order_reference_id='AMAZON_ORDER_REFERENCE_ID') 94 | print(ret.to_json()) # to_xml and to_dict are also valid 95 | ``` 96 | 97 | Authorize 98 | ```python 99 | ret = client.authorize( 100 | amazon_order_reference_id='AMAZON_ORDER_REFERENCE_ID', 101 | authorization_reference_id='MY_UNIQUE_AUTHORIZATION_ID', 102 | authorization_amount='1.00', 103 | seller_authorization_note='Authorization note.', 104 | transaction_timeout=10, 105 | capture_now=False) 106 | json_response = ret.to_json() 107 | ``` 108 | 109 | GetAuthorizationDetails 110 | ```python 111 | # authorization ID returned from 'Authorize' call. 112 | authorization_id = json.loads(json_response)['AuthorizeResponse'][ 113 | 'AuthorizeResult']['AuthorizationDetails']['AmazonAuthorizationId'] 114 | 115 | ret = client.get_authorization_details( 116 | amazon_authorization_id=authorization_id) 117 | print(ret.to_json()) # to_xml and to_dict are also valid 118 | ``` 119 | 120 | Capture 121 | ```python 122 | # authorization ID returned from 'Authorize' call. 123 | ret = client.capture( 124 | amazon_authorization_id='MY_ATHORIZATION_ID', 125 | capture_reference_id='MY_UNIQUE_CAPTURE_ID', 126 | capture_amount='1.00', 127 | seller_capture_note='Capture note.') 128 | print(ret.to_json()) # to_xml and to_dict are also valid 129 | ``` 130 | 131 | GetCaptureDetails 132 | ```python 133 | # capture ID returned from 'Capture' call. 134 | ret = client.get_capture_details( 135 | amazon_capture_id='MY_CAPTURE_ID') 136 | print(ret.to_json()) # to_xml and to_dict are also valid 137 | ``` 138 | 139 | Charge - This method combines all the above calls into one which allows you to 140 | set, confirm, authorize, and capture in a single call. 141 | If this is a billing agreement it will first check to see what state it's in 142 | to see if it needs to be set. If already set, it will authorize on the billing 143 | agreement. 144 | ```python 145 | ret = client.charge( 146 | amazon_order_reference_id='ORDER_REFERENCE_ID or BILLING_AGREEMENT_ID', 147 | charge_amount='10.00', 148 | charge_note='MY_CHARGE_NOTE', 149 | authorize_reference_id='MY_UNIQUE_AUTHORIZATION_ID') 150 | print(ret.to_json()) 151 | ``` 152 | 153 | Logging has been enabled, if you want to have logging output there are 3 ways it 154 | can be used. If you have logging settings you are currently using and you don't 155 | want them to change you can set log_enabled=True and logging output will follow 156 | your defined logging. If you are not familiar with how to setup logging settings 157 | we have pre-defined settings for you to use. In addition to setting log_enabled 158 | to true, if you want the logging output to a file set log_file_name to the name 159 | and location you want logging output to. If no value is provided logging will be 160 | sent to the console. The log_levels that can be set are "CRITICAL"; "ERROR"; 161 | "WARNING"; "INFO"; "DEBUG"; "NOTSET". In the SDK, only DEBUG is used. 162 | log_file_name and log_level are set to None by default. 163 | log_enabled is set to False. 164 | 165 | Below is an example of how you can enable logging and output to a file. For 166 | additional settings for client please see the client example above. 167 | ```python 168 | from amazon_pay.client import AmazonPayClient 169 | 170 | client = AmazonPayClient( 171 | mws_access_key=session['mws_access_key'], 172 | mws_secret_key=session['mws_secret_key'], 173 | merchant_id=session['merchant_id'], 174 | sandbox=True, 175 | region='na', 176 | currency_code='USD', 177 | log_enabled=True, 178 | log_file_name="log.txt", 179 | log_level="DEBUG") 180 | ``` 181 | 182 | ## Example Responses 183 | 184 | GetOrderReferenceDetails (JSON) 185 | ```json 186 | { 187 | "GetOrderReferenceDetailsResponse": { 188 | "ResponseMetadata": { 189 | "RequestId": "2dfh56f693-0asf-4121-430a-db59e3ec571d" 190 | }, 191 | "GetOrderReferenceDetailsResult": { 192 | "OrderReferenceDetails": { 193 | "CreationTimestamp": "2015-03-05T17:56:11.317Z", 194 | "AmazonOrderReferenceId": "S01-0000000-0000000", 195 | "OrderTotal": { 196 | "CurrencyCode": "USD", 197 | "Amount": "100.00" 198 | }, 199 | "SellerNote": "My seller note.", 200 | "SellerOrderAttributes": { 201 | "CustomInformation": "My custom information.", 202 | "SellerOrderId": "14553", 203 | "StoreName": "My store name." 204 | }, 205 | "ReleaseEnvironment": "Sandbox", 206 | "Buyer": { 207 | "Email": "bob@example.com", 208 | "Name": "Bob" 209 | }, 210 | "Destination": { 211 | "DestinationType": "Physical", 212 | "PhysicalDestination": { 213 | "PostalCode": "60602", 214 | "Phone": "800-000-0000", 215 | "Name": "Susie Smith", 216 | "StateOrRegion": "IL", 217 | "AddressLine2": "Suite 2500", 218 | "AddressLine1": "10 Ditka Ave", 219 | "CountryCode": "US", 220 | "City": "Chicago" 221 | } 222 | }, 223 | "OrderReferenceStatus": { 224 | "LastUpdateTimestamp": "2015-03-05T17:57:16.233Z", 225 | "State": "Open" 226 | }, 227 | "ExpirationTimestamp": "2015-09-01T17:56:11.317Z", 228 | "IdList": { 229 | "member": [ 230 | "S01-0000000-0000000-A000000", 231 | "S01-0000000-0000000-A999999" 232 | ] 233 | } 234 | } 235 | } 236 | } 237 | } 238 | ``` 239 | 240 | GetOrderReferenceDetails (XML) 241 | ```xml 242 | 243 | 244 | 245 | S01-5835994-2647190 246 | 2015-09-01T17:56:11.317Z 247 | My seller note. 248 | 249 | 100.00 250 | USD 251 | 252 | 253 | S01-0000000-0000000-A000000 254 | S01-0000000-0000000-A999999 255 | 256 | 257 | 258 | 2015-03-05T17:57:16.233Z 259 | Open 260 | 261 | 262 | Physical 263 | 264 | 800-000-0000 265 | 60602 266 | Susie Smith 267 | US 268 | IL 269 | Suite 2500 270 | 10 Ditka Ave 271 | Chicago 272 | 273 | 274 | Sandbox 275 | 276 | bob@example.com 277 | Bob 278 | 279 | 280 | My custom information. 281 | My store name. 282 | 14553 283 | 284 | 2015-03-05T17:56:11.317Z 285 | 286 | 287 | 288 | 6c2a39ce-afb3-492e-8e67-4945a9a63f0e 289 | 290 | 291 | ``` 292 | 293 | ## IPN Handler Code Example 294 | Flask 295 | ```python 296 | from flask import request 297 | 298 | @app.route('/ipn_handler', methods=['GET', 'POST']) 299 | def ipn_handler(): 300 | from pay_with_amazon.ipn_handler import IpnHandler 301 | 302 | ret = IpnHandler(request.data, request.headers) 303 | if ret.authenticate(): 304 | return(ret.to_json()) 305 | else: 306 | return(ret.error) 307 | ``` 308 | Response 309 | ```json 310 | { 311 | "OrderReferenceNotification": { 312 | "OrderReference": { 313 | "OrderTotal": { 314 | "CurrencyCode": "USD", 315 | "Amount": "0.0" 316 | }, 317 | "CreationTimestamp": "2013-01-01T01:01:01.001Z", 318 | "OrderReferenceStatus": { 319 | "State": "Closed", 320 | "ReasonCode": "AmazonClosed", 321 | "LastUpdateTimestamp": "2013-01-01T01:01:01.001Z" 322 | }, 323 | "SellerOrderAttributes": null, 324 | "AmazonOrderReferenceId": "P01-0000000-0000000-A000000", 325 | "ExpirationTimestamp": "2013-01-01T01:01:01.001Z" 326 | } 327 | } 328 | } 329 | ``` 330 | 331 | ## Search for Orders 332 | 333 | ListOrderReference 334 | ```python 335 | # This method returns a list of all orders made with the custom ID tag attached 336 | # on each order usually the SellerOrderId. 337 | # For query, you'll want to enter in the tag you wish to search. 338 | # For query_type, currently only SellerOrderId is accepted at this time. 339 | # However, more query types will become available in the future. 340 | ret = client.list_order_reference( 341 | query_id="MY_QUERY_ID", 342 | query_type="MY_QUERY_TYPE") 343 | print(ret.to_json()) 344 | ``` 345 | 346 | Response 347 | ```json 348 | { 349 | "ListOrderReferenceResponse": { 350 | "ListOrderReferenceResult": { 351 | "OrderReferenceList": { 352 | "OrderReference": { 353 | "ReleaseEnvironment": "Sandbox", 354 | "OrderReferenceStatus": { 355 | "LastUpdateTimestamp": "2017-08-10T21:25:38.628Z", 356 | "State": "Open" 357 | }, 358 | "AmazonOrderReferenceId": "S01-0000000-0000000", 359 | "CreationTimestamp": "2017-08-10T21:25:10.592Z", 360 | "SellerOrderAttributes": { 361 | "StoreName": "Test Store Name", 362 | "CustomInformation": "Example Customer Info", 363 | "OrderItemCategories": { 364 | "OrderItemCategory": "Antiques" 365 | }, 366 | "SellerOrderId": "QUERY_ID" 367 | }, 368 | "OrderTotal": { 369 | "CurrencyCode": "USD", 370 | "Amount": "12.00" 371 | } 372 | } 373 | } 374 | }, 375 | "ResponseMetadata": { 376 | "RequestId": "fbd130c0-fc7e-46ca-8d97-248f89c16a1e" 377 | } 378 | } 379 | } 380 | ``` 381 | 382 | ListOrderReferenceByNextToken 383 | ```python 384 | # This method returns a list of the continued orders from the previous call 385 | # using a NextPageToken value to render the next page of data if a page_size 386 | # was used to split the list of orders into multiple pages. 387 | reply = client.list_order_reference_by_next_token( 388 | next_page_token="NEXT_PAGE_TOKEN") 389 | print(ret.to_json()) 390 | ``` 391 | 392 | ## Show the Entire Payment History of an Order 393 | 394 | GetPaymentDetails 395 | ```python 396 | # This method returns the entire payment history of an order in an easy to 397 | # parse format of a list of objects . 398 | 399 | reply = client.get_payment_details(amazon_order_reference_id='AMAZON_ORDER_REFERENCE_ID') 400 | ``` 401 | 402 | Response 403 | ```python 404 | [, 405 | , 406 | ] 407 | ``` 408 | You can convert this data using our to_dict(), to_json(), or to_xml() 409 | and then parse through the response of each item. The example below 410 | shows the basic way to parse through these objects. The example below when 411 | paired with the above information will return the corresponding hex 412 | object as well as all of the information inside of it. This is to 413 | show what is stored, and how you can read through the data. 414 | ```python 415 | for i in range(len(reply)): 416 | query = json.loads(reply[i].to_json()) 417 | if 'GetOrderReferenceDetailsResponse' in query: 418 | print(reply[i]) 419 | print(query['GetOrderReferenceDetailsResponse']) 420 | elif 'GetAuthorizationDetailsResponse' in query: 421 | print(reply[i]) 422 | print(query['GetAuthorizationDetailsResponse']) 423 | elif 'GetCaptureDetailsResponse' in query: 424 | print(reply[i]) 425 | print(query['GetCaptureDetailsResponse']) 426 | elif 'GetRefundDetailsResponse' in query: 427 | print(reply[i]) 428 | print(query['GetRefundDetailsResponse']) 429 | else: 430 | print("Error") 431 | ``` 432 | 433 | ## API Reference 434 | 435 | [Official Amazon Pay API Reference](https://pay.amazon.com/developer/documentation) 436 | 437 | 438 | -------------------------------------------------------------------------------- /amazon_pay/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/amazon-pay-sdk-python/fb26c7950b0c269783476cc221f540ce266bf8ed/amazon_pay/__init__.py -------------------------------------------------------------------------------- /amazon_pay/ap_region.py: -------------------------------------------------------------------------------- 1 | regions = {'uk': 'mws-eu.amazonservices.com', 2 | 'de': 'mws-eu.amazonservices.com', 3 | 'eu': 'mws-eu.amazonservices.com', 4 | 'us': 'mws.amazonservices.com', 5 | 'na': 'mws.amazonservices.com', 6 | 'jp': 'mws.amazonservices.jp'} 7 | -------------------------------------------------------------------------------- /amazon_pay/client.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import sys 4 | import json 5 | import logging 6 | import platform 7 | import amazon_pay.ap_region as ap_region 8 | import amazon_pay.version as ap_version 9 | from amazon_pay.payment_request import PaymentRequest 10 | from fileinput import filename 11 | 12 | class AmazonPayClient: 13 | 14 | logger = logging.getLogger('__amazon_pay_sdk__') 15 | logger.addHandler(logging.NullHandler()) 16 | 17 | """This client allows you to make all the necessary API calls to 18 | integrate with Amazon Pay. 19 | """ 20 | # pylint: disable=too-many-instance-attributes, too-many-public-methods 21 | # pylint: disable=too-many-arguments, too-many-lines 22 | 23 | def __init__( 24 | self, 25 | mws_access_key=None, 26 | mws_secret_key=None, 27 | merchant_id=None, 28 | region=None, 29 | currency_code=None, 30 | sandbox=False, 31 | handle_throttle=True, 32 | application_name=None, 33 | application_version=None, 34 | log_enabled=False, 35 | log_file_name=None, 36 | log_level=None): 37 | 38 | """ 39 | Parameters 40 | ---------- 41 | mws_access_key : string, optional 42 | Your MWS access key. If no value is passed, check environment. 43 | Environment variable: AP_MWS_ACCESS_KEY 44 | (mws_access_key must be passed or specified in environment or this 45 | will result in an error) 46 | 47 | mws_secret_key : string, optional 48 | Your MWS secret key. If no value is passed, check environment. 49 | Environment variable: AP_MWS_SECRET_KEY 50 | (mws_secret_key must be passed or specified in environment or this 51 | will result in an error) 52 | 53 | merchant_id : string, optional 54 | Your merchant ID. If you are a marketplace enter the seller's merchant 55 | ID. If no value is passed, check environment. 56 | Environment variable: AP_MERCHANT_ID 57 | (merchant_id must be passed or specified in environment or this 58 | will result in an error) 59 | 60 | region : string, optional 61 | The region in which you are conducting business. If no value is 62 | passed, check environment. 63 | Environment variable: AP_REGION 64 | (region must be passed or specified in environment or this 65 | will result in an error) 66 | 67 | sandbox : string, optional 68 | Toggle sandbox mode. Default: False. 69 | 70 | currency_code: string, required 71 | Currency code for your region. 72 | Environment variable: AP_CURRENCY_CODE 73 | 74 | handle_throttle: boolean, optional 75 | If requests are throttled, do you want this client to pause and 76 | retry? Default: True 77 | 78 | application_name: string, optional 79 | The name of your application. This will get set in the UserAgent. 80 | Default: None 81 | 82 | application_version: string, optional 83 | Your application version. This will get set in the UserAgent. 84 | Default: None 85 | 86 | log_file_name: string, optional 87 | The name of the file for logging 88 | Default: None 89 | 90 | log_level: integer, optional 91 | The level of logging recorded 92 | Default: "None" 93 | Levels: "CRITICAL"; "ERROR"; "WARNING"; "INFO"; "DEBUG"; "NOTSET" 94 | """ 95 | env_param_map = {'mws_access_key': 'AP_MWS_ACCESS_KEY', 96 | 'mws_secret_key': 'AP_MWS_SECRET_KEY', 97 | 'merchant_id': 'AP_MERCHANT_ID', 98 | 'region': 'AP_REGION', 99 | 'currency_code': 'AP_CURRENCY_CODE'} 100 | for param in env_param_map: 101 | if eval(param) is None: 102 | try: 103 | setattr(self, param, os.environ[env_param_map[param]]) 104 | except: 105 | raise ValueError('Invalid {}.'.format(param)) 106 | else: 107 | setattr(self, param, eval(param)) 108 | 109 | try: 110 | self._region = ap_region.regions[self.region] 111 | # used for Login with Amazon helper 112 | self._region_code = self.region 113 | except KeyError: 114 | raise KeyError('Invalid region code ({})'.format(self.region)) 115 | 116 | self.mws_access_key = self.mws_access_key 117 | self.mws_secret_key = self.mws_secret_key 118 | self.merchant_id = self.merchant_id 119 | self.currency_code = self.currency_code 120 | self.handle_throttle = handle_throttle 121 | self.application_name = application_name 122 | self.application_version = application_version 123 | 124 | self._sandbox = sandbox 125 | self._api_version = ap_version.versions['api_version'] 126 | self._application_library_version = ap_version.versions[ 127 | 'application_version'] 128 | self._mws_endpoint = None 129 | self._set_endpoint() 130 | 131 | if log_enabled is not False: 132 | numeric_level = getattr(logging, log_level.upper(), None) 133 | if numeric_level is not None: 134 | if log_file_name is not None: 135 | self.logger.setLevel(numeric_level) 136 | fh = logging.FileHandler(log_file_name) 137 | self.logger.addHandler(fh) 138 | fh.setLevel(numeric_level) 139 | else: 140 | self.logger.setLevel(numeric_level) 141 | ch = logging.StreamHandler(sys.stdout) 142 | self.logger.addHandler(ch) 143 | ch.setLevel(numeric_level) 144 | 145 | app_name_and_ver = '' 146 | 147 | if application_name not in ['', None]: 148 | app_name_and_ver = app_name_and_ver + str(application_name) 149 | if application_version not in ['', None]: 150 | app_name_and_ver = app_name_and_ver + '/' + str(application_version) 151 | 152 | elif application_version not in ['', None]: 153 | app_name_and_ver = app_name_and_ver + str(application_version) 154 | 155 | if ((application_name not in ['', None]) | (application_version not in ['', None])): 156 | app_name_and_ver = app_name_and_ver + '; ' 157 | 158 | current_py_ver = ".".join(map(str, sys.version_info[:3])) 159 | 160 | self._user_agent = 'amazon-pay-sdk-python/{0} ({1}Python/{2}; {3}/{4})'.format( 161 | str(self._application_library_version), 162 | str(app_name_and_ver), 163 | str(current_py_ver), 164 | str(platform.system()), 165 | str(platform.release()) 166 | ) 167 | 168 | self.logger.debug('user agent: %s', self._user_agent) 169 | 170 | self._headers = { 171 | 'Content-Type': 'application/x-www-form-urlencoded', 172 | 'User-Agent': self._user_agent} 173 | 174 | @property 175 | def sandbox(self): 176 | return self._sandbox 177 | 178 | @sandbox.setter 179 | def sandbox(self, value): 180 | """Set Sandbox mode""" 181 | self._sandbox = value 182 | self._set_endpoint() 183 | 184 | def _set_endpoint(self): 185 | """Set endpoint for API calls""" 186 | if self._sandbox: 187 | self._mws_endpoint = \ 188 | 'https://{}/OffAmazonPayments_Sandbox/{}'.format( 189 | self._region, self._api_version) 190 | else: 191 | self._mws_endpoint = \ 192 | 'https://{}/OffAmazonPayments/{}'.format( 193 | self._region, self._api_version) 194 | 195 | def get_login_profile(self, access_token, client_id): 196 | """Get profile associated with LWA user. This is a helper method for 197 | Login with Amazon (separate service). Added here for convenience. 198 | """ 199 | from amazon_pay.login_with_amazon import LoginWithAmazon 200 | lwa_client = LoginWithAmazon( 201 | client_id=client_id, 202 | region=self._region_code, 203 | sandbox=self._sandbox) 204 | response = lwa_client.get_login_profile(access_token=access_token) 205 | return response 206 | 207 | def get_merchant_account_status( 208 | self, 209 | merchant_id=None, 210 | mws_auth_token=None): 211 | """ Check the account status of the merchant 212 | 213 | Parameters 214 | ---------- 215 | merchant_id : string, optional 216 | Your merchant ID. If you are a marketplace enter the seller's merchant 217 | ID. 218 | 219 | mws_auth_token: string, optional 220 | Your marketplace web service auth token. Default: None 221 | 222 | """ 223 | parameters = { 224 | 'Action': 'GetMerchantAccountStatus' 225 | } 226 | 227 | optionals = { 228 | 'SellerId': merchant_id, 229 | 'MWSAuthToken': mws_auth_token} 230 | 231 | return self._operation(params=parameters, options=optionals) 232 | 233 | def create_order_reference_for_id( 234 | self, 235 | object_id, 236 | object_id_type, 237 | order_total, 238 | inherit_shipping_address=True, 239 | confirm_now=False, 240 | platform_id=None, 241 | seller_note=None, 242 | seller_order_id=None, 243 | store_name=None, 244 | custom_information=None, 245 | merchant_id=None, 246 | mws_auth_token=None, 247 | supplementary_data=None): 248 | # pylint: disable=too-many-arguments 249 | """Creates an order reference for the given object. 250 | 251 | Parameters 252 | ---------- 253 | object_id : string, required 254 | The identifier of the object to be used to create an order reference. 255 | 256 | object_id_type : string, required 257 | The type of the object represented by the Id request parameter. 258 | 259 | order_total : string, required 260 | Specifies the total amount of the order represented by this order 261 | reference. 262 | 263 | inherit_shipping_address : boolean, optional 264 | Specifies whether to inherit the shipping address details from the 265 | object represented by the Id request parameter. Default: True 266 | 267 | confirm_now : boolean, optional 268 | Indicates whether to directly confirm the requested order reference. 269 | Default: False 270 | 271 | merchant_id : string, required 272 | Your merchant ID. If you are a marketplace enter the seller's merchant 273 | ID. 274 | 275 | mws_auth_token: string, optional 276 | Your marketplace web service auth token. Default: None 277 | 278 | supplementary_data: string, optional 279 | Only use if instructed to do so by Amazon Pay. JSON string. Default: None 280 | 281 | """ 282 | parameters = { 283 | 'Action': 'CreateOrderReferenceForId', 284 | 'Id': object_id, 285 | 'IdType': object_id_type, 286 | 'OrderReferenceAttributes.OrderTotal.Amount': order_total, 287 | 'OrderReferenceAttributes.OrderTotal.CurrencyCode': self.currency_code} 288 | optionals = { 289 | 'InheritShippingAddress': str(inherit_shipping_address).lower(), 290 | 'ConfirmNow': str(confirm_now).lower(), 291 | 'OrderReferenceAttributes.PlatformId': platform_id, 292 | 'OrderReferenceAttributes.SellerNote': seller_note, 293 | 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId': seller_order_id, 294 | 'OrderReferenceAttributes.SellerOrderAttributes.StoreName': store_name, 295 | 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation': custom_information, 296 | 'SellerId': merchant_id, 297 | 'OrderReferenceAttributes.SupplementaryData': supplementary_data, 298 | 'MWSAuthToken': mws_auth_token} 299 | 300 | return self._operation(params=parameters, options=optionals) 301 | 302 | def get_billing_agreement_details( 303 | self, 304 | amazon_billing_agreement_id, 305 | address_consent_token=None, 306 | merchant_id=None, 307 | mws_auth_token=None): 308 | """Returns details about the Billing Agreement object and its current 309 | state. 310 | 311 | Parameters 312 | ---------- 313 | amazon_billing_agreement_id : string, required 314 | The billing agreement identifier. 315 | 316 | address_consent_token : string, optional 317 | The buyer address consent token. You must provide a valid 318 | AddressConsentToken if you want to get the full shipping address 319 | before the billing agreement is confirmed. Otherwise you will only 320 | receive the city, state, postal code, and country before you confirm 321 | the billing agreement. Default: None 322 | 323 | merchant_id : string, required 324 | Your merchant ID. If you are a marketplace enter the seller's merchant 325 | ID. 326 | 327 | mws_auth_token: string, optional 328 | Your marketplace web service auth token. Default: None 329 | """ 330 | parameters = { 331 | 'Action': 'GetBillingAgreementDetails', 332 | 'AmazonBillingAgreementId': amazon_billing_agreement_id} 333 | optionals = { 334 | 'AddressConsentToken': address_consent_token, 335 | 'SellerId': merchant_id, 336 | 'MWSAuthToken': mws_auth_token} 337 | return self._operation(params=parameters, options=optionals) 338 | 339 | def set_billing_agreement_details( 340 | self, 341 | amazon_billing_agreement_id, 342 | platform_id=None, 343 | seller_note=None, 344 | seller_billing_agreement_id=None, 345 | store_name=None, 346 | custom_information=None, 347 | merchant_id=None, 348 | mws_auth_token=None): 349 | # pylint: disable=too-many-arguments 350 | """Sets billing agreement details such as a description of the agreement 351 | and other information about the seller. 352 | 353 | Parameters 354 | ---------- 355 | amazon_billing_agreement_id : string, required 356 | The billing agreement identifier. 357 | 358 | platform_id : string, optional 359 | Represents the SellerId of the Solution Provider that developed the 360 | platform. This value should only be provided by solution providers. 361 | It should not be provided by sellers creating their own custom 362 | integration. 363 | 364 | seller_note : string, optional 365 | Represents a description of the billing agreement that is displayed 366 | in emails to the buyer. 367 | 368 | seller_billing_agreement_id : string, optional 369 | The seller-specified identifier of this billing agreement. 370 | 371 | store_name : string, optional 372 | The identifier of the store from which the order was placed. This 373 | overrides the default value in Seller Central under Settings > 374 | Account Settings. It is displayed to the buyer in the email they 375 | receive from Amazon and also in their transaction history on the 376 | Amazon Pay website. 377 | 378 | custom_information : string, optional 379 | Any additional information you wish to include with this billing 380 | agreement. 381 | 382 | merchant_id : string, required 383 | Your merchant ID. If you are a marketplace enter the seller's merchant 384 | ID. 385 | 386 | mws_auth_token: string, optional 387 | Your marketplace web service auth token. Default: None 388 | """ 389 | parameters = { 390 | 'Action': 'SetBillingAgreementDetails', 391 | 'AmazonBillingAgreementId': amazon_billing_agreement_id} 392 | optionals = { 393 | 'BillingAgreementAttributes.PlatformId': platform_id, 394 | 'BillingAgreementAttributes.SellerNote': seller_note, 395 | 'BillingAgreementAttributes.SellerBillingAgreementAttributes.SellerBillingAgreementId': seller_billing_agreement_id, 396 | 'BillingAgreementAttributes.SellerBillingAgreementAttributes.StoreName': store_name, 397 | 'BillingAgreementAttributes.SellerBillingAgreementAttributes.CustomInformation': custom_information, 398 | 'SellerId': merchant_id, 399 | 'MWSAuthToken': mws_auth_token} 400 | return self._operation(params=parameters, options=optionals) 401 | 402 | def confirm_billing_agreement( 403 | self, 404 | amazon_billing_agreement_id, 405 | merchant_id=None, 406 | mws_auth_token=None): 407 | """Confirms that the billing agreement is free of constraints and all 408 | required information has been set on the billing agreement. 409 | 410 | Parameters 411 | ---------- 412 | amazon_billing_agreement_id : string, required 413 | The billing agreement identifier. 414 | 415 | merchant_id : string, required 416 | Your merchant ID. If you are a marketplace enter the seller's merchant 417 | ID. 418 | 419 | mws_auth_token: string, optional 420 | Your marketplace web service auth token. Default: None 421 | """ 422 | parameters = { 423 | 'Action': 'ConfirmBillingAgreement', 424 | 'AmazonBillingAgreementId': amazon_billing_agreement_id} 425 | optionals = { 426 | 'SellerId': merchant_id, 427 | 'MWSAuthToken': mws_auth_token} 428 | return self._operation(params=parameters, options=optionals) 429 | 430 | def validate_billing_agreement( 431 | self, 432 | amazon_billing_agreement_id, 433 | merchant_id=None, 434 | mws_auth_token=None): 435 | """Validates the status of the BillingAgreement object and the payment 436 | method associated with it. 437 | 438 | Parameters 439 | ---------- 440 | amazon_billing_agreement_id : string, required 441 | The billing agreement identifier. 442 | 443 | merchant_id : string, required 444 | Your merchant ID. If you are a marketplace enter the seller's merchant 445 | ID. 446 | 447 | mws_auth_token: string, optional 448 | Your marketplace web service auth token. Default: None 449 | """ 450 | parameters = { 451 | 'Action': 'ValidateBillingAgreement', 452 | 'AmazonBillingAgreementId': amazon_billing_agreement_id} 453 | optionals = { 454 | 'SellerId': merchant_id, 455 | 'MWSAuthToken': mws_auth_token} 456 | return self._operation(params=parameters, options=optionals) 457 | 458 | def authorize_on_billing_agreement( 459 | self, 460 | amazon_billing_agreement_id, 461 | authorization_reference_id, 462 | authorization_amount, 463 | seller_authorization_note=None, 464 | transaction_timeout=1440, 465 | capture_now=False, 466 | soft_descriptor=None, 467 | seller_note=None, 468 | platform_id=None, 469 | seller_order_id=None, 470 | store_name=None, 471 | custom_information=None, 472 | inherit_shipping_address=True, 473 | merchant_id=None, 474 | mws_auth_token=None, 475 | supplementary_data=None): 476 | # pylint: disable=too-many-arguments 477 | """Reserves a specified amount against the payment method(s) stored in 478 | the billing agreement. 479 | 480 | Parameters 481 | ---------- 482 | amazon_billing_agreement_id : string, required 483 | The billing agreement identifier. 484 | 485 | authorization_reference_id : string, required 486 | The identifier for this authorization transaction that you specify. 487 | This identifier must be unique for all your transactions 488 | (authorization, capture, refund, etc.). 489 | 490 | authorization_amount : string, required 491 | Represents the amount to be authorized. 492 | 493 | seller_authorization_note : string, optional 494 | A description for the transaction that is displayed in emails to the 495 | buyer. Default: None 496 | 497 | transaction_timeout : unsigned integer, optional 498 | The number of minutes after which the authorization will 499 | automatically be closed and you will not be able to capture funds 500 | against the authorization. Default: 1440 501 | 502 | capture_now : boolean, optional 503 | Indicates whether to directly capture the amount specified by the 504 | AuthorizationAmount request parameter against an order reference 505 | (without needing to call Capture and without waiting until the order 506 | ships). The captured amount is disbursed to your account in the next 507 | disbursement cycle. Default: False 508 | 509 | seller_note : string, optional 510 | Represents a description of the order that is displayed in emails to 511 | the buyer. Default: None 512 | 513 | platform_id : string, optional 514 | Represents the SellerId of the Solution Provider that developed the 515 | platform. This value should only be provided by Solution Providers. 516 | It should not be provided by sellers creating their own custom 517 | integration. Default: None 518 | 519 | seller_order_id : string, optional 520 | The seller-specified identifier of this order. This is displayed to 521 | the buyer in the email they receive from Amazon and transaction 522 | history on the Amazon Pay website. Default: None 523 | 524 | store_name : string, optional 525 | The identifier of the store from which the order was placed. This 526 | overrides the default value in Seller Central under Settings > 527 | Account Settings. It is displayed to the buyer in the email they 528 | receive from Amazon and also in their transaction history on the 529 | Amazon Pay website. Default: None 530 | 531 | custom_information : string, optional 532 | Any additional information you wish to include with this order 533 | reference. Default: None 534 | 535 | inherit_shipping_address : boolean, optional 536 | Specifies whether to inherit the shipping address details from the 537 | object represented by the Id request parameter. Default: True 538 | 539 | supplementary_data: string, optional 540 | Only use if instructed to do so by Amazon Pay. JSON string. Default: None 541 | 542 | merchant_id : string, required 543 | Your merchant ID. If you are a marketplace enter the seller's merchant 544 | ID. 545 | 546 | mws_auth_token: string, optional 547 | Your marketplace web service auth token. Default: None 548 | """ 549 | parameters = { 550 | 'Action': 'AuthorizeOnBillingAgreement', 551 | 'AmazonBillingAgreementId': amazon_billing_agreement_id, 552 | 'TransactionTimeout': transaction_timeout, 553 | 'AuthorizationReferenceId': authorization_reference_id, 554 | 'AuthorizationAmount.Amount': authorization_amount, 555 | 'AuthorizationAmount.CurrencyCode': self.currency_code} 556 | optionals = { 557 | 'CaptureNow': str(capture_now).lower(), 558 | 'SellerAuthorizationNote': seller_authorization_note, 559 | 'SoftDescriptor': soft_descriptor, 560 | 'SellerNote': seller_note, 561 | 'PlatformId': platform_id, 562 | 'InheritShippingAddress': str(inherit_shipping_address).lower(), 563 | 'SellerOrderAttributes.SellerOrderId': seller_order_id, 564 | 'SellerOrderAttributes.StoreName': store_name, 565 | 'SellerOrderAttributes.CustomInformation': custom_information, 566 | 'SellerOrderAttributes.SupplementaryData': supplementary_data, 567 | 'SellerId': merchant_id, 568 | 'MWSAuthToken': mws_auth_token} 569 | return self._operation(params=parameters, options=optionals) 570 | 571 | def close_billing_agreement( 572 | self, 573 | amazon_billing_agreement_id, 574 | closure_reason=None, 575 | merchant_id=None, 576 | mws_auth_token=None): 577 | """Confirms that you want to terminate the billing agreement with the 578 | buyer and that you do not expect to create any new order references or 579 | authorizations on this billing agreement. 580 | 581 | Parameters 582 | ---------- 583 | amazon_billing_agreement_id : string, required 584 | The billing agreement identifier. 585 | 586 | closure_reason : string, optional 587 | Describes the reason for closing the billing agreement. 588 | Default: None 589 | 590 | merchant_id : string, required 591 | Your merchant ID. If you are a marketplace enter the seller's merchant 592 | ID. 593 | 594 | mws_auth_token: string, optional 595 | Your marketplace web service auth token. Default: None 596 | """ 597 | parameters = { 598 | 'Action': 'CloseBillingAgreement', 599 | 'AmazonBillingAgreementId': amazon_billing_agreement_id} 600 | optionals = { 601 | 'ClosureReason': closure_reason, 602 | 'SellerId': merchant_id, 603 | 'MWSAuthToken': mws_auth_token} 604 | return self._operation(params=parameters, options=optionals) 605 | 606 | def set_order_reference_details( 607 | self, 608 | amazon_order_reference_id, 609 | order_total, 610 | platform_id=None, 611 | seller_note=None, 612 | seller_order_id=None, 613 | store_name=None, 614 | custom_information=None, 615 | merchant_id=None, 616 | mws_auth_token=None, 617 | supplementary_data=None): 618 | """Sets order reference details such as the order total and a 619 | description for the order. 620 | 621 | Parameters 622 | ---------- 623 | amazon_order_reference_id : string, required 624 | The order reference identifier retrieved from the amazon pay Button 625 | widget. 626 | 627 | order_total : string, required 628 | Specifies the total amount of the order represented by this order 629 | reference. 630 | 631 | platform_id : string, optional 632 | Represents the SellerId of the Solution Provider that developed the 633 | platform. This value should only be provided by Solution Providers. 634 | It should not be provided by sellers creating their own custom 635 | integration. Default: None 636 | 637 | seller_note : string, optional 638 | Represents a description of the order that is displayed in emails 639 | to the buyer. Default: None 640 | 641 | seller_order_id : string, optional 642 | The seller-specified identifier of this order. This is displayed to 643 | the buyer in the email they receive from Amazon and also in their 644 | transaction history on the Amazon Pay website. Default: None 645 | 646 | store_name : string, optional 647 | The identifier of the store from which the order was placed. This 648 | overrides the default value in Seller Central under Settings > 649 | Account Settings. It is displayed to the buyer in the email they 650 | receive from Amazon and also in their transaction history on the 651 | Amazon Pay website. Default: None 652 | 653 | custom_information : string, optional 654 | Any additional information you wish to include with this order 655 | reference. Default: None 656 | 657 | merchant_id : string, required 658 | Your merchant ID. If you are a marketplace enter the seller's merchant 659 | ID. 660 | 661 | mws_auth_token: string, optional 662 | Your marketplace web service auth token. Default: None 663 | 664 | supplementary_data: string, optional 665 | Only use if instructed to do so by Amazon Pay. JSON string. Default: None 666 | """ 667 | parameters = { 668 | 'Action': 'SetOrderReferenceDetails', 669 | 'AmazonOrderReferenceId': amazon_order_reference_id, 670 | 'OrderReferenceAttributes.OrderTotal.Amount': order_total, 671 | 'OrderReferenceAttributes.OrderTotal.CurrencyCode': self.currency_code} 672 | optionals = { 673 | 'OrderReferenceAttributes.PlatformId': platform_id, 674 | 'OrderReferenceAttributes.SellerNote': seller_note, 675 | 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId': seller_order_id, 676 | 'OrderReferenceAttributes.SellerOrderAttributes.StoreName': store_name, 677 | 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation': custom_information, 678 | 'OrderReferenceAttributes.SellerOrderAttributes.SupplementaryData': supplementary_data, 679 | 'SellerId': merchant_id, 680 | 'MWSAuthToken': mws_auth_token} 681 | return self._operation(params=parameters, options=optionals) 682 | 683 | def set_order_attributes( 684 | self, 685 | amazon_order_reference_id, 686 | currency_code=None, 687 | amount=None, 688 | seller_order_id=None, 689 | payment_service_provider_id=None, 690 | payment_service_provider_order_id=None, 691 | platform_id=None, 692 | seller_note=None, 693 | request_payment_authorization=None, 694 | store_name=None, 695 | list_order_item_categories=None, 696 | custom_information=None, 697 | merchant_id=None, 698 | mws_auth_token=None, 699 | supplementary_data=None): 700 | ''' 701 | Return and update the information of an order with missing 702 | or updated information 703 | 704 | Parameters 705 | ---------- 706 | amazon_order_reference_id : string, required 707 | The order reference identifier retrieved from the amazon pay Button 708 | widget. 709 | 710 | merchant_id : string, optional 711 | Your merchant ID. If you are a marketplace enter the seller's merchant 712 | ID. 713 | 714 | mws_auth_token: string, optional 715 | Your marketplace web service auth token. Default: None 716 | 717 | currency_code : string, optional 718 | The currency you're accepting the order in. A three-digit 719 | currency code, formatted based on the ISO 4217 standard. 720 | Default: None 721 | 722 | amount : string, optional 723 | Specifies the total amount of the order represented by this order 724 | reference. Default: None 725 | 726 | seller_order_id : string, optional 727 | The seller-specified identifier of this order. This is displayed to 728 | the buyer in the email they receive from Amazon and also in their 729 | transaction history on the Amazon Pay website. Default: None 730 | 731 | payment_service_provider_id : string, optional 732 | For use with a Payment Service Provider. This is their specific 733 | ID that is associated with Amazon Pay accounts and services. 734 | Default: None 735 | 736 | payment_service_provider_order_id : string, optional 737 | For use with a Payment Service Provider. This is their specific 738 | ID that is linked to your specific Amazon Pay Order Reference ID. 739 | Default: None 740 | 741 | platform_id : string, optional 742 | Represents the SellerId of the Solution Provider that developed the 743 | platform. This value should only be provided by Solution Providers. 744 | It should not be provided by sellers creating their own custom 745 | integration. Default: None 746 | 747 | seller_note : string, optional 748 | Represents a description of the order that is displayed in 749 | e-mails to the buyer. Default: None 750 | 751 | request_payment_authorization : boolean (string), optional 752 | Specifies if the merchants want their buyers to go through 753 | multi-factor authentication. Default: None 754 | 755 | store_name : string, optional 756 | The identifier of the store from which the order was placed. This 757 | overrides the default value in Seller Central under Settings > 758 | Account Settings. It is displayed to the buyer in the email they 759 | receive from Amazon and also in their transaction history on the 760 | Amazon Pay website. Default: None 761 | 762 | list_order_item_categories : list (string), optional 763 | List the category, or categories, that correlate to the order 764 | in question. You may set more than one item. Default: None 765 | 766 | custom_information : string, optional 767 | Any additional information you want your back-end system to 768 | keep record of. Your customers will not see this, and this 769 | will not be visible on Seller Central. This can only be 770 | accessed if your back end system supports calling this variable. 771 | Default: None 772 | 773 | supplementary_data: string, optional 774 | Only use if instructed to do so by Amazon Pay. JSON string. Default: None 775 | ''' 776 | 777 | parameters = { 778 | 'Action': 'SetOrderAttributes', 779 | 'AmazonOrderReferenceId': amazon_order_reference_id 780 | } 781 | 782 | optionals = { 783 | 'OrderAttributes.OrderTotal.Amount': amount, 784 | 'OrderAttributes.OrderTotal.CurrencyCode': currency_code, 785 | 'OrderAttributes.SellerOrderAttributes.CustomInformation': custom_information, 786 | 'OrderAttributes.PaymentServiceProviderAttributes.PaymentServiceProviderId': 787 | payment_service_provider_id, 788 | 'OrderAttributes.PaymentServiceProviderAttributes.PaymentServiceProviderOrderId': 789 | payment_service_provider_order_id, 790 | 'OrderAttributes.PlatformId': platform_id, 791 | 'OrderAttributes.RequestPaymentAuthorization': request_payment_authorization, 792 | 'OrderAttributes.SellerNote': seller_note, 793 | 'OrderAttributes.SellerOrderAttributes.SellerOrderId': seller_order_id, 794 | 'OrderAttributes.SellerOrderAttributes.StoreName': store_name, 795 | 'OrderAttributes.SellerOrderAttributes.SupplementaryData': supplementary_data, 796 | 'SellerId': merchant_id, 797 | 'MWSAuthToken': mws_auth_token 798 | } 799 | 800 | if list_order_item_categories is not None: 801 | self._enumerate( 802 | 'OrderAttributes.SellerOrderAttributes.OrderItemCategories.OrderItemCategory.', 803 | list_order_item_categories, optionals) 804 | 805 | return self._operation(params=parameters, options=optionals) 806 | 807 | 808 | def get_order_reference_details( 809 | self, 810 | amazon_order_reference_id, 811 | access_token=None, 812 | address_consent_token=None, 813 | merchant_id=None, 814 | mws_auth_token=None): 815 | """Returns details about the order reference object and its current 816 | state. 817 | 818 | Parameters 819 | ---------- 820 | amazon_order_reference_id : string, optional 821 | The order reference identifier. This value is retrieved from the 822 | amazon pay Button widget after the buyer has successfully authenticated 823 | with Amazon. 824 | 825 | access_token : string, optional 826 | The access token. This value is retrieved from the 827 | amazon pay Button widget after the buyer has successfully authenticated 828 | with Amazon. (Note: When using this value, you cannot use the 829 | address_consent_token at the same time, or this will cause an error. 830 | The same note applies when using just the address_consent_token) 831 | 832 | address_consent_token : string, optional 833 | The buyer address consent token. This value is retrieved from the 834 | amazon pay Button widget after the buyer has successfully authenticated 835 | with Amazon. 836 | 837 | merchant_id : string, required 838 | Your merchant ID. If you are a marketplace enter the seller's merchant 839 | ID. 840 | 841 | mws_auth_token: string, optional 842 | Your marketplace web service auth token. Default: None 843 | """ 844 | parameters = { 845 | 'Action': 'GetOrderReferenceDetails', 846 | 'AmazonOrderReferenceId': amazon_order_reference_id 847 | } 848 | optionals = { 849 | 'AddressConsentToken': address_consent_token, 850 | 'AccessToken': access_token, 851 | 'SellerId': merchant_id, 852 | 'MWSAuthToken': mws_auth_token} 853 | return self._operation(params=parameters, options=optionals) 854 | 855 | def confirm_order_reference( 856 | self, 857 | amazon_order_reference_id, 858 | merchant_id=None, 859 | mws_auth_token=None, 860 | success_url=None, 861 | failure_url=None, 862 | authorization_amount=None, 863 | currency_code=None, 864 | expect_immediate_authorization=None): 865 | """Confirms that the order reference is free of constraints and all 866 | required information has been set on the order reference. 867 | 868 | Parameters 869 | ---------- 870 | amazon_order_reference_id : string : required 871 | The order reference identifier. 872 | 873 | merchant_id : string, required 874 | Your merchant ID. If you are a marketplace enter the seller's merchant 875 | ID. 876 | 877 | mws_auth_token: string, optional 878 | Your marketplace web service auth token. Default: None 879 | 880 | success_url: string, optional 881 | Represents the return URL for SCA success. 882 | 883 | failure_url: string, optional 884 | Represents the return URL for SCA failure. 885 | 886 | authorization_amount: string, optional 887 | Represents the amount to be authorized. If blank both it and currency_code will be excluded. 888 | 889 | currency_code: string, optional 890 | Currency code for your region. 891 | Environment variable: AP_CURRENCY_CODE 892 | 893 | expect_immediate_authorization: boolean, optional 894 | If this value is set to true, the ORO will auto-close in case no authorize is triggered within 60 minutes after the confirm. Default: None 895 | 896 | """ 897 | parameters = { 898 | 'Action': 'ConfirmOrderReference', 899 | 'AmazonOrderReferenceId': amazon_order_reference_id} 900 | optionals = { 901 | 'SellerId': merchant_id, 902 | 'MWSAuthToken': mws_auth_token, 903 | 'SuccessUrl': success_url, 904 | 'FailureUrl': failure_url, 905 | 'AuthorizationAmount.Amount': authorization_amount, 906 | 'AuthorizationAmount.CurrencyCode': self.currency_code if currency_code is None else currency_code, 907 | 'ExpectImmediateAuthorization': str(expect_immediate_authorization).lower() if expect_immediate_authorization is not None else None} 908 | 909 | if authorization_amount == "0" or authorization_amount is None: 910 | del optionals['AuthorizationAmount.Amount'] 911 | del optionals['AuthorizationAmount.CurrencyCode'] 912 | 913 | return self._operation(params=parameters, options=optionals) 914 | 915 | def cancel_order_reference( 916 | self, 917 | amazon_order_reference_id, 918 | cancelation_reason=None, 919 | merchant_id=None, 920 | mws_auth_token=None): 921 | """Cancels a previously confirmed order reference. 922 | 923 | Parameters 924 | ---------- 925 | amazon_order_reference_id : string, required 926 | The order reference identifier. 927 | 928 | cancelation_reason : string, optional 929 | Describes the reason for the cancelation. Default: None 930 | 931 | merchant_id : string, required 932 | Your merchant ID. If you are a marketplace enter the seller's merchant 933 | ID. 934 | 935 | mws_auth_token: string, optional 936 | Your marketplace web service auth token. Default: None 937 | """ 938 | parameters = { 939 | 'Action': 'CancelOrderReference', 940 | 'AmazonOrderReferenceId': amazon_order_reference_id} 941 | optionals = { 942 | 'CancelationReason': cancelation_reason, 943 | 'SellerId': merchant_id, 944 | 'MWSAuthToken': mws_auth_token} 945 | return self._operation(params=parameters, options=optionals) 946 | 947 | def close_order_reference( 948 | self, 949 | amazon_order_reference_id, 950 | closure_reason=None, 951 | merchant_id=None, 952 | mws_auth_token=None): 953 | """Confirms that an order reference has been fulfilled 954 | (fully or partially) and that you do not expect to create any new 955 | authorizations on this order reference. 956 | 957 | Parameters 958 | ---------- 959 | amazon_order_reference_id : string, required 960 | The ID of the order reference for which the details are being 961 | requested. 962 | 963 | closure_reason : string, optional 964 | Describes the reason for closing the order reference. Default: None 965 | 966 | merchant_id : string, required 967 | Your merchant ID. If you are a marketplace enter the seller's merchant 968 | ID. 969 | 970 | mws_auth_token: string, optional 971 | Your marketplace web service auth token. Default: None 972 | """ 973 | parameters = { 974 | 'Action': 'CloseOrderReference', 975 | 'AmazonOrderReferenceId': amazon_order_reference_id} 976 | optionals = { 977 | 'ClosureReason': closure_reason, 978 | 'SellerId': merchant_id, 979 | 'MWSAuthToken': mws_auth_token} 980 | return self._operation(params=parameters, options=optionals) 981 | 982 | def list_order_reference( 983 | self, 984 | query_id, 985 | query_id_type, 986 | created_time_range_start=None, 987 | created_time_range_end=None, 988 | sort_order=None, 989 | payment_domain=None, 990 | page_size=None, 991 | order_reference_status_list_filter=None, 992 | merchant_id=None, 993 | mws_auth_token=None): 994 | 995 | """ 996 | Allows the search of any Amazon Pay order made using secondary 997 | seller order IDs generated manually, a solution provider, or a custom 998 | order fulfillment service. 999 | 1000 | Parameters 1001 | ========================================== 1002 | query_id: string, required 1003 | The identifier that the merchant wishes to use in relation to the 1004 | query id type. 1005 | 1006 | query_id_type: string, required 1007 | The type of query the id is referencing. 1008 | Note: At this time, you can only use the query type (SellerOrderId). 1009 | More options will be available in the future. Default: SellerOrderId 1010 | 1011 | payment_domain: string, optional 1012 | The region and currency that will be set to authorize and collect 1013 | payments from your customers. You can leave this blank for the 1014 | system to automatically assign the default payment domain for 1015 | your region. 1016 | 1017 | created_time_range_start: string, optional 1018 | This filter will allow a merchant to search for a particular item 1019 | within a date range of their choice. 1020 | Note: If you wish to use this filter, you MUST fill in an end date, 1021 | otherwise you will get an error returned when searching. You must 1022 | also use the ISO 8601 time format to return a valid response when 1023 | searching in a date range. Either of the two examples will work 1024 | when using this filter. Default: None 1025 | Example: YYYY-MM-DD or YYYY-MM-DDTHH:MM. 1026 | 1027 | created_time_range_end: string, optional 1028 | The end-date for the date range the merchant wishes to search for 1029 | any of their orders they're looking for. 1030 | Note: You need to only use this option if you are using the 1031 | created_time_range_start parameter. Default: None 1032 | 1033 | sort_order: string, optional 1034 | Filter can be set for "Ascending", or "Descending" order, and must 1035 | be written out as shown above. This will sort the orders via 1036 | the respective option. Default: None 1037 | 1038 | page_size: integer, optional 1039 | This filter limits how many results will be displayed per 1040 | request. Default: None 1041 | 1042 | merchant_id : string, optional 1043 | Your merchant ID. If you are a marketplace enter the seller's merchant 1044 | ID. 1045 | 1046 | mws_auth_token: string, optional 1047 | Your marketplace web service auth token. Default: None 1048 | 1049 | order_reference_status_list_filter: list (string), optional 1050 | When searching for an order, this filter is related to the status 1051 | of the orders on file. You can search for any valid status for orders 1052 | on file. Filters MUST be written out in English. 1053 | Example: "Open", "Closed", "Suspended", "Canceled" 1054 | Default: None 1055 | """ 1056 | 1057 | if self.region is not None: 1058 | region_code = self.region.lower() 1059 | if region_code == 'na': 1060 | payment_domain = 'NA_USD' 1061 | elif region_code in ('uk', 'gb'): 1062 | payment_domain = 'EU_GBP' 1063 | elif region_code in ('jp', 'fe'): 1064 | payment_domain = 'FE_JPY' 1065 | elif region_code in ('eu', 'de', 'fr', 'it', 'es', 'cy'): 1066 | payment_domain = 'EU_EUR' 1067 | else: 1068 | raise ValueError("Error. The current region code does not match our records") 1069 | 1070 | parameters = { 1071 | 'Action': 'ListOrderReference', 1072 | 'QueryId': query_id, 1073 | 'QueryIdType': query_id_type, 1074 | 'PaymentDomain': payment_domain 1075 | } 1076 | optionals = { 1077 | 'CreatedTimeRange.StartTime': created_time_range_start, 1078 | 'CreatedTimeRange.EndTime': created_time_range_end, 1079 | 'SortOrder': sort_order, 1080 | 'SellerId': merchant_id, 1081 | 'MWSAuthToken': mws_auth_token, 1082 | 'PageSize': page_size 1083 | } 1084 | 1085 | if order_reference_status_list_filter is not None: 1086 | self._enumerate( 1087 | 'OrderReferenceStatusListFilter.OrderReferenceStatus.', 1088 | order_reference_status_list_filter, optionals) 1089 | 1090 | return self._operation(params=parameters, options=optionals) 1091 | 1092 | def list_order_reference_by_next_token( 1093 | self, 1094 | next_page_token, 1095 | merchant_id=None, 1096 | mws_auth_token=None): 1097 | """ 1098 | next_page_token : string, required 1099 | Uses the key from a list_order_reference call that provides a 1100 | NextPageToken for a merchant to call to review the next page 1101 | of items, and if applicable, another NextPageToken for the next 1102 | set of items to read through. 1103 | 1104 | merchant_id : string, optional 1105 | Your merchant ID. If you are a marketplace enter the seller's merchant 1106 | ID. 1107 | 1108 | mws_auth_token: string, optional 1109 | Your marketplace web service auth token. Default: None 1110 | 1111 | """ 1112 | 1113 | parameters = { 1114 | 'Action': 'ListOrderReferenceByNextToken', 1115 | 'NextPageToken': next_page_token} 1116 | optionals = { 1117 | 'SellerId': merchant_id, 1118 | 'MWSAuthToken': mws_auth_token} 1119 | return self._operation(params=parameters, options=optionals) 1120 | 1121 | def get_payment_details( 1122 | self, 1123 | amazon_order_reference_id, 1124 | merchant_id=None, 1125 | mws_auth_token=None): 1126 | 1127 | ''' 1128 | This is a convenience function that will return every authorization, 1129 | charge, and refund call of an Amazon Pay order ID. 1130 | 1131 | Parameters 1132 | ---------- 1133 | amazon_order_reference_id: string, required 1134 | The ID of the order reference for which the details are being 1135 | requested. 1136 | 1137 | merchant_id : string, optional 1138 | Your merchant ID. If you are a marketplace enter the seller's merchant 1139 | ID. 1140 | 1141 | mws_auth_token: string, optional 1142 | Your marketplace web service auth token. Default: None 1143 | ''' 1144 | 1145 | parameters = { 1146 | 'Action': 'GetOrderReferenceDetails', 1147 | 'AmazonOrderReferenceId': amazon_order_reference_id 1148 | } 1149 | 1150 | optionals = { 1151 | 'SellerId': merchant_id, 1152 | 'MWSAuthToken': mws_auth_token 1153 | } 1154 | 1155 | query = self._operation(params=parameters, options=optionals) 1156 | answer = [] 1157 | answer.append(query) 1158 | queryID = json.loads(query.to_json()) 1159 | memberID = queryID['GetOrderReferenceDetailsResponse']\ 1160 | ['GetOrderReferenceDetailsResult']['OrderReferenceDetails']['IdList'] 1161 | 1162 | if memberID is not None: 1163 | ''' 1164 | This check will see if the variable is in a list form or not 1165 | if it is not it will covert it into a single item list. 1166 | Otherwise, it will process the variable normally as a list. 1167 | ''' 1168 | if type(memberID['member']) is not list: 1169 | memberID = [memberID['member']] 1170 | else: 1171 | memberID = memberID['member'] 1172 | 1173 | for id in memberID: 1174 | parameters = { 1175 | 'Action': 'GetAuthorizationDetails', 1176 | 'AmazonAuthorizationId': id 1177 | } 1178 | response = self._operation(params=parameters) 1179 | answer.append(response) 1180 | queryID = json.loads(response.to_json()) 1181 | chargeID = queryID['GetAuthorizationDetailsResponse']\ 1182 | ['GetAuthorizationDetailsResult']['AuthorizationDetails']['IdList'] 1183 | 1184 | if chargeID is not None: 1185 | chargeID = chargeID['member'] 1186 | parameters = { 1187 | 'Action': 'GetCaptureDetails', 1188 | 'AmazonCaptureId': chargeID 1189 | } 1190 | response = self._operation(params=parameters) 1191 | queryID = json.loads(response.to_json()) 1192 | refundID = queryID['GetCaptureDetailsResponse']\ 1193 | ['GetCaptureDetailsResult']['CaptureDetails']['IdList'] 1194 | answer.append(response) 1195 | 1196 | if refundID is not None: 1197 | if type(refundID['member']) is not list: 1198 | refundID = [refundID['member']] 1199 | else: 1200 | refundID = refundID['member'] 1201 | 1202 | for id in refundID: 1203 | parameters = { 1204 | 'Action': 'GetRefundDetails', 1205 | 'AmazonRefundId': id 1206 | } 1207 | response = self._operation(params=parameters) 1208 | answer.append(response) 1209 | 1210 | return answer 1211 | 1212 | def authorize( 1213 | self, 1214 | amazon_order_reference_id, 1215 | authorization_reference_id, 1216 | authorization_amount, 1217 | seller_authorization_note=None, 1218 | transaction_timeout=1440, 1219 | capture_now=False, 1220 | soft_descriptor=None, 1221 | merchant_id=None, 1222 | mws_auth_token=None): 1223 | # pylint: disable=too-many-arguments 1224 | """Reserves a specified amount against the payment method(s) stored in 1225 | the order reference. 1226 | 1227 | Parameters 1228 | ---------- 1229 | amazon_order_reference_id : string, required 1230 | The order reference identifier. 1231 | 1232 | authorization_reference_id : string, required 1233 | The identifier for this authorization transaction that you specify. 1234 | This identifier must be unique for all your transactions 1235 | (authorization, capture, refund, etc.). 1236 | 1237 | authorization_amount : string, required 1238 | Represents the amount to be authorized. 1239 | 1240 | seller_authorization_note : string, optional 1241 | A description for the transaction that is displayed in emails to 1242 | the buyer. Maximum: 225 characters. 1243 | 1244 | transaction_timeout : unsigned integer, optional 1245 | The number of minutes after which the authorization will 1246 | automatically be closed and you will not be able to capture funds 1247 | against the authorization. 1248 | 1249 | Note: In asynchronous mode, the Authorize operation always returns 1250 | the State as Pending. The authorization remains in this state until 1251 | it is processed by Amazon. The processing time varies and can be a 1252 | minute or more. After processing is complete, Amazon will notify 1253 | you of the final processing status. For more information, see 1254 | Synchronizing your systems with Amazon Pay in the Amazon Pay 1255 | Integration Guide. Default: 1440 1256 | 1257 | capture_now : boolean, optional 1258 | Indicates whether to directly capture a specified amount against an 1259 | order reference (without needing to call Capture and without waiting 1260 | until the order ships). The captured amount is disbursed to your 1261 | account in the next disbursement cycle. 1262 | 1263 | Note: The Amazon Pay policy states that you charge your buyer 1264 | when you fulfill the items in the order. You should not collect 1265 | funds prior to fulfilling the order. Default: False 1266 | 1267 | soft_descriptor : string, optional 1268 | The description to be shown on the buyer’s payment instrument 1269 | statement if CaptureNow is set to true. The soft descriptor sent to 1270 | the payment processor is: “AMZ* ”. 1271 | 1272 | merchant_id : string, required 1273 | Your merchant ID. If you are a marketplace enter the seller's merchant 1274 | ID. 1275 | 1276 | mws_auth_token: string, optional 1277 | Your marketplace web service auth token. Default: None 1278 | """ 1279 | parameters = { 1280 | 'Action': 'Authorize', 1281 | 'AmazonOrderReferenceId': amazon_order_reference_id, 1282 | 'TransactionTimeout': transaction_timeout, 1283 | 'AuthorizationReferenceId': authorization_reference_id, 1284 | 'AuthorizationAmount.Amount': authorization_amount, 1285 | 'AuthorizationAmount.CurrencyCode': self.currency_code} 1286 | optionals = { 1287 | 'SellerAuthorizationNote': seller_authorization_note, 1288 | 'CaptureNow': str(capture_now).lower(), 1289 | 'SoftDescriptor': soft_descriptor, 1290 | 'SellerId': merchant_id, 1291 | 'MWSAuthToken': mws_auth_token} 1292 | return self._operation(params=parameters, options=optionals) 1293 | 1294 | def get_authorization_details( 1295 | self, 1296 | amazon_authorization_id, 1297 | merchant_id=None, 1298 | mws_auth_token=None): 1299 | """Returns the status of a particular authorization and the total amount 1300 | captured on the authorization. 1301 | 1302 | Parameters 1303 | ---------- 1304 | amazon_authorization_id : string, required 1305 | The authorization identifier that was generated by Amazon in the 1306 | earlier call to Authorize. 1307 | 1308 | merchant_id : string, required 1309 | Your merchant ID. If you are a marketplace enter the seller's merchant 1310 | ID. 1311 | 1312 | mws_auth_token: string, optional 1313 | Your marketplace web service auth token. Default: None 1314 | """ 1315 | parameters = { 1316 | 'Action': 'GetAuthorizationDetails', 1317 | 'AmazonAuthorizationId': amazon_authorization_id} 1318 | optionals = { 1319 | 'SellerId': merchant_id, 1320 | 'MWSAuthToken': mws_auth_token} 1321 | return self._operation(params=parameters, options=optionals) 1322 | 1323 | def capture( 1324 | self, 1325 | amazon_authorization_id, 1326 | capture_reference_id, 1327 | capture_amount, 1328 | seller_capture_note=None, 1329 | soft_descriptor=None, 1330 | merchant_id=None, 1331 | mws_auth_token=None): 1332 | # pylint: disable=too-many-arguments 1333 | """Captures funds from an authorized payment instrument. 1334 | 1335 | Parameters 1336 | ---------- 1337 | amazon_authorization_id : string, required 1338 | The authorization identifier that was generated by Amazon in the 1339 | earlier call to Authorize or AuthorizeOnBillingAgreement. 1340 | 1341 | capture_reference_id : string, required 1342 | The identifier for this capture transaction that you specify. This 1343 | identifier must be unique for all your transactions 1344 | (authorization, capture, refund, etc.). 1345 | 1346 | capture_amount : string, required 1347 | The amount to capture in this transaction. This amount cannot exceed 1348 | the original amount that was authorized less any previously captured 1349 | amount on this authorization. 1350 | 1351 | seller_capture_note : string, optional 1352 | A description for the capture transaction that is displayed in 1353 | emails to the buyer. Maximum: 255 characters, Default: None 1354 | 1355 | soft_descriptor : string, optional 1356 | The description to be shown on the buyer’s payment instrument 1357 | statement. The soft descriptor sent to the payment processor is: 1358 | “AMZ* ”. 1359 | 1360 | merchant_id : string, required 1361 | Your merchant ID. If you are a marketplace enter the seller's merchant 1362 | ID. 1363 | 1364 | mws_auth_token: string, optional 1365 | Your marketplace web service auth token. Default: None 1366 | """ 1367 | parameters = { 1368 | 'Action': 'Capture', 1369 | 'AmazonAuthorizationId': amazon_authorization_id, 1370 | 'CaptureReferenceId': capture_reference_id, 1371 | 'CaptureAmount.Amount': capture_amount, 1372 | 'CaptureAmount.CurrencyCode': self.currency_code} 1373 | optionals = { 1374 | 'SellerCaptureNote': seller_capture_note, 1375 | 'SoftDescriptor': soft_descriptor, 1376 | 'SellerId': merchant_id, 1377 | 'MWSAuthToken': mws_auth_token} 1378 | return self._operation(params=parameters, options=optionals) 1379 | 1380 | def get_capture_details( 1381 | self, 1382 | amazon_capture_id, 1383 | merchant_id=None, 1384 | mws_auth_token=None): 1385 | """Returns the status of a particular capture and the total amount 1386 | refunded on the capture. 1387 | 1388 | Parameters 1389 | ---------- 1390 | amazon_capture_id : string, required 1391 | The capture identifier that was generated by Amazon on the earlier 1392 | call to Capture. 1393 | 1394 | merchant_id : string, required 1395 | Your merchant ID. If you are a marketplace enter the seller's merchant 1396 | ID. 1397 | 1398 | mws_auth_token: string, optional 1399 | Your marketplace web service auth token. Default: None 1400 | """ 1401 | parameters = { 1402 | 'Action': 'GetCaptureDetails', 1403 | 'AmazonCaptureId': amazon_capture_id} 1404 | optionals = { 1405 | 'SellerId': merchant_id, 1406 | 'MWSAuthToken': mws_auth_token} 1407 | return self._operation(params=parameters, options=optionals) 1408 | 1409 | def close_authorization( 1410 | self, 1411 | amazon_authorization_id, 1412 | closure_reason=None, 1413 | merchant_id=None, 1414 | mws_auth_token=None): 1415 | """Closes an authorization. 1416 | 1417 | Parameters 1418 | ---------- 1419 | amazon_authorization_id : string, required 1420 | The authorization identifier that was generated by Amazon in the 1421 | earlier call to Authorize. 1422 | 1423 | closure_reason : string, optional 1424 | A description for the closure that is displayed in emails to the 1425 | buyer. 1426 | 1427 | merchant_id : string, required 1428 | Your merchant ID. If you are a marketplace enter the seller's merchant 1429 | ID. 1430 | 1431 | mws_auth_token: string, optional 1432 | Your marketplace web service auth token. Default: None 1433 | """ 1434 | parameters = { 1435 | 'Action': 'CloseAuthorization', 1436 | 'AmazonAuthorizationId': amazon_authorization_id} 1437 | optionals = { 1438 | 'ClosureReason': closure_reason, 1439 | 'SellerId': merchant_id, 1440 | 'MWSAuthToken': mws_auth_token} 1441 | return self._operation(params=parameters, options=optionals) 1442 | 1443 | def refund( 1444 | self, 1445 | amazon_capture_id, 1446 | refund_reference_id, 1447 | refund_amount, 1448 | seller_refund_note=None, 1449 | soft_descriptor=None, 1450 | merchant_id=None, 1451 | mws_auth_token=None): 1452 | # pylint: disable=too-many-arguments 1453 | """Refunds a previously captured amount. 1454 | 1455 | Parameters 1456 | ---------- 1457 | amazon_capture_id : string, required 1458 | The capture identifier that was generated by Amazon in the earlier 1459 | call to Capture. 1460 | 1461 | refund_reference_id : string, required 1462 | The identifier for this refund transaction that you specify. This 1463 | identifier must be unique for all your transactions 1464 | (authorization, capture, refund, etc.). 1465 | 1466 | refund_amount : string, required 1467 | The amount to refund. This amount cannot exceed: 1468 | In the US: the lesser of 15% or $75 above the captured amount 1469 | less the amount already refunded on the capture. 1470 | In the UK: the lesser of 15% or £75 above the captured amount 1471 | for the Capture object. 1472 | In Germany: the lesser of 15% or €75 above the captured amount 1473 | for the Capture object. 1474 | 1475 | seller_refund_note : string, optional 1476 | A description for the refund that is displayed in emails to the 1477 | buyer. Maximum: 255 characters, Default: None 1478 | 1479 | soft_descriptor : string, optional 1480 | The description to be shown on the buyer’s payment instrument 1481 | statement. The soft descriptor sent to the payment processor is: 1482 | “AMZ* ”. 1483 | 1484 | merchant_id : string, required 1485 | Your merchant ID. If you are a marketplace enter the seller's merchant 1486 | ID. 1487 | 1488 | mws_auth_token: string, optional 1489 | Your marketplace web service auth token. Default: None 1490 | """ 1491 | parameters = { 1492 | 'Action': 'Refund', 1493 | 'AmazonCaptureId': amazon_capture_id, 1494 | 'RefundReferenceId': refund_reference_id, 1495 | 'RefundAmount.Amount': refund_amount, 1496 | 'RefundAmount.CurrencyCode': self.currency_code} 1497 | optionals = { 1498 | 'SellerRefundNote': seller_refund_note, 1499 | 'SoftDescriptor': soft_descriptor, 1500 | 'SellerId': merchant_id, 1501 | 'MWSAuthToken': mws_auth_token} 1502 | return self._operation(params=parameters, options=optionals) 1503 | 1504 | def get_refund_details( 1505 | self, 1506 | amazon_refund_id, 1507 | merchant_id=None, 1508 | mws_auth_token=None): 1509 | """Returns the status of a particular refund. 1510 | 1511 | Parameters 1512 | ---------- 1513 | amazon_refund_id : string, required 1514 | The Amazon-generated identifier for this refund transaction. 1515 | 1516 | merchant_id : string, required 1517 | Your merchant ID. If you are a marketplace enter the seller's merchant 1518 | ID. 1519 | 1520 | mws_auth_token: string, optional 1521 | Your marketplace web service auth token. Default: None 1522 | """ 1523 | parameters = { 1524 | 'Action': 'GetRefundDetails', 1525 | 'AmazonRefundId': amazon_refund_id} 1526 | optionals = { 1527 | 'SellerId': merchant_id, 1528 | 'MWSAuthToken': mws_auth_token} 1529 | return self._operation(params=parameters, options=optionals) 1530 | 1531 | def get_service_status(self): 1532 | """Returns the operational status of the Off-Amazon Payments API section. 1533 | """ 1534 | parameters = { 1535 | 'Action': 'GetServiceStatus'} 1536 | 1537 | return self._operation(params=parameters) 1538 | 1539 | def charge( 1540 | self, 1541 | amazon_reference_id, 1542 | charge_amount, 1543 | authorize_reference_id, 1544 | charge_note, 1545 | charge_order_id=None, 1546 | store_name=None, 1547 | custom_information=None, 1548 | platform_id=None, 1549 | merchant_id=None, 1550 | mws_auth_token=None, 1551 | soft_descriptor=None): 1552 | """Combine the set, confirm, authorize, and capture calls into one. 1553 | 1554 | Parameters 1555 | ---------- 1556 | amazon_reference_id : string, required 1557 | The order reference or billing agreement identifier. 1558 | 1559 | charge_amount : string, required 1560 | The amount to capture in this transaction. 1561 | 1562 | authorize_reference_id : string, required 1563 | The seller-specified identifier of this charge. This parameter sets 1564 | both authorization_reference_id and capture_reference_id. 1565 | 1566 | charge_order_id : string, optional 1567 | The seller-specified identifier of this order. This is displayed to 1568 | the buyer in the emails they receive from Amazon and also in their 1569 | transaction history on the Amazon Pay website. 1570 | 1571 | store_name : string, optional 1572 | The identifier of the store from which the order was placed. This 1573 | overrides the default value in Seller Central under Settings > 1574 | Account Settings. It is displayed to the buyer in the email they 1575 | receive from Amazon and also in their transaction history on the 1576 | Amazon Pay website. 1577 | 1578 | custom_information : string, optional 1579 | Any additional information you wish to include with this billing 1580 | agreement. 1581 | 1582 | charge_note : string, optional 1583 | A description for the capture transaction that is displayed in 1584 | emails to the buyer. 1585 | 1586 | platform_id 1587 | Represents the SellerId of the Solution Provider that developed the 1588 | platform. This value should only be provided by Solution Providers. 1589 | It should not be provided by sellers creating their own custom 1590 | integration. 1591 | 1592 | merchant_id : string, required 1593 | Your merchant ID. If you are a marketplace enter the seller's merchant 1594 | ID. 1595 | 1596 | mws_auth_token: string, optional 1597 | Your marketplace web service auth token. Default: None 1598 | 1599 | soft_descriptor : string, optional 1600 | The description to be shown on the buyer’s payment instrument 1601 | statement if CaptureNow is set to true. The soft descriptor sent to 1602 | the payment processor is: “AMZ* ”. 1603 | """ 1604 | 1605 | if self.is_order_reference_id(amazon_reference_id): 1606 | # set 1607 | ret = self.set_order_reference_details( 1608 | amazon_order_reference_id=amazon_reference_id, 1609 | order_total=charge_amount, 1610 | platform_id=platform_id, 1611 | seller_note=charge_note, 1612 | seller_order_id=charge_order_id, 1613 | store_name=store_name, 1614 | custom_information=custom_information, 1615 | merchant_id=merchant_id, 1616 | mws_auth_token=mws_auth_token) 1617 | if ret.success: 1618 | # confirm 1619 | ret = self.confirm_order_reference( 1620 | amazon_order_reference_id=amazon_reference_id, 1621 | merchant_id=merchant_id, 1622 | mws_auth_token=mws_auth_token) 1623 | if ret.success: 1624 | # auth 1625 | ret = self.authorize( 1626 | amazon_order_reference_id=amazon_reference_id, 1627 | authorization_reference_id=authorize_reference_id, 1628 | authorization_amount=charge_amount, 1629 | seller_authorization_note=charge_note, 1630 | transaction_timeout=0, 1631 | capture_now=True, 1632 | soft_descriptor=soft_descriptor, 1633 | merchant_id=merchant_id, 1634 | mws_auth_token=mws_auth_token) 1635 | return ret 1636 | else: 1637 | return ret 1638 | else: 1639 | return ret 1640 | 1641 | if self.is_billing_agreement_id(amazon_reference_id): 1642 | """Since this is a billing agreement we need to see if details have 1643 | already been set. If so, we just need to authorize. 1644 | """ 1645 | ret = self.get_billing_agreement_details( 1646 | amazon_billing_agreement_id=amazon_reference_id, 1647 | address_consent_token=None, 1648 | merchant_id=merchant_id, 1649 | mws_auth_token=mws_auth_token) 1650 | if ret.to_dict().get('GetBillingAgreementDetailsResponse').get( 1651 | 'GetBillingAgreementDetailsResult').get( 1652 | 'BillingAgreementDetails').get( 1653 | 'BillingAgreementStatus').get('State') == 'Draft': 1654 | # set 1655 | ret = self.set_billing_agreement_details( 1656 | amazon_billing_agreement_id=amazon_reference_id, 1657 | platform_id=platform_id, 1658 | seller_note=charge_note, 1659 | seller_billing_agreement_id=charge_order_id, 1660 | store_name=store_name, 1661 | custom_information=custom_information, 1662 | merchant_id=merchant_id, 1663 | mws_auth_token=mws_auth_token) 1664 | if ret.success: 1665 | # confirm 1666 | ret = self.confirm_billing_agreement( 1667 | amazon_billing_agreement_id=amazon_reference_id, 1668 | merchant_id=merchant_id, 1669 | mws_auth_token=mws_auth_token) 1670 | if not ret.success: 1671 | return ret 1672 | else: 1673 | return ret 1674 | # auth 1675 | ret = self.authorize_on_billing_agreement( 1676 | amazon_billing_agreement_id=amazon_reference_id, 1677 | authorization_reference_id=authorize_reference_id, 1678 | authorization_amount=charge_amount, 1679 | seller_authorization_note=charge_note, 1680 | transaction_timeout=0, 1681 | capture_now=True, 1682 | soft_descriptor=soft_descriptor, 1683 | seller_note=charge_note, 1684 | platform_id=platform_id, 1685 | seller_order_id=charge_order_id, 1686 | store_name=store_name, 1687 | custom_information=custom_information, 1688 | inherit_shipping_address=True, 1689 | merchant_id=merchant_id, 1690 | mws_auth_token=mws_auth_token) 1691 | return ret 1692 | 1693 | def is_order_reference_id(self, amazon_reference_id): 1694 | """Checks if Id is order reference. P or S at the beginning indicate a 1695 | order reference ID. 1696 | """ 1697 | return re.search('^(P|S)', amazon_reference_id) 1698 | 1699 | def is_billing_agreement_id(self, amazon_reference_id): 1700 | """Checks if Id is billing agreement. B or C at the beginning indicate a 1701 | billing agreement ID 1702 | """ 1703 | return re.search('^(B|C)', amazon_reference_id) 1704 | 1705 | def _operation(self, params, options=None): 1706 | """Parses required and optional parameters and passes to the Request 1707 | object. 1708 | """ 1709 | 1710 | if options is not None: 1711 | for opt in options.keys(): 1712 | if options[opt] is not None: 1713 | params[opt] = options[opt] 1714 | 1715 | request = PaymentRequest( 1716 | params=params, 1717 | config={'mws_access_key': self.mws_access_key, 1718 | 'mws_secret_key': self.mws_secret_key, 1719 | 'api_version': self._api_version, 1720 | 'merchant_id': self.merchant_id, 1721 | 'mws_endpoint': self._mws_endpoint, 1722 | 'headers': self._headers, 1723 | 'handle_throttle': self.handle_throttle}) 1724 | 1725 | request.send_post() 1726 | return request.response 1727 | 1728 | def _enumerate( 1729 | self, 1730 | category, 1731 | filter_types, 1732 | optionals): 1733 | 1734 | def enumerate_param(param, values): 1735 | """ 1736 | Builds a dictionary of an enumerated parameter from the filter list. 1737 | This is currently used for two API calls in the client. Specifically, 1738 | set_order_attributes & list_order_reference 1739 | Example: (For set_order_attributes) 1740 | enumerate_param( 1741 | 'OrderAttributes.SellerOrderAttributes.OrderItemCategories.OrderItemCategory.', 1742 | (["Antiques", "Outdoor"]) 1743 | returns 1744 | { 1745 | OrderAttributes.SellerOrderAttributes.OrderItemCategories.OrderItemCategory.1: 1746 | Antiques, 1747 | OrderAttributes.SellerOrderAttributes.OrderItemCategories.OrderItemCategory.2: 1748 | Outdoor 1749 | } 1750 | """ 1751 | params = {} 1752 | if values is not None: 1753 | if not param.endswith('.'): 1754 | param = "%s." % param 1755 | for num, value in enumerate(values): 1756 | params['%s%d' % (param, (num + 1))] = value 1757 | return params 1758 | 1759 | """This will apply your filters from the list filter parameter""" 1760 | if isinstance(filter_types, list): 1761 | optionals.update(enumerate_param(category, filter_types)) 1762 | else: 1763 | if ',' in filter_types: 1764 | filter_types = filter_types.replace(' ','') 1765 | filter_types = filter_types.split(',') 1766 | optionals.update(enumerate_param(category, filter_types)) 1767 | else: 1768 | raise Exception("Invalid format for this request.") 1769 | -------------------------------------------------------------------------------- /amazon_pay/ipn_handler.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import base64 4 | import logging 5 | from urllib import request 6 | from OpenSSL import crypto 7 | from urllib.error import HTTPError 8 | from urllib.parse import urlparse 9 | from amazon_pay.payment_response import PaymentResponse 10 | 11 | 12 | class IpnHandler(): 13 | 14 | logger = logging.getLogger('__amazon_pay_sdk__') 15 | logger.addHandler(logging.NullHandler()) 16 | 17 | """Instant Payment Notifications (IPN) can be used to monitor the state 18 | transition of payment objects. 19 | 20 | Amazon sends you a notification when the state of any of the payment 21 | objects or the Order Reference object changes. These notifications are 22 | always sent without any action required on your part and can be used to 23 | update any internal tracking or fulfillment systems you might be using to 24 | manage the order. 25 | 26 | After you receive an IPN, a best practice is to perform a get operation for 27 | the respective object for which you have received the notification. You can 28 | use the response of the get operation to update your systems. 29 | 30 | With each notification you receive, you should configure your endpoint to 31 | send Amazon a '200 OK' response immediately after receipt. If you do not 32 | send this response or if your server is down when the SNS message is sent, 33 | Amazon SNS will perform retries every hour for 14 days. 34 | 35 | Amazon Simple Notification Service (Amazon SNS) is a fast, flexible, fully 36 | managed push notification service. 37 | """ 38 | 39 | def __init__(self, body, headers): 40 | """ 41 | Parameters 42 | ---------- 43 | body : string 44 | The body of the SNS message. 45 | 46 | headers : dictionary 47 | The headers of the SNS message. 48 | 49 | 50 | Properties 51 | ---------- 52 | error : string 53 | Holds the latest error, if any. 54 | """ 55 | 56 | self.error = None 57 | 58 | self._root = None 59 | self._ns = None 60 | self._response_type = None 61 | self._headers = headers 62 | self._payload = json.loads(body.decode('utf-8')) 63 | self._pem = None 64 | 65 | self._message_encoded = self._payload['Message'] 66 | self._message = json.loads(self._payload['Message']) 67 | self._message_id = self._payload['MessageId'] 68 | self._topic_arn = self._payload['TopicArn'] 69 | self._notification_data = self._message['NotificationData'] 70 | self._signing_cert_url = self._payload['SigningCertURL'] 71 | self._signature = self._payload['Signature'] 72 | self._timestamp = self._payload['Timestamp'] 73 | self._type = self._payload['Type'] 74 | self._xml = self._notification_data.replace( 75 | '\n', 76 | '') 77 | self.logger.debug('IPN Response: %s', 78 | self._sanitize_response_data(self._xml)) 79 | 80 | def authenticate(self): 81 | """Attempt to validate a SNS message received from Amazon 82 | From release version 2.7.9/3.4.3 on, Python by default attempts to 83 | perform certificate validation. Returns True on success. 84 | 85 | https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection 86 | 87 | Changed in version 3.4.3: This class now performs all the necessary 88 | certificate and hostname checks by default. 89 | """ 90 | self._validate_header() 91 | self._validate_cert_url() 92 | self._get_cert() 93 | self._validate_signature() 94 | 95 | return True 96 | 97 | def _validate_header(self): 98 | """Compare the header topic_arn to the body topic_arn """ 99 | if 'X-Amz-Sns-Topic-Arn' in self._headers: 100 | if self._topic_arn != self._headers.get( 101 | 'X-Amz-Sns-Topic-Arn'): 102 | self.error = 'Invalid TopicArn.' 103 | raise ValueError('Invalid TopicArn') 104 | else: 105 | self.error = 'Invalid TopicArn' 106 | raise ValueError('Invalid TopicArn') 107 | 108 | return True 109 | 110 | def _validate_cert_url(self): 111 | """Checks to see if the certificate URL points to a AWS endpoint and 112 | validates the signature using the .pem from the certificate URL. 113 | """ 114 | try: 115 | url_object = urlparse(self._signing_cert_url) 116 | except: 117 | raise ValueError('Invalid signing cert URL.') 118 | 119 | if url_object.scheme != 'https': 120 | raise ValueError('Invalid certificate.') 121 | 122 | if not re.search( 123 | '^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$', url_object.netloc): 124 | raise ValueError('Invalid certificate.') 125 | 126 | if not re.search('^\/(.*)\.pem$', url_object.path): 127 | raise ValueError('Invalid certificate.') 128 | 129 | return True 130 | 131 | def _get_cert(self): 132 | try: 133 | cert_req = request.urlopen( 134 | url=request.Request(self._signing_cert_url)) 135 | except HTTPError as ex: 136 | self.error = 'Error retrieving certificate.' 137 | raise ValueError( 138 | 'Error retrieving certificate. {}'.format( 139 | ex.reason)) 140 | 141 | self._pem = str(cert_req.read(), encoding='utf-8') 142 | return True 143 | 144 | def _validate_signature(self): 145 | """Generate signing string and validate signature""" 146 | signing_string = '{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n'.format( 147 | 'Message', 148 | self._message_encoded, 149 | 'MessageId', 150 | self._message_id, 151 | 'Timestamp', 152 | self._timestamp, 153 | 'TopicArn', 154 | self._topic_arn, 155 | 'Type', 156 | self._type) 157 | 158 | crt = crypto.load_certificate(crypto.FILETYPE_PEM, self._pem) 159 | signature = base64.b64decode(self._signature) 160 | 161 | try: 162 | crypto.verify( 163 | crt, 164 | signature, 165 | signing_string.encode('utf-8'), 166 | 'sha1') 167 | except: 168 | self.error = 'Invalid signature.' 169 | raise ValueError('Invalid signature.') 170 | 171 | return True 172 | 173 | def to_json(self): 174 | """Retuns notification message as JSON""" 175 | return PaymentResponse(self._xml).to_json() 176 | 177 | def to_xml(self): 178 | """Retuns notification message as XML""" 179 | return PaymentResponse(self._xml).to_xml() 180 | 181 | def _sanitize_response_data(self, text): 182 | editText = text 183 | patterns = [] 184 | patterns.append(r'(?s)().*(<\/SellerNote>)') 185 | patterns.append(r'(?s)().*(<\/AuthorizationBillingAddress>)') 186 | patterns.append(r'(?s)().*(<\/SellerAuthorizationNote>)') 187 | patterns.append(r'(?s)().*(<\/SellerCaptureNote>)') 188 | patterns.append(r'(?s)().*(<\/SellerRefundNote>)') 189 | replacement = r'\1 REMOVED \2' 190 | 191 | for pattern in patterns: 192 | editText = re.sub(pattern, replacement, editText) 193 | return editText -------------------------------------------------------------------------------- /amazon_pay/login_with_amazon.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import amazon_pay.lwa_region as lwa_region 3 | 4 | 5 | class LoginWithAmazon: 6 | 7 | """Login with Amazon class to wrap the get login profile method""" 8 | 9 | def __init__(self, client_id, region, sandbox=False): 10 | """ 11 | Parameters 12 | ---------- 13 | client_id: string, required 14 | The client Id of your Login with Amazon application. 15 | 16 | region : string, required 17 | The region in which you are conducting business. 18 | 19 | sandbox : string, optional 20 | Toggle sandbox mode. Default: False 21 | """ 22 | self._client_id = client_id 23 | 24 | try: 25 | self.region = lwa_region.regions[region] 26 | except KeyError: 27 | raise KeyError('Invalid region code ({}).'.format(region)) 28 | 29 | self._sandbox_str = 'api.sandbox' if sandbox else 'api' 30 | self._endpoint = 'https://{}.{}'.format( 31 | self._sandbox_str, 32 | self.region) 33 | 34 | def get_login_profile(self, access_token): 35 | """Get profile associated with LWA user.""" 36 | token_info = requests.get( 37 | url='{}/auth/o2/tokeninfo'.format(self._endpoint), 38 | headers={'x-amz-access-token': access_token}, 39 | params=None, 40 | verify=True) 41 | 42 | token_decoded = token_info.json() 43 | 44 | if 'error' in token_decoded: 45 | raise ValueError(token_decoded['error']) 46 | 47 | if 'aud' not in token_decoded: 48 | raise ValueError('Client Id not present.') 49 | 50 | if token_decoded['aud'] != self._client_id: 51 | raise ValueError('Invalid client Id.') 52 | 53 | profile = requests.get( 54 | url='{}/user/profile'.format(self._endpoint), 55 | headers={'x-amz-access-token': access_token}, 56 | params=None, 57 | verify=True) 58 | 59 | return profile.json() 60 | -------------------------------------------------------------------------------- /amazon_pay/lwa_region.py: -------------------------------------------------------------------------------- 1 | regions = {'jp': 'amazon.co.jp', 2 | 'uk': 'amazon.co.uk', 3 | 'de': 'amazon.de', 4 | 'eu': 'amazon.co.uk', 5 | 'us': 'amazon.com', 6 | 'na': 'amazon.com'} 7 | -------------------------------------------------------------------------------- /amazon_pay/payment_request.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import time 3 | import base64 4 | import hashlib 5 | import datetime 6 | import requests 7 | import logging 8 | import re 9 | from urllib import parse 10 | from collections import OrderedDict 11 | from amazon_pay.payment_response import PaymentResponse, PaymentErrorResponse 12 | 13 | 14 | class PaymentRequest: 15 | 16 | logger = logging.getLogger('__amazon_pay_sdk__') 17 | logger.addHandler(logging.NullHandler()) 18 | 19 | """Parses request, generates signature and parameter string, posts 20 | request to Amazon, and returns result. 21 | """ 22 | 23 | def __init__(self, params, config): 24 | """ 25 | Parameters 26 | ---------- 27 | params : dictionary, required 28 | Dictionary containing keys passed from the _operation method. Each 29 | API call fills this dictionary so you shouldn't need to modify this. 30 | The keys will vary depending on the API call. 31 | 32 | config : dictionary, required 33 | Dictionary containing configuration information. 34 | Required keys: mws_access_key, mws_secret_key, api_version, 35 | merchant_id, mws_endpoint, headers, handle_throttle 36 | """ 37 | self.success = False 38 | self.response = None 39 | self.mws_access_key = config['mws_access_key'] 40 | self.mws_secret_key = config['mws_secret_key'] 41 | self.merchant_id = config['merchant_id'] 42 | self.handle_throttle = config['handle_throttle'] 43 | 44 | self._retry_time = 0 45 | self._params = params 46 | self._api_version = config['api_version'] 47 | self._mws_endpoint = config['mws_endpoint'] 48 | self._headers = config['headers'] 49 | self._should_throttle = False 50 | 51 | def _sign(self, string_to_sign): 52 | """Generate the signature for the request""" 53 | signature = hmac.new( 54 | self.mws_secret_key.encode('utf_8'), 55 | msg=string_to_sign.encode('utf_8'), 56 | digestmod=hashlib.sha256).digest() 57 | signature = base64.b64encode(signature).decode() 58 | self.logger.debug('string to generate signature: %s', string_to_sign) 59 | self.logger.debug('signature: %s', signature) 60 | return signature 61 | 62 | def _querystring(self, params): 63 | """Generate the querystring to be posted to the MWS endpoint 64 | 65 | Required parameters for every API call. 66 | 67 | AWSAccessKeyId: Your Amazon MWS account is identified by your access key, 68 | which Amazon MWS uses to look up your secret key. 69 | 70 | SignatureMethod: The HMAC hash algorithm you are using to calculate your 71 | signature. Both HmacSHA256 and HmacSHA1 are supported hash algorithms, 72 | but Amazon recommends using HmacSHA256. 73 | 74 | SignatureVersion: Which signature version is being used. This is Amazon 75 | MWS-specific information that tells Amazon MWS the algorithm you used 76 | to form the string that is the basis of the signature. For Amazon MWS, 77 | this value is currently SignatureVersion=2. 78 | 79 | Version: The version of the API section being called. 80 | 81 | Timestamp: Each request must contain the timestamp of the request. The 82 | Timestamp attribute must contain the client's machine time in 83 | ISO8601 format; requests with a timestamp significantly different 84 | (15 minutes) than the receiving machine's clock will be rejected to 85 | help prevent replay attacks. 86 | 87 | SellerId: Your seller or merchant identifier. 88 | """ 89 | parameters = {'AWSAccessKeyId': self.mws_access_key, 90 | 'SignatureMethod': 'HmacSHA256', 91 | 'SignatureVersion': '2', 92 | 'Version': self._api_version, 93 | 'Timestamp': datetime.datetime.utcnow().replace( 94 | microsecond=0).isoformat(sep='T') + 'Z'} 95 | 96 | if 'SellerId' not in params: 97 | parameters['SellerId'] = self.merchant_id 98 | 99 | parameters.update({k: v for (k, v) in params.items()}) 100 | parse_results = parse.urlparse(self._mws_endpoint) 101 | 102 | string_to_sign = "POST\n{}\n{}\n{}".format( 103 | parse_results[1], 104 | parse_results[2], 105 | parse.urlencode( 106 | sorted(parameters.items())).replace( 107 | '+', '%20').replace('*', '%2A').replace('%7E', '~')) 108 | 109 | parameters['Signature'] = self._sign(string_to_sign) 110 | 111 | ordered_parameters = OrderedDict(sorted(parameters.items())) 112 | ordered_parameters.move_to_end('Signature') 113 | return parse.urlencode(ordered_parameters).encode(encoding='utf_8') 114 | 115 | def _request(self, retry_time): 116 | time.sleep(retry_time) 117 | data = self._querystring(self._params) 118 | 119 | self.logger.debug('Request Header: %s', 120 | self._sanitize_request_data(str(self._headers))) 121 | 122 | r = requests.post( 123 | url=self._mws_endpoint, 124 | data=data, 125 | headers=self._headers, 126 | verify=True) 127 | r.encoding = 'utf-8' 128 | self._status_code = r.status_code 129 | 130 | if self._status_code == 200: 131 | self.success = True 132 | self._should_throttle = False 133 | self.response = PaymentResponse(r.text) 134 | self.logger.debug('Response: %s', 135 | self._sanitize_response_data(r.text)) 136 | elif (self._status_code == 500 or self._status_code == 137 | 503) and self.handle_throttle: 138 | self._should_throttle = True 139 | self.response = PaymentErrorResponse( 140 | '{}'.format(r.status_code)) 141 | else: 142 | self.response = PaymentErrorResponse(r.text) 143 | self.logger.debug('Response: %s', 144 | self._sanitize_response_data(r.text)) 145 | 146 | def send_post(self): 147 | """Call request to send to MWS endpoint and handle throttle if set.""" 148 | if self.handle_throttle: 149 | for retry_time in (0, 1, 4, 10): 150 | self._request(retry_time) 151 | if self.success or not self._should_throttle: 152 | break 153 | else: 154 | self._request(0) 155 | 156 | def _sanitize_request_data(self, text): 157 | editText = text 158 | patterns = [] 159 | patterns.append(r'(?s)(SellerNote).*(&)') 160 | patterns.append(r'(?s)(SellerAuthorizationNote).*(&)') 161 | patterns.append(r'(?s)(SellerCaptureNote).*(&)') 162 | patterns.append(r'(?s)(SellerRefundNote).*(&)') 163 | replacement = r'\1 REMOVED \2' 164 | 165 | for pattern in patterns: 166 | editText = re.sub(pattern, replacement, editText) 167 | return editText 168 | 169 | def _sanitize_response_data(self, text): 170 | editText = text 171 | patterns = [] 172 | patterns.append(r'(?s)().*()') 173 | patterns.append(r'(?s)().*()') 174 | patterns.append(r'(?s)().*(<\/BillingAddress>)') 175 | patterns.append(r'(?s)().*(<\/SellerNote>)') 176 | patterns.append(r'(?s)().*(<\/AuthorizationBillingAddress>)') 177 | patterns.append(r'(?s)().*(<\/SellerAuthorizationNote>)') 178 | patterns.append(r'(?s)().*(<\/SellerCaptureNote>)') 179 | patterns.append(r'(?s)().*(<\/SellerRefundNote>)') 180 | replacement = r'\1 REMOVED \2' 181 | 182 | for pattern in patterns: 183 | editText = re.sub(pattern, replacement, editText) 184 | return editText -------------------------------------------------------------------------------- /amazon_pay/payment_response.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import xml.etree.ElementTree as et 4 | from collections import defaultdict 5 | 6 | 7 | class PaymentResponse: 8 | 9 | """Base class for all OffAmazonPayments responses 10 | 11 | Parameters 12 | ---------- 13 | xml : string 14 | XML response from Amazon. 15 | 16 | 17 | Properties 18 | ---------- 19 | root : string 20 | Root XML node. 21 | 22 | ns : string 23 | XML namespace. 24 | 25 | success : boolean 26 | Success or failure of the API call. 27 | 28 | response : Response 29 | Holds the response type for the API call. 30 | 31 | xml : string 32 | XML response from Amazon. 33 | """ 34 | 35 | def __init__(self, xml): 36 | """Initialize response""" 37 | self.success = True 38 | self._xml = xml 39 | try: 40 | self._root = et.fromstring(xml) 41 | self._ns = self._namespace(self._root) 42 | self._response_type = self._root.tag.replace(self._ns, '') 43 | except: 44 | raise ValueError('Invalid XML.') 45 | 46 | """There is a bug where 'eu' endpoint returns ErrorResponse XML node 47 | 'RequestID' with capital 'ID'. 'na' endpoint returns 'RequestId' 48 | """ 49 | try: 50 | if self._root.find('.//{}RequestId'.format(self._ns)) is None: 51 | self.request_id = self._root.find( 52 | './/{}RequestID'.format(self._ns)).text 53 | else: 54 | self.request_id = self._root.find( 55 | './/{}RequestId'.format(self._ns)).text 56 | except: 57 | self.request_id = None 58 | 59 | def _namespace(self, element): 60 | """Get XML namespace""" 61 | ns = re.match('\{.*\}', element.tag) 62 | return ns.group(0) if ns else '' 63 | 64 | def to_xml(self): 65 | """Return XML""" 66 | return self._xml 67 | 68 | def to_json(self): 69 | """Return JSON""" 70 | return json.dumps(self._etree_to_dict(self._root), ensure_ascii=False) 71 | 72 | def to_dict(self): 73 | """Return Dictionary""" 74 | return self._etree_to_dict(self._root) 75 | 76 | def _etree_to_dict(self, t): 77 | """Convert XML to Dictionary""" 78 | d = {t.tag.replace(self._ns, ''): {} if t.attrib else None} 79 | children = list(t) 80 | if children: 81 | dd = defaultdict(list) 82 | for dc in map(self._etree_to_dict, children): 83 | for k, v in dc.items(): 84 | dd[k].append(v) 85 | d = { 86 | t.tag.replace(self._ns, ''): { 87 | k: v[0] if len(v) == 1 else v for k, 88 | v in dd.items()}} 89 | if t.attrib: 90 | d[t.tag.replace(self._ns, '')].update(('@' + k, v) 91 | for k, v in t.attrib.items()) 92 | if t.text: 93 | text = t.text.strip() 94 | if children or t.attrib: 95 | if text: 96 | d[t.tag.replace(self._ns, '')]['#text'] = text 97 | else: 98 | d[t.tag.replace(self._ns, '')] = text 99 | return d 100 | 101 | 102 | class PaymentErrorResponse(PaymentResponse): 103 | 104 | """Error response subclass""" 105 | 106 | def __init__(self, xml): 107 | 108 | super(PaymentErrorResponse, self).__init__(xml) 109 | self.success = False 110 | -------------------------------------------------------------------------------- /amazon_pay/version.py: -------------------------------------------------------------------------------- 1 | versions = {'api_version': '2013-01-01', 2 | 'application_version': '2.7.1'} 3 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov amazon_pay_sdk_python test 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import amazon_pay.version as pwa_version 3 | 4 | setup( 5 | name='amazon_pay', 6 | packages=['amazon_pay'], 7 | version=pwa_version.versions['application_version'], 8 | description='Amazon Pay Python SDK', 9 | url='https://github.com/amzn/amazon-pay-sdk-python', 10 | download_url='https://github.com/amzn/amazon-pay-sdk-python/tarball/{}'.format( 11 | pwa_version.versions['application_version']), 12 | author='EPS-DSE', 13 | author_email='amazon-pay-sdk@amazon.com', 14 | license='Apache License version 2.0, January 2004', 15 | install_requires=['pyOpenSSL >= 0.11', 16 | 'requests >= 2.6.0'], 17 | keywords=['Amazon', 'Payments', 'Login', 'Python', 'API', 'SDK'], 18 | classifiers=[ 19 | 'Development Status :: 5 - Production/Stable', 20 | 'Intended Audience :: Developers', 21 | 'License :: OSI Approved :: Apache Software License', 22 | 'Natural Language :: English', 23 | 'Operating System :: OS Independent', 24 | 'Programming Language :: Python', 25 | 'Programming Language :: Python :: 3.2', 26 | 'Programming Language :: Python :: 3.4', 27 | 'Topic :: Software Development :: Libraries', 28 | 'Topic :: Software Development :: Libraries :: Python Modules'] 29 | ) 30 | -------------------------------------------------------------------------------- /test/log.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Physical 5 | REMOVED 6 | 7 | London 8 | Chelsea 9 | GB 10 | SW3 4ND 11 | 12 | 13 | London 14 | Chelsea 15 | GB 16 | SW3 4ND 17 | 18 | 19 | 2017-04-03T15:36:30.897Z 20 | 21 | This is a seller note 22 | This is a seller note 23 | This is a seller note 24 | This is a seller note 25 | 26 | USD 27 | 19.95 28 | 29 | Sandbox 30 | S01-3616844-6294179 31 | 2016-10-05T15:36:30.897Z 32 | false 33 | 34 | -------------------------------------------------------------------------------- /test/sanlog.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Physical 5 | REMOVED 6 | REMOVED 7 | REMOVED 8 | 9 | 2017-04-03T15:36:30.897Z 10 | 11 | REMOVED 12 | REMOVED 13 | REMOVED 14 | REMOVED 15 | 16 | USD 17 | 19.95 18 | 19 | Sandbox 20 | S01-3616844-6294179 21 | 2016-10-05T15:36:30.897Z 22 | false 23 | 24 | -------------------------------------------------------------------------------- /test/test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFFzCCA/+gAwIBAgIQfXvtWTP5lfZLpmyNHLk1TDANBgkqhkiG9w0BAQUFADCB 3 | tTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL 4 | ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug 5 | YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMm 6 | VmVyaVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwHhcNMTQwODIz 7 | MDAwMDAwWhcNMTUwODIyMjM1OTU5WjBrMQswCQYDVQQGEwJVUzETMBEGA1UECBMK 8 | V2FzaGluZ3RvbjEQMA4GA1UEBxQHU2VhdHRsZTEZMBcGA1UEChQQQW1hem9uLmNv 9 | bSwgSW5jLjEaMBgGA1UEAxQRc25zLmFtYXpvbmF3cy5jb20wggEiMA0GCSqGSIb3 10 | DQEBAQUAA4IBDwAwggEKAoIBAQDP/HD18qyBx4IgBvgVCkLTW18bULmoaaOQYtRY 11 | yVpPxIFkNSHxT4uYH9knKUqddKQd1TEXHh0bF50lBiHZpuascNc3+FP2YKbF2t/z 12 | a+zHfLipW01np85VDdIWedvB9TpnMdY9PQYTVx41+2fnei9WgjwXVM085WRSECh3 13 | aRdkvOjwTN/Tlrgy3hoebVN3V5kB67b139m3xAlZjoB8MPdk/tlsk+wgVxuAY/gz 14 | xGIZRJxlgEtsu2g8+rDkjS2tk3457Cz8aXRZSCGi+BB6yN2WhvWwPzSDJMDKxwXY 15 | I8fGw0xutF4WHN414KBUp/s/+E6Ib7GxLUCwFon1swKRz9NxAgMBAAGjggFqMIIB 16 | ZjAcBgNVHREEFTATghFzbnMuYW1hem9uYXdzLmNvbTAJBgNVHRMEAjAAMA4GA1Ud 17 | DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwZQYDVR0g 18 | BF4wXDBaBgpghkgBhvhFAQc2MEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1j 19 | Yi5jb20vY3BzMCUGCCsGAQUFBwICMBkaF2h0dHBzOi8vZC5zeW1jYi5jb20vcnBh 20 | MB8GA1UdIwQYMBaAFA1EXBZTRMGCfh0gqyX0AWPYvnmlMCsGA1UdHwQkMCIwIKAe 21 | oByGGmh0dHA6Ly9zZC5zeW1jYi5jb20vc2QuY3JsMFcGCCsGAQUFBwEBBEswSTAf 22 | BggrBgEFBQcwAYYTaHR0cDovL3NkLnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0 23 | cDovL3NkLnN5bWNiLmNvbS9zZC5jcnQwDQYJKoZIhvcNAQEFBQADggEBABm5RaeH 24 | sLtJftDeGghHAUkco8wkCshKQO1obhuMDJkJHAHbUrweP4Gw7WRhHNHo1jgA6Q61 25 | NXik2w6H7SDVngCVIqOLFsr1DQ7fB5oSevMwjLvlaLxWBAvgKPSsjCt+QF1aNQiv 26 | sfhOIgiJZTObBERsSs9FXWQ/vMkoisPKYJGn3KigzZj0GXSAB0do8Ejq8siBczgM 27 | 9o8NixqVvD7AOoE/QWXCtRDMRnQ5oIAc/Q9iUCJ8oAlbljDFkSqwgunpaG0iuHQZ 28 | 6Q4iHgmrgHM302H9fFxupyW46zwLH9gmbrUulmLRZT14bIzd0cZXqIl6nd8il4nq 29 | kqQRCW8P2LDot4s= 30 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /test/test_ap.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import platform 5 | import unittest 6 | import xml.etree.ElementTree as et 7 | import amazon_pay.ap_region as ap_region 8 | import amazon_pay.version as ap_version 9 | from unittest.mock import Mock, patch 10 | from amazon_pay.client import AmazonPayClient 11 | from amazon_pay.payment_request import PaymentRequest 12 | from amazon_pay.payment_response import PaymentResponse, PaymentErrorResponse 13 | from symbol import parameters 14 | 15 | class AmazonPayClientTest(unittest.TestCase): 16 | 17 | def setUp(self): 18 | self.maxDiff = None 19 | self.mws_access_key = 'mws_access_key' 20 | self.mws_secret_key = 'mws_secret_key' 21 | self.merchant_id = 'merchant_id' 22 | self.service_version = '2013-01-01' 23 | self.mws_endpoint = \ 24 | 'https://mws.amazonservices.com/OffAmazonPayments_Sandbox/{}'.format( 25 | self.service_version) 26 | 27 | self.client = AmazonPayClient( 28 | mws_access_key=self.mws_access_key, 29 | mws_secret_key=self.mws_secret_key, 30 | merchant_id=self.merchant_id, 31 | handle_throttle=False, 32 | sandbox=True, 33 | region='na', 34 | currency_code='USD') 35 | 36 | self.request = PaymentRequest( 37 | params={'test': 'test'}, 38 | config={'mws_access_key': self.mws_access_key, 39 | 'mws_secret_key': self.mws_secret_key, 40 | 'api_version': '2013-01-01', 41 | 'merchant_id': self.merchant_id, 42 | 'mws_endpoint': self.mws_endpoint, 43 | 'headers': {'test': 'test'}, 44 | 'handle_throttle': True}) 45 | 46 | self.response = PaymentResponse('الفلانية فلا') 47 | self.supplementary_data = '{"AirlineMetaData" : {"version": 1.0, "airlineCode": "PAX", "flightDate": "2018-03-24T20:29:19.22Z", "departureAirport": "CDG", "destinationAirport": "LUX", "bookedLastTime": -1, "classOfTravel": "F", "passengers": {"numberOfPassengers": 4, "numberOfChildren": 1, "numberOfInfants": 1 }}, "AccommodationMetaData": {"version": 1.0, "startDate": "2018-03-24T20:29:19.22Z", "endDate": "2018-03-24T20:29:19.22Z", "lengthOfStay": 5, "numberOfGuests": 4, "class": "Standard", "starRating": 5, "bookedLastTime": -1 }, "OrderMetaData": {"version": 1.0, "numberOfItems": 3, "type": "Digital" }, "BuyerMetaData": {"version" : 1.0, "isFirstTimeCustomer" : true, "numberOfPastPurchases" : 2, "numberOfDisputedPurchases" : 3, "hasOpenDispute" : true, "riskScore" : 0.75 }}' 48 | 49 | def mock_requests_post(self, url, data=None, headers=None, verify=False): 50 | mock_response = Mock() 51 | mock_response.text = '\ 52 | \ 53 | Draft\ 54 | \ 55 | \ 56 | ' 57 | mock_response.status_code = 200 58 | return mock_response 59 | 60 | def mock_requests_500_post( 61 | self, url, data=None, headers=None, verify=False): 62 | mock_response = Mock() 63 | mock_response.text = 'test' 64 | mock_response.status_code = 500 65 | return mock_response 66 | 67 | def mock_requests_generic_error_post( 68 | self, url, data=None, headers=None, verify=False): 69 | mock_response = Mock() 70 | mock_response.text = 'test' 71 | mock_response.status_code = 502 72 | return mock_response 73 | 74 | def mock_requests_503_post( 75 | self, url, data=None, headers=None, verify=False): 76 | mock_response = Mock() 77 | mock_response.text = 'test' 78 | mock_response.status_code = 503 79 | return mock_response 80 | 81 | def mock_get_login_profile(self, url, headers, params, verify): 82 | mock_response = Mock() 83 | mock_response.json.return_value = {"aud": "client_id"} 84 | mock_response.status_code = 200 85 | return mock_response 86 | 87 | def test_sandbox_setter(self): 88 | self.client.sandbox = False 89 | self.assertEqual( 90 | self.client._mws_endpoint, 91 | 'https://mws.amazonservices.com/OffAmazonPayments/2013-01-01') 92 | self.client.sandbox = True 93 | self.assertEqual( 94 | self.client._mws_endpoint, 95 | 'https://mws.amazonservices.com/OffAmazonPayments_Sandbox/2013-01-01') 96 | 97 | def test_sanitize_response_data(self): 98 | current_file_dir = os.path.dirname(__file__) 99 | test_file_path = os.path.join(current_file_dir, "log.txt") 100 | f = open(test_file_path, "r") 101 | source_text = f.read() 102 | f.close() 103 | text = self.request._sanitize_response_data(source_text) 104 | test_file_path = os.path.join(current_file_dir, "sanlog.txt") 105 | f = open(test_file_path, "r") 106 | san_text = f.read() 107 | f.close 108 | self.assertEqual(text, san_text) 109 | 110 | def test_region_exception(self): 111 | with self.assertRaises(KeyError): 112 | AmazonPayClient( 113 | mws_access_key=self.mws_access_key, 114 | mws_secret_key=self.mws_secret_key, 115 | merchant_id=self.merchant_id, 116 | handle_throttle=False, 117 | sandbox=True, 118 | region='should_throw_exception', 119 | currency_code='test') 120 | 121 | def test_set_endpoint(self): 122 | self.client._set_endpoint() 123 | self.assertEqual( 124 | self.client._mws_endpoint, 125 | 'https://mws.amazonservices.com/OffAmazonPayments_Sandbox/2013-01-01') 126 | 127 | def test_sign(self): 128 | test_signature = self.request._sign('my_test_string') 129 | self.assertEqual( 130 | test_signature, 131 | 'JQZYxe8EFlLE3XCAWotsn329rpZF7OFYhA8oo7rUV2E=') 132 | 133 | def test_application_settings(self): 134 | client = AmazonPayClient( 135 | mws_access_key=self.mws_access_key, 136 | mws_secret_key=self.mws_secret_key, 137 | merchant_id=self.merchant_id, 138 | handle_throttle=False, 139 | sandbox=True, 140 | region='na', 141 | currency_code='USD', 142 | application_name='test_application', 143 | application_version='test_application_version') 144 | self.assertEqual(client.application_name, 'test_application') 145 | self.assertEqual( 146 | client.application_version, 147 | 'test_application_version') 148 | 149 | def test_properties(self): 150 | self.assertEqual(self.client.mws_access_key, 'mws_access_key') 151 | self.assertEqual(self.client.mws_secret_key, 'mws_secret_key') 152 | self.assertEqual(self.client.merchant_id, 'merchant_id') 153 | self.assertEqual(self.client._region_code, 'na') 154 | self.assertEqual(self.client.currency_code, 'USD') 155 | self.assertEqual(self.client.handle_throttle, False) 156 | self.assertEqual(self.client.sandbox, True) 157 | 158 | @patch('requests.post') 159 | def test_generic_error_response(self, mock_urlopen): 160 | mock_urlopen.side_effect = self.mock_requests_generic_error_post 161 | self.request.send_post() 162 | response = self.request.response 163 | self.assertEqual(type(response), PaymentErrorResponse) 164 | 165 | @patch('requests.post') 166 | def test_500_response(self, mock_urlopen): 167 | mock_urlopen.side_effect = self.mock_requests_500_post 168 | self.request.send_post() 169 | response = self.request.response.to_dict() 170 | self.assertEqual(response['error'], '500') 171 | 172 | @patch('requests.post') 173 | def test_503_response(self, mock_urlopen): 174 | mock_urlopen.side_effect = self.mock_requests_503_post 175 | self.request.send_post() 176 | response = self.request.response.to_dict() 177 | self.assertEqual(response['error'], '503') 178 | 179 | @patch('requests.post') 180 | def test_headers(self, mock_urlopen): 181 | py_version = ".".join(map(str, sys.version_info[:3])) 182 | mock_urlopen.side_effect = self.mock_requests_post 183 | self.client.get_service_status() 184 | if sys.version_info[0] == 3 and sys.version_info[1] >= 2: 185 | py_valid = True 186 | 187 | header_expected = { 188 | 'Content-Type': 'application/x-www-form-urlencoded', 189 | "User-Agent":'amazon-pay-sdk-python/{0} ({1}Python/{2}; {3}/{4})'.format( 190 | str(ap_version.versions['application_version']), 191 | (''), 192 | py_version, 193 | str(platform.system()), 194 | str(platform.release()) 195 | ) 196 | } 197 | self.assertEqual(mock_urlopen.call_args[1]['headers'], header_expected) 198 | self.assertTrue(py_valid, True) 199 | 200 | @patch('requests.post') 201 | def test_get_merchant_account_status(self, mock_urlopen): 202 | mock_urlopen.side_effect = self.mock_requests_post 203 | self.client.get_merchant_account_status( 204 | merchant_id='A2AMGDUDUJFL', 205 | mws_auth_token='amzn.mws.d8f2d-6a5f-b46293482379') 206 | parameters = { 207 | 'Action': 'GetMerchantAccountStatus', 208 | 'SellerId': 'A2AMGDUDUJFL', 209 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b46293482379'} 210 | data_expected = self.request._querystring(parameters) 211 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 212 | 213 | @patch('requests.post') 214 | def test_create_order_reference_for_id(self, mock_urlopen): 215 | mock_urlopen.side_effect = self.mock_requests_post 216 | self.client.create_order_reference_for_id( 217 | object_id='B01-462347-4762387', 218 | object_id_type='BillingAgreement', 219 | order_total='1', 220 | inherit_shipping_address=False, 221 | confirm_now=True, 222 | platform_id='testPlatformId123', 223 | seller_note='testSellerNote2145', 224 | seller_order_id='testSellerOrderId21434', 225 | supplementary_data=self.supplementary_data, 226 | store_name='testStoreName1234', 227 | custom_information='testCustomInfo12435', 228 | merchant_id='A2AMR0DUGHIUEHQ', 229 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06476237468923749823') 230 | parameters = { 231 | 'Action': 'CreateOrderReferenceForId', 232 | 'Id': 'B01-462347-4762387', 233 | 'IdType': 'BillingAgreement', 234 | 'OrderReferenceAttributes.OrderTotal.Amount': '1', 235 | 'OrderReferenceAttributes.OrderTotal.CurrencyCode': 'USD', 236 | 'InheritShippingAddress': 'false', 237 | 'ConfirmNow': 'true', 238 | 'OrderReferenceAttributes.PlatformId': 'testPlatformId123', 239 | 'OrderReferenceAttributes.SellerNote': 'testSellerNote2145', 240 | 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId': 'testSellerOrderId21434', 241 | 'OrderReferenceAttributes.SupplementaryData': self.supplementary_data, 242 | 'OrderReferenceAttributes.SellerOrderAttributes.StoreName': 'testStoreName1234', 243 | 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation': 'testCustomInfo12435', 244 | 'SellerId': 'A2AMR0DUGHIUEHQ', 245 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06476237468923749823'} 246 | 247 | data_expected = self.request._querystring(parameters) 248 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 249 | 250 | @patch('requests.post') 251 | def test_get_billing_agreement_details(self, mock_urlopen): 252 | mock_urlopen.side_effect = self.mock_requests_post 253 | self.client.get_billing_agreement_details( 254 | amazon_billing_agreement_id='B01-47236478-46253862', 255 | address_consent_token='AFYDFWIGHUIP', 256 | merchant_id='ADEIUYIOQUIOW', 257 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-7462348237498') 258 | parameters = { 259 | 'Action': 'GetBillingAgreementDetails', 260 | 'AmazonBillingAgreementId': 'B01-47236478-46253862', 261 | 'AddressConsentToken': 'AFYDFWIGHUIP', 262 | 'SellerId': 'ADEIUYIOQUIOW', 263 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-7462348237498'} 264 | data_expected = self.request._querystring(parameters) 265 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 266 | 267 | @patch('requests.post') 268 | def test_set_billing_agreement_details(self, mock_urlopen): 269 | mock_urlopen.side_effect = self.mock_requests_post 270 | self.client.set_billing_agreement_details( 271 | amazon_billing_agreement_id='B01-47236478-462863428', 272 | platform_id='testPlatformId89', 273 | seller_note='testSellerNote3251', 274 | seller_billing_agreement_id='testBillingAgreement1213', 275 | store_name='testStoreName5237', 276 | custom_information='testCustomInfo32365', 277 | merchant_id='AGDUIEJOQEOPQWIKO', 278 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06a-bc12-4623862') 279 | parameters = { 280 | 'Action': 'SetBillingAgreementDetails', 281 | 'AmazonBillingAgreementId': 'B01-47236478-462863428', 282 | 'BillingAgreementAttributes.PlatformId': 'testPlatformId89', 283 | 'BillingAgreementAttributes.SellerNote': 'testSellerNote3251', 284 | 'BillingAgreementAttributes.SellerBillingAgreementAttributes.SellerBillingAgreementId': 'testBillingAgreement1213', 285 | 'BillingAgreementAttributes.SellerBillingAgreementAttributes.StoreName': 'testStoreName5237', 286 | 'BillingAgreementAttributes.SellerBillingAgreementAttributes.CustomInformation': 'testCustomInfo32365', 287 | 'SellerId': 'AGDUIEJOQEOPQWIKO', 288 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06a-bc12-4623862'} 289 | data_expected = self.request._querystring(parameters) 290 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 291 | 292 | @patch('requests.post') 293 | def test_confirm_billing_agreement(self, mock_urlopen): 294 | mock_urlopen.side_effect = self.mock_requests_post 295 | self.client.confirm_billing_agreement( 296 | amazon_billing_agreement_id='B01-47236478-46284638789', 297 | merchant_id='AGFUHWIEJLMLK', 298 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06a-bc12-4263289') 299 | parameters = { 300 | 'Action': 'ConfirmBillingAgreement', 301 | 'AmazonBillingAgreementId': 'B01-47236478-46284638789', 302 | 'SellerId': 'AGFUHWIEJLMLK', 303 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06a-bc12-4263289'} 304 | data_expected = self.request._querystring(parameters) 305 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 306 | 307 | @patch('requests.post') 308 | def test_validate_billing_agreement(self, mock_urlopen): 309 | mock_urlopen.side_effect = self.mock_requests_post 310 | self.client.validate_billing_agreement( 311 | amazon_billing_agreement_id='B01-47236478-46287462347823490', 312 | merchant_id='AGFUHWHYDIIJQWL', 313 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06a-bc12-457267342897') 314 | parameters = { 315 | 'Action': 'ValidateBillingAgreement', 316 | 'AmazonBillingAgreementId': 'B01-47236478-46287462347823490', 317 | 'SellerId': 'AGFUHWHYDIIJQWL', 318 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06a-bc12-457267342897'} 319 | data_expected = self.request._querystring(parameters) 320 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 321 | 322 | @patch('requests.post') 323 | def test_authorize_on_billing_agreement(self, mock_urlopen): 324 | mock_urlopen.side_effect = self.mock_requests_post 325 | self.client.authorize_on_billing_agreement( 326 | amazon_billing_agreement_id='B01-4653268-47632947', 327 | authorization_reference_id='testAuthRefId31253', 328 | authorization_amount='1', 329 | seller_authorization_note='testSellerAuthNote3612367', 330 | transaction_timeout=0, 331 | capture_now=True, 332 | soft_descriptor='testSoftDescriptor42837', 333 | seller_note='testSellerNote4721893', 334 | platform_id='testPlatformId47237', 335 | seller_order_id='testSellerOrderId4237', 336 | store_name='testStoreName842398', 337 | custom_information='testCustomInfo623', 338 | supplementary_data=self.supplementary_data, 339 | inherit_shipping_address=False, 340 | merchant_id='A2AMR0FDYHGHJD', 341 | mws_auth_token='amzn.mws.d6ac8f2d-463286-fhegsdj46238') 342 | parameters = { 343 | 'Action': 'AuthorizeOnBillingAgreement', 344 | 'AmazonBillingAgreementId': 'B01-4653268-47632947', 345 | 'TransactionTimeout': '0', 346 | 'AuthorizationReferenceId': 'testAuthRefId31253', 347 | 'AuthorizationAmount.Amount': '1', 348 | 'AuthorizationAmount.CurrencyCode': 'USD', 349 | 'CaptureNow': 'true', 350 | 'SellerAuthorizationNote': 'testSellerAuthNote3612367', 351 | 'SoftDescriptor': 'testSoftDescriptor42837', 352 | 'SellerNote': 'testSellerNote4721893', 353 | 'PlatformId': 'testPlatformId47237', 354 | 'InheritShippingAddress': 'false', 355 | 'SellerOrderAttributes.SellerOrderId': 'testSellerOrderId4237', 356 | 'SellerOrderAttributes.StoreName': 'testStoreName842398', 357 | 'SellerOrderAttributes.CustomInformation': 'testCustomInfo623', 358 | 'SellerOrderAttributes.SupplementaryData': self.supplementary_data, 359 | 'SellerId': 'A2AMR0FDYHGHJD', 360 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-463286-fhegsdj46238'} 361 | data_expected = self.request._querystring(parameters) 362 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 363 | 364 | @patch('requests.post') 365 | def test_close_billing_agreement(self, mock_urlopen): 366 | mock_urlopen.side_effect = self.mock_requests_post 367 | self.client.close_billing_agreement( 368 | amazon_billing_agreement_id='B01-4236278-3761372', 369 | closure_reason='testClosureReason', 370 | merchant_id='A2AMR0DGUQHWIJQWL', 371 | mws_auth_token='amzn.mws.d6ac8f2d-463286-fhegsdj46238') 372 | parameters = { 373 | 'Action': 'CloseBillingAgreement', 374 | 'AmazonBillingAgreementId': 'B01-4236278-3761372', 375 | 'ClosureReason': 'testClosureReason', 376 | 'SellerId': 'A2AMR0DGUQHWIJQWL', 377 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-463286-fhegsdj46238'} 378 | data_expected = self.request._querystring(parameters) 379 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 380 | 381 | @patch('requests.post') 382 | def test_set_order_reference_details(self, mock_urlopen): 383 | mock_urlopen.side_effect = self.mock_requests_post 384 | self.client.set_order_reference_details( 385 | amazon_order_reference_id='P01-1234567-7654897', 386 | order_total='1', 387 | platform_id='platformId4673', 388 | seller_note='sellerNote38278', 389 | seller_order_id='sellerOrderId123', 390 | store_name='testStoreName387289', 391 | custom_information='customInfo34278', 392 | merchant_id='A2AMR0CLHYUTGH', 393 | mws_auth_token='amzn.mws.d8f2d-6a5f-b06a4628', 394 | supplementary_data=self.supplementary_data) 395 | parameters = { 396 | 'Action': 'SetOrderReferenceDetails', 397 | 'AmazonOrderReferenceId': 'P01-1234567-7654897', 398 | 'OrderReferenceAttributes.OrderTotal.Amount': '1', 399 | 'OrderReferenceAttributes.OrderTotal.CurrencyCode': 'USD', 400 | 'OrderReferenceAttributes.PlatformId': 'platformId4673', 401 | 'OrderReferenceAttributes.SellerNote': 'sellerNote38278', 402 | 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId': 'sellerOrderId123', 403 | 'OrderReferenceAttributes.SellerOrderAttributes.StoreName': 'testStoreName387289', 404 | 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation': 'customInfo34278', 405 | 'SellerId': 'A2AMR0CLHYUTGH', 406 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b06a4628', 407 | 'OrderReferenceAttributes.SellerOrderAttributes.SupplementaryData': self.supplementary_data} 408 | data_expected = self.request._querystring(parameters) 409 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 410 | 411 | @patch('requests.post') 412 | def test_set_order_attributes(self, mock_urlopen): 413 | mock_urlopen.side_effect = self.mock_requests_post 414 | self.client.set_order_attributes( 415 | amazon_order_reference_id='P01-1234567-4827348237', 416 | currency_code='USD', 417 | amount='1', 418 | seller_order_id='testSellerOrderId5371', 419 | payment_service_provider_id='AGHJHHJKJHL', 420 | payment_service_provider_order_id='testPSPOrderId', 421 | platform_id='testPlatformId472', 422 | seller_note='testSellerNote4628', 423 | request_payment_authorization='true', 424 | store_name='testStoreName26157', 425 | list_order_item_categories=['test'], 426 | custom_information='testCustomInfo35273', 427 | merchant_id='AGHJHHJKJHL', 428 | mws_auth_token='amzn.mws.d8f2d-6a5f-b06a4628', 429 | supplementary_data=self.supplementary_data) 430 | 431 | parameters = { 432 | 'Action': 'SetOrderAttributes', 433 | 'AmazonOrderReferenceId': 'P01-1234567-4827348237', 434 | 'OrderAttributes.OrderTotal.Amount': '1', 435 | 'OrderAttributes.OrderTotal.CurrencyCode': 'USD', 436 | 'OrderAttributes.SellerOrderAttributes.CustomInformation': 'testCustomInfo35273', 437 | 'OrderAttributes.SellerOrderAttributes.OrderItemCategories.OrderItemCategory.1': 'test', 438 | 'OrderAttributes.PaymentServiceProviderAttributes.PaymentServiceProviderId': 'AGHJHHJKJHL', 439 | 'OrderAttributes.PaymentServiceProviderAttributes.PaymentServiceProviderOrderId': 'testPSPOrderId', 440 | 'OrderAttributes.PlatformId': 'testPlatformId472', 441 | 'OrderAttributes.RequestPaymentAuthorization': 'true', 442 | 'OrderAttributes.SellerNote': 'testSellerNote4628', 443 | 'OrderAttributes.SellerOrderAttributes.SellerOrderId': 'testSellerOrderId5371', 444 | 'OrderAttributes.SellerOrderAttributes.StoreName': 'testStoreName26157', 445 | 'SellerId': 'AGHJHHJKJHL', 446 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b06a4628', 447 | 'OrderAttributes.SellerOrderAttributes.SupplementaryData': self.supplementary_data} 448 | data_expected = self.request._querystring(parameters) 449 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 450 | 451 | 452 | @patch('requests.post') 453 | def test_get_order_reference_details(self, mock_urlopen): 454 | mock_urlopen.side_effect = self.mock_requests_post 455 | self.client.get_order_reference_details( 456 | amazon_order_reference_id='P01-476238-47238', 457 | address_consent_token='ADUHIQILPLP', 458 | access_token='AHJJOKJJHNJNJK', 459 | merchant_id='ADGJUHJWKJKJ', 460 | mws_auth_token='amzn.mws.d8f2d-6a5f-b427489234798') 461 | parameters = { 462 | 'Action': 'GetOrderReferenceDetails', 463 | 'AmazonOrderReferenceId': 'P01-476238-47238', 464 | 'AddressConsentToken': 'ADUHIQILPLP', 465 | 'AccessToken': 'AHJJOKJJHNJNJK', 466 | 'SellerId': 'ADGJUHJWKJKJ', 467 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b427489234798'} 468 | data_expected = self.request._querystring(parameters) 469 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 470 | 471 | @patch('requests.post') 472 | def test_confirm_order_reference(self, mock_urlopen): 473 | mock_urlopen.side_effect = self.mock_requests_post 474 | self.client.confirm_order_reference( 475 | amazon_order_reference_id='P01-476238-47263849238', 476 | merchant_id='AHDGJHDJKFJIIIJ', 477 | mws_auth_token='amzn.mws.d8f2d-6a5f-b42rwe74237489', 478 | success_url='https://www.success.com', 479 | failure_url='https://www.failure.com', 480 | authorization_amount='5', 481 | currency_code='USD' 482 | ) 483 | 484 | parameters = { 485 | 'Action': 'ConfirmOrderReference', 486 | 'AmazonOrderReferenceId': 'P01-476238-47263849238', 487 | 'SellerId': 'AHDGJHDJKFJIIIJ', 488 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b42rwe74237489', 489 | 'SuccessUrl': 'https://www.success.com', 490 | 'FailureUrl': 'https://www.failure.com', 491 | 'AuthorizationAmount.Amount': '5', 492 | 'AuthorizationAmount.CurrencyCode': 'USD' 493 | } 494 | 495 | data_expected = self.request._querystring(parameters) 496 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 497 | 498 | @patch('requests.post') 499 | def test_confirm_order_reference_with_expect_immediate_authorization_as_true(self, mock_urlopen): 500 | mock_urlopen.side_effect = self.mock_requests_post 501 | self.client.confirm_order_reference( 502 | amazon_order_reference_id='P02-6009038-6480465', 503 | merchant_id='AWBW6G04XFUTG', 504 | expect_immediate_authorization=True) 505 | parameters = { 506 | 'Action': 'ConfirmOrderReference', 507 | 'AmazonOrderReferenceId': 'P02-6009038-6480465', 508 | 'SellerId': 'AWBW6G04XFUTG', 509 | 'ExpectImmediateAuthorization': 'true'} 510 | data_expected = self.request._querystring(parameters) 511 | # print(data_expected) 512 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 513 | 514 | @patch('requests.post') 515 | def test_confirm_order_reference_with_expect_immediate_authorization_as_false(self, mock_urlopen): 516 | mock_urlopen.side_effect = self.mock_requests_post 517 | self.client.confirm_order_reference( 518 | amazon_order_reference_id='P02-0736942-3399325', 519 | merchant_id='AWBW6G04XFUTG', 520 | expect_immediate_authorization=False) 521 | parameters = { 522 | 'Action': 'ConfirmOrderReference', 523 | 'AmazonOrderReferenceId': 'P02-0736942-3399325', 524 | 'SellerId': 'AWBW6G04XFUTG', 525 | 'ExpectImmediateAuthorization': 'false'} 526 | data_expected = self.request._querystring(parameters) 527 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 528 | 529 | @patch('requests.post') 530 | def test_cancel_order_reference(self, mock_urlopen): 531 | mock_urlopen.side_effect = self.mock_requests_post 532 | self.client.cancel_order_reference( 533 | amazon_order_reference_id='P01-476238-472642737489', 534 | cancelation_reason='testCancelReason', 535 | merchant_id='AJHDELWJEKELW', 536 | mws_auth_token='amzn.mws.d8f2d-6a5f-b42rw72372897893') 537 | parameters = { 538 | 'Action': 'CancelOrderReference', 539 | 'AmazonOrderReferenceId': 'P01-476238-472642737489', 540 | 'CancelationReason': 'testCancelReason', 541 | 'SellerId': 'AJHDELWJEKELW', 542 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b42rw72372897893'} 543 | data_expected = self.request._querystring(parameters) 544 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 545 | 546 | @patch('requests.post') 547 | def test_close_order_reference(self, mock_urlopen): 548 | mock_urlopen.side_effect = self.mock_requests_post 549 | self.client.close_order_reference( 550 | amazon_order_reference_id='P01-476238-472642737489', 551 | closure_reason='testClosureReason24156', 552 | merchant_id='AJHYJHJLYFYGTUHK', 553 | mws_auth_token='amzn.mws.d8f2d-6a5f-b42ryurueruio3uio87') 554 | parameters = { 555 | 'Action': 'CloseOrderReference', 556 | 'AmazonOrderReferenceId': 'P01-476238-472642737489', 557 | 'ClosureReason': 'testClosureReason24156', 558 | 'SellerId': 'AJHYJHJLYFYGTUHK', 559 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b42ryurueruio3uio87'} 560 | data_expected = self.request._querystring(parameters) 561 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 562 | 563 | @patch('requests.post') 564 | def test_list_order_reference(self, mock_urlopen): 565 | mock_urlopen.side_effect = self.mock_requests_post 566 | self.client.list_order_reference( 567 | query_id='testSellerOrderId124', 568 | query_id_type='SellerOrderId', 569 | created_time_range_start='testStart', 570 | created_time_range_end='testEnd', 571 | sort_order='ascending', 572 | page_size=1, 573 | merchant_id='AFHRWKJEKJLJKL', 574 | mws_auth_token='amzn.mws.d8f2d-6a5f-b42ryurueruio3uio87', 575 | order_reference_status_list_filter=['test1', 'test2']) 576 | 577 | if self.client.region in ('na'): 578 | payment_domain = 'NA_USD' 579 | elif self.client.region in ('uk', 'gb'): 580 | payment_domain = 'EU_GBP' 581 | elif self.client.region in ('jp', 'fe'): 582 | payment_domain = 'FE_JPY' 583 | elif self.client.region in ('eu', 'de', 'fr', 'it', 'es', 'cy'): 584 | payment_domain = 'EU_EUR' 585 | else: 586 | raise ValueError("Error. The current region code does not match our records") 587 | 588 | 589 | parameters = { 590 | 'Action': 'ListOrderReference', 591 | 'QueryId': 'testSellerOrderId124', 592 | 'QueryIdType': 'SellerOrderId', 593 | 'PaymentDomain': payment_domain, 594 | 'CreatedTimeRange.StartTime': 'testStart', 595 | 'CreatedTimeRange.EndTime': 'testEnd', 596 | 'SortOrder': 'ascending', 597 | 'PageSize': 1, 598 | 'SellerId': 'AFHRWKJEKJLJKL', 599 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b42ryurueruio3uio87', 600 | 'OrderReferenceStatusListFilter.OrderReferenceStatus.1': 'test1', 601 | 'OrderReferenceStatusListFilter.OrderReferenceStatus.2': 'test2'} 602 | data_expected = self.request._querystring(parameters) 603 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 604 | 605 | @patch('requests.post') 606 | def test_list_order_reference_time_check_error(self, mock_urlopen): 607 | mock_urlopen.side_effect = self.mock_requests_generic_error_post 608 | self.client.list_order_reference( 609 | query_id='testSellerOrderId12444', 610 | query_id_type='SellerOrderId', 611 | created_time_range_start='testStart', 612 | created_time_range_end=None, 613 | sort_order=None, 614 | page_size=None, 615 | merchant_id='AGDJHKWJLHHK', 616 | mws_auth_token='amzn.mws.d8f2d-6a5f-b42r23564783492380', 617 | order_reference_status_list_filter=None) 618 | 619 | if self.client.region in ('na'): 620 | payment_domain = 'NA_USD' 621 | elif self.client.region in ('uk', 'gb'): 622 | payment_domain = 'EU_GBP' 623 | elif self.client.region in ('jp', 'fe'): 624 | payment_domain = 'FE_JPY' 625 | elif self.client.region in ('eu', 'de', 'fr', 'it', 'es', 'cy'): 626 | payment_domain = 'EU_EUR' 627 | else: 628 | raise ValueError("Error. The current region code does not match our records") 629 | 630 | 631 | parameters = { 632 | 'Action': 'ListOrderReference', 633 | 'QueryId': 'testSellerOrderId12444', 634 | 'QueryIdType': 'SellerOrderId', 635 | 'PaymentDomain': payment_domain, 636 | 'SellerId': 'AGDJHKWJLHHK', 637 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b42r23564783492380', 638 | 'CreatedTimeRange.StartTime': 'testStart'} 639 | data_expected = self.request._querystring(parameters) 640 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 641 | 642 | @patch('requests.post') 643 | def test_list_order_reference_by_next_token(self, mock_urlopen): 644 | mock_urlopen.side_effect = self.mock_requests_post 645 | self.client.list_order_reference_by_next_token( 646 | next_page_token='yrtewyy4823749329482394023940', 647 | merchant_id='AHFUHWJELWJELEJW', 648 | mws_auth_token='amzn.mws.d8f2d-6a5f-b42r23436248623748') 649 | parameters= { 650 | 'Action': 'ListOrderReferenceByNextToken', 651 | 'NextPageToken': 'yrtewyy4823749329482394023940', 652 | 'SellerId': 'AHFUHWJELWJELEJW', 653 | 'MWSAuthToken': 'amzn.mws.d8f2d-6a5f-b42r23436248623748'} 654 | data_expected = self.request._querystring(parameters) 655 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 656 | 657 | @patch('requests.post') 658 | def test_authorize(self, mock_urlopen): 659 | mock_urlopen.side_effect = self.mock_requests_post 660 | self.client.authorize( 661 | amazon_order_reference_id='P01-351-461238848937', 662 | authorization_reference_id='testAuthId123', 663 | authorization_amount='1', 664 | seller_authorization_note='testAuthNote123', 665 | transaction_timeout=0, 666 | capture_now=True, 667 | soft_descriptor='testSoftDescriptor12', 668 | merchant_id='A2AMR0CUYDHYIOW', 669 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06a-bc3276378843298-fgeswyd') 670 | parameters = { 671 | 'Action': 'Authorize', 672 | 'AmazonOrderReferenceId': 'P01-351-461238848937', 673 | 'AuthorizationReferenceId': 'testAuthId123', 674 | 'AuthorizationAmount.Amount': '1', 675 | 'AuthorizationAmount.CurrencyCode': 'USD', 676 | 'SellerAuthorizationNote': 'testAuthNote123', 677 | 'TransactionTimeout': '0', 678 | 'CaptureNow': 'true', 679 | 'SoftDescriptor': 'testSoftDescriptor12', 680 | 'SellerId': 'A2AMR0CUYDHYIOW', 681 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06a-bc3276378843298-fgeswyd'} 682 | data_expected = self.request._querystring(parameters) 683 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 684 | 685 | @patch('requests.post') 686 | def test_get_authorization_details(self, mock_urlopen): 687 | mock_urlopen.side_effect = self.mock_requests_post 688 | self.client.get_authorization_details( 689 | amazon_authorization_id='P01-351-461238848937-A42374987239849', 690 | merchant_id='AGDFHGWEHGWJH', 691 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06a-bc412328378') 692 | parameters = { 693 | 'Action': 'GetAuthorizationDetails', 694 | 'AmazonAuthorizationId': 'P01-351-461238848937-A42374987239849', 695 | 'SellerId': 'AGDFHGWEHGWJH', 696 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06a-bc412328378'} 697 | data_expected = self.request._querystring(parameters) 698 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 699 | 700 | @patch('requests.post') 701 | def test_capture(self, mock_urlopen): 702 | mock_urlopen.side_effect = self.mock_requests_post 703 | self.client.capture( 704 | amazon_authorization_id='P01-1234567-7654321-A467823648', 705 | capture_reference_id='testCaptureRefId123', 706 | capture_amount='1', 707 | seller_capture_note='testCaptureNote124', 708 | soft_descriptor='testSoftDescriptor123', 709 | merchant_id='A2AMR8YRGWKHK', 710 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b06a-472637-753648') 711 | parameters = { 712 | 'Action': 'Capture', 713 | 'AmazonAuthorizationId': 'P01-1234567-7654321-A467823648', 714 | 'CaptureReferenceId': 'testCaptureRefId123', 715 | 'CaptureAmount.Amount': '1', 716 | 'CaptureAmount.CurrencyCode': 'USD', 717 | 'SellerCaptureNote': 'testCaptureNote124', 718 | 'SoftDescriptor': 'testSoftDescriptor123', 719 | 'SellerId': 'A2AMR8YRGWKHK', 720 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b06a-472637-753648'} 721 | data_expected = self.request._querystring(parameters) 722 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 723 | 724 | @patch('requests.post') 725 | def test_get_capture_details(self, mock_urlopen): 726 | mock_urlopen.side_effect = self.mock_requests_post 727 | self.client.get_capture_details( 728 | amazon_capture_id='P01-4763247-C6472482379', 729 | merchant_id='A2AYDGTIQUYOHO', 730 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b645234782374903') 731 | parameters = { 732 | 'Action': 'GetCaptureDetails', 733 | 'AmazonCaptureId': 'P01-4763247-C6472482379', 734 | 'SellerId': 'A2AYDGTIQUYOHO', 735 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b645234782374903'} 736 | data_expected = self.request._querystring(parameters) 737 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 738 | 739 | @patch('requests.post') 740 | def test_close_authorization(self, mock_urlopen): 741 | mock_urlopen.side_effect = self.mock_requests_post 742 | self.client.close_authorization( 743 | amazon_authorization_id='P01-4763247-A6568472482379', 744 | closure_reason='testClosure', 745 | merchant_id='A2ATTYIUHBUMTYU', 746 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b645234782374903') 747 | parameters = { 748 | 'Action': 'CloseAuthorization', 749 | 'AmazonAuthorizationId': 'P01-4763247-A6568472482379', 750 | 'ClosureReason': 'testClosure', 751 | 'SellerId': 'A2ATTYIUHBUMTYU', 752 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b645234782374903'} 753 | data_expected = self.request._querystring(parameters) 754 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 755 | 756 | @patch('requests.post') 757 | def test_refund(self, mock_urlopen): 758 | mock_urlopen.side_effect = self.mock_requests_post 759 | self.client.refund( 760 | amazon_capture_id='P01-4763247-C645749', 761 | refund_reference_id='testRefundRefId125', 762 | refund_amount='1', 763 | seller_refund_note='testRefundNote123', 764 | soft_descriptor='testSoftDescriptor167', 765 | merchant_id='A2ATGUHFHWDJEOPW', 766 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b645234782374903') 767 | parameters = { 768 | 'Action': 'Refund', 769 | 'AmazonCaptureId': 'P01-4763247-C645749', 770 | 'RefundReferenceId': 'testRefundRefId125', 771 | 'RefundAmount.Amount': '1', 772 | 'RefundAmount.CurrencyCode': 'USD', 773 | 'SellerRefundNote': 'testRefundNote123', 774 | 'SoftDescriptor': 'testSoftDescriptor167', 775 | 'SellerId': 'A2ATGUHFHWDJEOPW', 776 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b645234782374903'} 777 | data_expected = self.request._querystring(parameters) 778 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 779 | 780 | @patch('requests.post') 781 | def test_get_refund_details(self, mock_urlopen): 782 | mock_urlopen.side_effect = self.mock_requests_post 783 | self.client.get_refund_details( 784 | amazon_refund_id='P01-4763247-R643927483', 785 | merchant_id='A2ATGUYIOUHIJL', 786 | mws_auth_token='amzn.mws.d6ac8f2d-6a5f-b6447623479') 787 | parameters = { 788 | 'Action': 'GetRefundDetails', 789 | 'AmazonRefundId': 'P01-4763247-R643927483', 790 | 'SellerId': 'A2ATGUYIOUHIJL', 791 | 'MWSAuthToken': 'amzn.mws.d6ac8f2d-6a5f-b6447623479'} 792 | data_expected = self.request._querystring(parameters) 793 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 794 | 795 | @patch('requests.post') 796 | def test_get_service_status(self, mock_urlopen): 797 | mock_urlopen.side_effect = self.mock_requests_post 798 | self.client.get_service_status() 799 | parameters = { 800 | 'Action': 'GetServiceStatus'} 801 | data_expected = self.request._querystring(parameters) 802 | self.assertEqual(mock_urlopen.call_args[1]['data'], data_expected) 803 | 804 | def test_is_order_reference_id(self): 805 | self.assertTrue(self.client.is_order_reference_id('P')) 806 | self.assertTrue(self.client.is_order_reference_id('S')) 807 | self.assertFalse(self.client.is_order_reference_id('X')) 808 | 809 | def test_is_billing_agreement_id(self): 810 | self.assertTrue(self.client.is_billing_agreement_id('B')) 811 | self.assertTrue(self.client.is_billing_agreement_id('C')) 812 | self.assertFalse(self.client.is_billing_agreement_id('X')) 813 | 814 | def test_response_invalid_xml(self): 815 | with self.assertRaises(ValueError): 816 | PaymentResponse('') 817 | 818 | @patch('requests.post') 819 | def test_response_to_xml(self, mock_urlopen): 820 | mock_urlopen.side_effect = self.mock_requests_post 821 | response = self.client.get_service_status() 822 | self.assertTrue(et.fromstring(response.to_xml())) 823 | 824 | @patch('requests.post') 825 | def test_response_to_json(self, mock_urlopen): 826 | mock_urlopen.side_effect = self.mock_requests_post 827 | response = self.client.get_service_status() 828 | self.assertTrue(json.loads(response.to_json())) 829 | 830 | def test_response_to_json_utf8(self): 831 | text = self.response.to_json() 832 | utf8_text = '{"test": "الفلانية فلا"}' 833 | self.assertEqual(text, utf8_text) 834 | 835 | @patch('requests.post') 836 | def test_response_to_dict(self, mock_urlopen): 837 | mock_urlopen.side_effect = self.mock_requests_post 838 | response = self.client.get_service_status() 839 | self.assertEqual(type(response.to_dict()), dict) 840 | 841 | @patch('requests.get') 842 | def test_get_login_profile(self, mock_urlopen): 843 | mock_urlopen.side_effect = self.mock_get_login_profile 844 | response = self.client.get_login_profile('access_token', 'client_id') 845 | 846 | def test_environment_variables(self): 847 | os.environ['AP_REGION'] = 'na' 848 | os.environ['AP_MWS_ACCESS_KEY'] = 'AP_MWS_ACCESS_KEY' 849 | os.environ['AP_MERCHANT_ID'] = 'AP_MERCHANT_ID' 850 | os.environ['AP_CURRENCY_CODE'] = 'AP_CURRENCY_CODE' 851 | os.environ['AP_MWS_SECRET_KEY'] = 'AP_MWS_SECRET_KEY' 852 | 853 | client = AmazonPayClient(sandbox=True) 854 | self.assertEqual(client.region, 'na') 855 | self.assertEqual(client.mws_access_key, 'AP_MWS_ACCESS_KEY') 856 | self.assertEqual(client.mws_secret_key, 'AP_MWS_SECRET_KEY') 857 | self.assertEqual(client.merchant_id, 'AP_MERCHANT_ID') 858 | self.assertEqual(client.currency_code, 'AP_CURRENCY_CODE') 859 | 860 | os.environ['AP_REGION'] = 'AP_REGION' 861 | with self.assertRaises(KeyError): 862 | client = AmazonPayClient() 863 | 864 | 865 | if __name__ == "__main__": 866 | unittest.main() 867 | -------------------------------------------------------------------------------- /test/test_ipn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from amazon_pay.ipn_handler import IpnHandler 4 | 5 | 6 | class IpnHandlerTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.maxDiff = None 10 | 11 | with open( 12 | '{}/test.pem'.format(os.path.dirname(os.path.realpath(__file__)))) as pemfile: 13 | self.pem = pemfile.read() 14 | 15 | 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\\":\\"\\\\n \\\\n \\\\n P01-0000000-0000000-000000<\\\\/AmazonOrderReferenceId>\\\\n \\\\n 0.0<\\\\/Amount>\\\\n USD<\\\\/CurrencyCode>\\\\n <\\\\/OrderTotal>\\\\n \\\\n \\\\n Closed<\\\\/State> \\\\n 2013-01-01T01:01:01.001Z<\\\\/LastUpdateTimestamp>\\\\n AmazonClosed<\\\\/ReasonCode>\\\\n <\\\\/OrderReferenceStatus>\\\\n 2013-01-01T01:01:01.001Z<\\\\/CreationTimestamp> \\\\n 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}' 16 | self.body_invalid = b'{\n "Type" : "Notification",\n "MessageId" : "28908206-3478-5398-bcf7-cfbd41c2a223",\n "TopicArn" : "invalid",\n "Message" : "{\\"NotificationReferenceId\\":\\"1111111-1111-11111-1111-11111EXAMPLE\\",\\"MarketplaceID\\":\\"A3BXB0YN3XH17H\\",\\"NotificationType\\":\\"OrderReferenceNotification\\",\\"IsSample\\":true,\\"SellerId\\":\\"AQR8184NJXADU\\",\\"ReleaseEnvironment\\":\\"Sandbox\\",\\"Version\\":\\"2013-01-01\\",\\"NotificationData\\":\\"\\\\n \\\\n \\\\n P01-0000000-0000000-000000<\\\\/AmazonOrderReferenceId>\\\\n \\\\n 0.0<\\\\/Amount>\\\\n USD<\\\\/CurrencyCode>\\\\n <\\\\/OrderTotal>\\\\n \\\\n \\\\n Closed<\\\\/State> \\\\n 2013-01-01T01:01:01.001Z<\\\\/LastUpdateTimestamp>\\\\n AmazonClosed<\\\\/ReasonCode>\\\\n <\\\\/OrderReferenceStatus>\\\\n 2013-01-01T01:01:01.001Z<\\\\/CreationTimestamp> \\\\n 2013-01-01T01:01:01.001Z<\\\\/ExpirationTimestamp>\\\\n <\\\\/OrderReference>\\\\n <\\\\/OrderReferenceNotification>\\",\\"Timestamp\\":\\"2015-04-30T00:12:42.805Z\\"}",\n "Timestamp" : "2015-04-30T00:12:42.885Z",\n "SignatureVersion" : "1",\n "Signature" : "ZChg+1FlUr8OUfu9kd7B2wzT7G1Z0BWf2mH3MH5MtDqhI4t9j5lvG9YqC20LSXV+x3ajvnEmyt2YO635KIAA+Ig4IKeCgnm/YJNjxqtdaOS01M4+3vw9zaeKPY3FlTBgG3T+J3+K3SLARIeblVJhabA0TXVatqtFbMwV81xxKnLxqE5Ik8MZSBAQdHFm6u2lNIruluQakL1mmDUm/2Szj+DkMFrjsQce7fcbkr5TCJ0YB5oYAtkG2MKODYEXYAAlpUe3G0qtBT8WyOVkMGyVQswpgZbJseCER/5xU1Vjm7UNL+tR5AbOABDX/4wi+5670gqmEumny6CvZTxIVbLmjg==",\n "SigningCertURL" : "https://invalid.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}' 17 | 18 | self.headers = { 19 | 'Content-Type': 'text/plain; charset=UTF-8', 20 | 'Accept-Encoding': 'gzip,deflate', 21 | 'Host': 'test.me', 22 | 'X-Amz-Sns-Message-Id': '15e7412b-e9ac-5f6a-b6df-0c909df567a0', 23 | 'Connection': 'Keep-Alive', 24 | 'User-Agent': 'Amazon Simple Notification Service Agent', 25 | 'X-Amz-Sns-Message-Type': 'Notification', 26 | 'X-Amz-Sns-Topic-Arn': 'arn:aws:sns:us-east-1:291180941288:A3BXB0YN3XH17HAQR8184NJXADU', 27 | 'Content-Length': '100', 28 | 'X-Amz-Sns-Subscription-Arn': 'arn:aws:sns:us-east-1:291180941288:A3BXB0YN3XH17HAQR8184NJXADU:6cab6de5-c2c7-4ef0-9d4f-d6a5db8b1636'} 29 | 30 | self.ipn_handler = IpnHandler( 31 | body=self.body_valid, 32 | headers=self.headers) 33 | 34 | def test_validate_header(self): 35 | self.assertTrue(self.ipn_handler._validate_header()) 36 | 37 | with self.assertRaises(ValueError): 38 | ipn_handler = IpnHandler( 39 | body=self.body_invalid, 40 | headers={'test': 'test'}) 41 | ipn_handler._validate_header() 42 | 43 | with self.assertRaises(ValueError): 44 | ipn_handler = IpnHandler( 45 | body=self.body_valid, 46 | headers={'X-Amz-Sns-Topic-Arn': 'invalid'}) 47 | ipn_handler._validate_header() 48 | 49 | def test_validate_cert_url(self): 50 | with self.assertRaises(ValueError): 51 | ipn_handler = IpnHandler( 52 | body=self.body_invalid, 53 | headers={'test': 'test'}) 54 | ipn_handler._validate_cert_url() 55 | 56 | # https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem 57 | good = 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\\":\\"\\\\n \\\\n \\\\n P01-0000000-0000000-000000<\\\\/AmazonOrderReferenceId>\\\\n \\\\n 0.0<\\\\/Amount>\\\\n USD<\\\\/CurrencyCode>\\\\n <\\\\/OrderTotal>\\\\n \\\\n \\\\n Closed<\\\\/State> \\\\n 2013-01-01T01:01:01.001Z<\\\\/LastUpdateTimestamp>\\\\n AmazonClosed<\\\\/ReasonCode>\\\\n <\\\\/OrderReferenceStatus>\\\\n 2013-01-01T01:01:01.001Z<\\\\/CreationTimestamp> \\\\n 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}' 58 | ipn_handler = IpnHandler( 59 | body=good, 60 | headers={'test': 'test'}) 61 | self.assertTrue(ipn_handler._validate_cert_url()) 62 | 63 | # http://invalid.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem 64 | bad = b'{\n "Type" : "Notification",\n "MessageId" : "28908206-3478-5398-bcf7-cfbd41c2a223",\n "TopicArn" : "invalid",\n "Message" : "{\\"NotificationReferenceId\\":\\"1111111-1111-11111-1111-11111EXAMPLE\\",\\"MarketplaceID\\":\\"A3BXB0YN3XH17H\\",\\"NotificationType\\":\\"OrderReferenceNotification\\",\\"IsSample\\":true,\\"SellerId\\":\\"AQR8184NJXADU\\",\\"ReleaseEnvironment\\":\\"Sandbox\\",\\"Version\\":\\"2013-01-01\\",\\"NotificationData\\":\\"\\\\n \\\\n \\\\n P01-0000000-0000000-000000<\\\\/AmazonOrderReferenceId>\\\\n \\\\n 0.0<\\\\/Amount>\\\\n USD<\\\\/CurrencyCode>\\\\n <\\\\/OrderTotal>\\\\n \\\\n \\\\n Closed<\\\\/State> \\\\n 2013-01-01T01:01:01.001Z<\\\\/LastUpdateTimestamp>\\\\n AmazonClosed<\\\\/ReasonCode>\\\\n <\\\\/OrderReferenceStatus>\\\\n 2013-01-01T01:01:01.001Z<\\\\/CreationTimestamp> \\\\n 2013-01-01T01:01:01.001Z<\\\\/ExpirationTimestamp>\\\\n <\\\\/OrderReference>\\\\n <\\\\/OrderReferenceNotification>\\",\\"Timestamp\\":\\"2015-04-30T00:12:42.805Z\\"}",\n "Timestamp" : "2015-04-30T00:12:42.885Z",\n "SignatureVersion" : "1",\n "Signature" : "ZChg+1FlUr8OUfu9kd7B2wzT7G1Z0BWf2mH3MH5MtDqhI4t9j5lvG9YqC20LSXV+x3ajvnEmyt2YO635KIAA+Ig4IKeCgnm/YJNjxqtdaOS01M4+3vw9zaeKPY3FlTBgG3T+J3+K3SLARIeblVJhabA0TXVatqtFbMwV81xxKnLxqE5Ik8MZSBAQdHFm6u2lNIruluQakL1mmDUm/2Szj+DkMFrjsQce7fcbkr5TCJ0YB5oYAtkG2MKODYEXYAAlpUe3G0qtBT8WyOVkMGyVQswpgZbJseCER/5xU1Vjm7UNL+tR5AbOABDX/4wi+5670gqmEumny6CvZTxIVbLmjg==",\n "SigningCertURL" : "http://invalid.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}' 65 | ipn_handler = IpnHandler( 66 | body=bad, 67 | headers={'test': 'test'}) 68 | with self.assertRaises(ValueError): 69 | ipn_handler._validate_cert_url() 70 | 71 | # https://https://sns.foo.amazon.com/large.file?file=ddos.pem 72 | bad = b'{\n "Type" : "Notification",\n "MessageId" : "28908206-3478-5398-bcf7-cfbd41c2a223",\n "TopicArn" : "invalid",\n "Message" : "{\\"NotificationReferenceId\\":\\"1111111-1111-11111-1111-11111EXAMPLE\\",\\"MarketplaceID\\":\\"A3BXB0YN3XH17H\\",\\"NotificationType\\":\\"OrderReferenceNotification\\",\\"IsSample\\":true,\\"SellerId\\":\\"AQR8184NJXADU\\",\\"ReleaseEnvironment\\":\\"Sandbox\\",\\"Version\\":\\"2013-01-01\\",\\"NotificationData\\":\\"\\\\n \\\\n \\\\n P01-0000000-0000000-000000<\\\\/AmazonOrderReferenceId>\\\\n \\\\n 0.0<\\\\/Amount>\\\\n USD<\\\\/CurrencyCode>\\\\n <\\\\/OrderTotal>\\\\n \\\\n \\\\n Closed<\\\\/State> \\\\n 2013-01-01T01:01:01.001Z<\\\\/LastUpdateTimestamp>\\\\n AmazonClosed<\\\\/ReasonCode>\\\\n <\\\\/OrderReferenceStatus>\\\\n 2013-01-01T01:01:01.001Z<\\\\/CreationTimestamp> \\\\n 2013-01-01T01:01:01.001Z<\\\\/ExpirationTimestamp>\\\\n <\\\\/OrderReference>\\\\n <\\\\/OrderReferenceNotification>\\",\\"Timestamp\\":\\"2015-04-30T00:12:42.805Z\\"}",\n "Timestamp" : "2015-04-30T00:12:42.885Z",\n "SignatureVersion" : "1",\n "Signature" : "ZChg+1FlUr8OUfu9kd7B2wzT7G1Z0BWf2mH3MH5MtDqhI4t9j5lvG9YqC20LSXV+x3ajvnEmyt2YO635KIAA+Ig4IKeCgnm/YJNjxqtdaOS01M4+3vw9zaeKPY3FlTBgG3T+J3+K3SLARIeblVJhabA0TXVatqtFbMwV81xxKnLxqE5Ik8MZSBAQdHFm6u2lNIruluQakL1mmDUm/2Szj+DkMFrjsQce7fcbkr5TCJ0YB5oYAtkG2MKODYEXYAAlpUe3G0qtBT8WyOVkMGyVQswpgZbJseCER/5xU1Vjm7UNL+tR5AbOABDX/4wi+5670gqmEumny6CvZTxIVbLmjg==",\n "SigningCertURL" : "https://sns.foo.amazon.com/large.file?file=ddos.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}' 73 | ipn_handler = IpnHandler( 74 | body=bad, 75 | headers={'test': 'test'}) 76 | with self.assertRaises(ValueError): 77 | ipn_handler._validate_cert_url() 78 | 79 | def test_validate_signature(self): 80 | self.ipn_handler._pem = self.pem 81 | self.ipn_handler._validate_signature() 82 | 83 | with self.assertRaises(ValueError): 84 | ipn_handler = IpnHandler( 85 | body=self.body_invalid, 86 | headers=self.headers) 87 | ipn_handler._pem = self.pem 88 | ipn_handler._validate_signature() 89 | -------------------------------------------------------------------------------- /test/test_lwa.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import Mock, patch 3 | from amazon_pay.login_with_amazon import LoginWithAmazon 4 | 5 | 6 | class LoginWithAmazonClientTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.maxDiff = None 10 | 11 | self.lwa_client = LoginWithAmazon( 12 | client_id='client_id', 13 | region='na', 14 | sandbox=True) 15 | 16 | def mock_get_error(self, url, headers, params, verify): 17 | mock_response = Mock() 18 | mock_response.json.return_value = { 19 | "error": "test error", 20 | "error_description": "This is a test error"} 21 | mock_response.status_code = 200 22 | return mock_response 23 | 24 | def mock_get_error_aud(self, url, headers, params, verify): 25 | mock_response = Mock() 26 | mock_response.json.return_value = {"test": "aud not present"} 27 | mock_response.status_code = 200 28 | return mock_response 29 | 30 | def mock_get_success(self, url, headers, params, verify): 31 | mock_response = Mock() 32 | mock_response.json.return_value = {"aud": "client_id"} 33 | mock_response.status_code = 200 34 | return mock_response 35 | 36 | @patch('requests.get') 37 | def test_get_login_profile_error(self, mock_urlopen): 38 | mock_urlopen.side_effect = self.mock_get_error 39 | with self.assertRaises(ValueError): 40 | self.lwa_client.get_login_profile(access_token='access_token') 41 | 42 | @patch('requests.get') 43 | def test_get_login_profile_error_aud(self, mock_urlopen): 44 | mock_urlopen.side_effect = self.mock_get_error_aud 45 | with self.assertRaises(ValueError): 46 | self.lwa_client.get_login_profile(access_token='access_token') 47 | 48 | @patch('requests.get') 49 | def test_get_login_profile_success(self, mock_urlopen): 50 | mock_urlopen.side_effect = self.mock_get_success 51 | res = self.lwa_client.get_login_profile( 52 | access_token='access_token') 53 | print(res) 54 | 55 | def test_invalid_region(self): 56 | with self.assertRaises(KeyError): 57 | LoginWithAmazon(client_id='test', region='xx', sandbox=True) 58 | --------------------------------------------------------------------------------