diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee28272 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.DS_Store + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6119cfd --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,51 @@ +Apache License +Version 2.0, January 2004 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and + 2. You must cause any modified files to carry prominent notices stating that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..b3e2446 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,12 @@ +*-*-**-***-*****-********-************* +Login and Pay with Amazon PHP Library +Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +*-*-**-***-*****-********-************* + +You may not use this file except in compliance with the License. You may obtain a copy of the License at: +http://aws.amazon.com/apache2.0 + +This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/PayWithAmazon/Client.php b/PayWithAmazon/Client.php new file mode 100644 index 0000000..5fb9dd8 --- /dev/null +++ b/PayWithAmazon/Client.php @@ -0,0 +1,1607 @@ + null, + 'secret_key' => null, + 'access_key' => null, + 'region' => null, + 'currency_code' => null, + 'sandbox' => false, + 'platform_id' => null, + 'cabundle_file' => null, + 'application_name' => null, + 'application_version' => null, + 'proxy_host' => null, + 'proxy_port' => -1, + 'proxy_username' => null, + 'proxy_password' => null, + 'client_id' => null, + 'handle_throttle' => true + ); + + private $modePath = null; + + // Final URL to where the API parameters POST done, based off the config['region'] and respective $mwsServiceUrls + private $mwsServiceUrl = null; + private $mwsServiceUrls; + private $profileEndpointUrls; + private $regionMappings; + + // Boolean variable to check if the API call was a success + public $success = false; + + /* Takes user configuration array from the user as input + * Takes JSON file path with configuration information as input + * Validates the user configuration array against existing config array + */ + + public function __construct($config = null) + { + $this->getRegionUrls(); + if (!is_null($config)) { + + if (is_array($config)) { + $configArray = $config; + } elseif (!is_array($config)) { + $configArray = $this->checkIfFileExists($config); + } + + if (is_array($configArray)) { + $this->checkConfigKeys($configArray); + } else { + throw new \Exception('$config is of the incorrect type ' . gettype($configArray) . ' and should be of the type array'); + } + } else { + throw new \Exception('$config cannot be null.'); + } + } + + /* Get the Region specific properties from the Regions class.*/ + + private function getRegionUrls() + { + $regionObject = new Regions(); + $this->mwsServiceUrls = $regionObject->mwsServiceUrls; + $this->regionMappings = $regionObject->regionMappings; + $this->profileEndpointUrls = $regionObject->profileEndpointUrls; + } + + /* checkIfFileExists - check if the JSON file exists in the path provided */ + + private function checkIfFileExists($config) + { + if(file_exists($config)) + { + $jsonString = file_get_contents($config); + $configArray = json_decode($jsonString, true); + + $jsonError = json_last_error(); + + if ($jsonError != 0) { + $errorMsg = "Error with message - content is not in json format" . $this->getErrorMessageForJsonError($jsonError) . " " . $configArray; + throw new \Exception($errorMsg); + } + } else { + $errorMsg ='$config is not a Json File path or the Json File was not found in the path provided'; + throw new \Exception($errorMsg); + } + return $configArray; + } + + /* Checks if the keys of the input configuration matches the keys in the config array + * if they match the values are taken else throws exception + * strict case match is not performed + */ + + private function checkConfigKeys($config) + { + $config = array_change_key_case($config, CASE_LOWER); + $config = $this->trimArray($config); + + foreach ($config as $key => $value) { + if (array_key_exists($key, $this->config)) { + $this->config[$key] = $value; + } else { + throw new \Exception('Key ' . $key . ' is either not part of the configuration or has incorrect Key name. + check the config array key names to match your key names of your config array', 1); + } + } + } + + /* Convert a json error code to a descriptive error message + * + * @param int $jsonError message code + * + * @return string error message + */ + + private function getErrorMessageForJsonError($jsonError) + { + switch ($jsonError) { + case JSON_ERROR_DEPTH: + return " - maximum stack depth exceeded."; + break; + case JSON_ERROR_STATE_MISMATCH: + return " - invalid or malformed JSON."; + break; + case JSON_ERROR_CTRL_CHAR: + return " - control character error."; + break; + case JSON_ERROR_SYNTAX: + return " - syntax error."; + break; + default: + return "."; + break; + } + } + + /* Setter for sandbox + * Sets the Boolean value for config['sandbox'] variable + */ + + public function setSandbox($value) + { + if (is_bool($value)) { + $this->config['sandbox'] = $value; + } else { + throw new \Exception($value . ' is of type ' . gettype($value) . ' and should be a boolean value'); + } + } + + /* Setter for config['client_id'] + * Sets the value for config['client_id'] variable + */ + + public function setClientId($value) + { + if (!empty($value)) { + $this->config['client_id'] = $value; + } else { + throw new \Exception('setter value for client ID provided is empty'); + } + } + + /* Setter for Proxy + * input $proxy [array] + * @param $proxy['proxy_user_host'] - hostname for the proxy + * @param $proxy['proxy_user_port'] - hostname for the proxy + * @param $proxy['proxy_user_name'] - if your proxy required a username + * @param $proxy['proxy_user_password'] - if your proxy required a password + */ + + public function setProxy($proxy) + { + if (!empty($proxy['proxy_user_host'])) + $this->config['proxy_host'] = $proxy['proxy_user_host']; + + if (!empty($proxy['proxy_user_port'])) + $this->config['proxy_port'] = $proxy['proxy_user_port']; + + if (!empty($proxy['proxy_user_name'])) + $this->config['proxy_username'] = $proxy['proxy_user_name']; + + if (!empty($proxy['proxy_user_password'])) + $this->config['proxy_password'] = $proxy['proxy_user_password']; + } + + /* Setter for $mwsServiceUrl + * Set the URL to which the post request has to be made for unit testing + */ + + public function setMwsServiceUrl($url) + { + $this->mwsServiceUrl = $url; + } + + /* Getter + * Gets the value for the key if the key exists in config + */ + + public function __get($name) + { + if (array_key_exists(strtolower($name), $this->config)) { + return $this->config[strtolower($name)]; + } else { + throw new \Exception('Key ' . $name . ' is either not a part of the configuration array config or the' . $name . 'does not match the key name in the config array', 1); + } + } + + /* Getter for parameters string + * Gets the value for the parameters string for unit testing + */ + + public function getParameters() + { + return trim($this->parameters); + } + + /* Trim the input Array key values */ + + private function trimArray($array) + { + foreach ($array as $key => $value) + { + if(!is_array($value) && $key!=='proxy_password') + { + $array[$key] = trim($value); + } + } + return $array; + } + + /* GetUserInfo convenience function - Returns user's profile information from Amazon using the access token returned by the Button widget. + * + * @see http://login.amazon.com/website Step 4 + * @param $accessToken [String] + */ + + public function getUserInfo($accessToken) + { + // Get the correct Profile Endpoint URL based off the country/region provided in the config['region'] + $this->profileEndpointUrl(); + + if (empty($accessToken)) { + throw new \InvalidArgumentException('Access Token is a required parameter and is not set'); + } + + // To make sure double encoding doesn't occur decode first and encode again. + $accessToken = urldecode($accessToken); + $url = $this->profileEndpoint . '/auth/o2/tokeninfo?access_token=' . urlEncode($accessToken); + + $httpCurlRequest = new HttpCurl($this->config); + + $response = $httpCurlRequest->httpGet($url); + $data = json_decode($response); + + if ($data->aud != $this->config['client_id']) { + // The access token does not belong to us + throw new \Exception('The Access token entered is incorrect'); + } + + // Exchange the access token for user profile + $url = $this->profileEndpoint . '/user/profile'; + $httpCurlRequest = new HttpCurl($this->config); + + $httpCurlRequest->setAccessToken($accessToken); + $httpCurlRequest->setHttpHeader(true); + $response = $httpCurlRequest->httpGet($url); + + $userInfo = json_decode($response, true); + return $userInfo; + } + + /* setParametersAndPost - sets the parameters array with non empty values from the requestParameters array sent to API calls. + * If Provider Credit Details is present, values are set by setProviderCreditDetails + * If Provider Credit Reversal Details is present, values are set by setProviderCreditDetails + */ + + private function setParametersAndPost($parameters, $fieldMappings, $requestParameters) + { + /* For loop to take all the non empty parameters in the $requestParameters and add it into the $parameters array, + * if the keys are matched from $requestParameters array with the $fieldMappings array + */ + foreach ($requestParameters as $param => $value) { + + if(!is_array($value)) { + $value = trim($value); + } + + if (array_key_exists($param, $fieldMappings) && $value!='') { + + if(is_array($value)) { + // If the parameter is a provider_credit_details or provider_credit_reversal_details, call the respective functions to set the values + if($param === 'provider_credit_details') { + $parameters = $this->setProviderCreditDetails($parameters,$value); + } elseif ($param === 'provider_credit_reversal_details') { + $parameters = $this->setProviderCreditReversalDetails($parameters,$value); + } + + } else{ + // For variables that are boolean values, strtolower them + if($this->checkIfBool($value)) + { + $value = strtolower($value); + } + + $parameters[$fieldMappings[$param]] = $value; + } + } + } + + $parameters = $this->setDefaultValues($parameters, $fieldMappings, $requestParameters); + $responseObject = $this->calculateSignatureAndPost($parameters); + + return $responseObject; + } + + /* checkIfBool - checks if the input is a boolean */ + + private function checkIfBool($string) + { + $string = strtolower($string); + return in_array($string, array('true', 'false')); + } + + /* calculateSignatureAndPost - convert the Parameters array to string and curl POST the parameters to MWS */ + + private function calculateSignatureAndPost($parameters) + { + // Call the signature and Post function to perform the actions. Returns XML in array format + $parametersString = $this->calculateSignatureAndParametersToString($parameters); + + // POST using curl the String converted Parameters + $response = $this->invokePost($parametersString); + + // Send this response as args to ResponseParser class which will return the object of the class. + $responseObject = new ResponseParser($response); + return $responseObject; + } + + /* If merchant_id is not set via the requestParameters array then it's taken from the config array + * + * Set the platform_id if set in the config['platform_id'] array + * + * If currency_code is set in the $requestParameters and it exists in the $fieldMappings array, strtoupper it + * else take the value from config array if set + */ + + private function setDefaultValues($parameters, $fieldMappings, $requestParameters) + { + if (empty($requestParameters['merchant_id'])) + $parameters['SellerId'] = $this->config['merchant_id']; + + if (array_key_exists('platform_id', $fieldMappings)) { + if (empty($requestParameters['platform_id']) && !empty($this->config['platform_id'])) + $parameters[$fieldMappings['platform_id']] = $this->config['platform_id']; + } + + if (array_key_exists('currency_code', $fieldMappings)) { + if (!empty($requestParameters['currency_code'])) { + $parameters[$fieldMappings['currency_code']] = strtoupper($requestParameters['currency_code']); + } else { + $parameters[$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /* setProviderCreditDetails - sets the provider credit details sent via the Capture or Authorize API calls + * @param provider_id - [String] + * @param credit_amount - [String] + * @optional currency_code - [String] + */ + + private function setProviderCreditDetails($parameters, $providerCreditInfo) + { + $providerIndex = 0; + $providerString = 'ProviderCreditList.member.'; + + $fieldMappings = array( + 'provider_id' => 'ProviderId', + 'credit_amount' => 'CreditAmount.Amount', + 'currency_code' => 'CreditAmount.CurrencyCode' + ); + + foreach ($providerCreditInfo as $key => $value) + { + $value = array_change_key_case($value, CASE_LOWER); + $providerIndex = $providerIndex + 1; + + foreach ($value as $param => $val) + { + if (array_key_exists($param, $fieldMappings) && trim($val)!='') { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings[$param]] = $val; + } + } + + // If currency code is not entered take it from the config array + if(empty($parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']])) + { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /* setProviderCreditReversalDetails - sets the reverse provider credit details sent via the Refund API call. + * @param provider_id - [String] + * @param credit_amount - [String] + * @optional currency_code - [String] + */ + + private function setProviderCreditReversalDetails($parameters, $providerCreditInfo) + { + $providerIndex = 0; + $providerString = 'ProviderCreditReversalList.member.'; + + $fieldMappings = array( + 'provider_id' => 'ProviderId', + 'credit_reversal_amount' => 'CreditReversalAmount.Amount', + 'currency_code' => 'CreditReversalAmount.CurrencyCode' + ); + + foreach ($providerCreditInfo as $key => $value) + { + $value = array_change_key_case($value, CASE_LOWER); + $providerIndex = $providerIndex + 1; + + foreach ($value as $param => $val) + { + if (array_key_exists($param, $fieldMappings) && trim($val)!='') { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings[$param]] = $val; + } + } + + // If currency code is not entered take it from the config array + if(empty($parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']])) + { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /* GetOrderReferenceDetails API call - Returns details about the Order Reference object and its current state. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetOrderReferenceDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['address_consent_token'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getOrderReferenceDetails($requestParameters = array()) + { + + $parameters['Action'] = 'GetOrderReferenceDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'address_consent_token' => 'AddressConsentToken', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + return ($responseObject); + } + + /* SetOrderReferenceDetails API call - Sets order reference details such as the order total and a description for the order. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_SetOrderReferenceDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @param requestParameters['amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function setOrderReferenceDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'SetOrderReferenceDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ConfirmOrderReferenceDetails API call - Confirms that the order reference is free of constraints and all required information has been set on the order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_ConfirmOrderReference.html + + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function confirmOrderReference($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ConfirmOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CancelOrderReferenceDetails API call - Cancels a previously confirmed order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CancelOrderReference.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['cancelation_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function cancelOrderReference($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CancelOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'cancelation_reason' => 'CancelationReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CloseOrderReferenceDetails API call - Confirms that an order reference has been fulfilled (fully or partially) + * and that you do not expect to create any new authorizations on this order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CloseOrderReference.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function closeOrderReference($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CloseOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CloseAuthorization API call - Closes an authorization. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CloseOrderReference.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] - [String] + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function closeAuthorization($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CloseAuthorization'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Authorize API call - Reserves a specified amount against the payment method(s) stored in the order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_Authorize.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @param requestParameters['authorization_amount'] [String] + * @param requestParameters['currency_code'] - [String] + * @param requestParameters['authorization_reference_id'] [String] + * @optional requestParameters['capture_now'] [String] + * @optional requestParameters['provider_credit_details'] - [array (array())] + * @optional requestParameters['seller_authorization_note'] [String] + * @optional requestParameters['transaction_timeout'] [String] - Defaults to 1440 minutes + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function authorize($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'Authorize'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'capture_now' => 'CaptureNow', + 'provider_credit_details' => array(), + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetAuthorizationDetails API call - Returns the status of a particular authorization and the total amount captured on the authorization. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetAuthorizationDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getAuthorizationDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetAuthorizationDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Capture API call - Captures funds from an authorized payment instrument. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_Capture.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] - [String] + * @param requestParameters['capture_amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @param requestParameters['capture_reference_id'] - [String] + * @optional requestParameters['provider_credit_details'] - [array (array())] + * @optional requestParameters['seller_capture_note'] - [String] + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function capture($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'Capture'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'capture_amount' => 'CaptureAmount.Amount', + 'currency_code' => 'CaptureAmount.CurrencyCode', + 'capture_reference_id' => 'CaptureReferenceId', + 'provider_credit_details' => array(), + 'seller_capture_note' => 'SellerCaptureNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetCaptureDetails API call - Returns the status of a particular capture and the total amount refunded on the capture. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetCaptureDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_capture_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getCaptureDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetCaptureDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Refund API call - Refunds a previously captured amount. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_Refund.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_capture_id'] - [String] + * @param requestParameters['refund_reference_id'] - [String] + * @param requestParameters['refund_amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['provider_credit_reversal_details'] - [array(array())] + * @optional requestParameters['seller_refund_note'] [String] + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function refund($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'Refund'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'refund_reference_id' => 'RefundReferenceId', + 'refund_amount' => 'RefundAmount.Amount', + 'currency_code' => 'RefundAmount.CurrencyCode', + 'provider_credit_reversal_details' => array(), + 'seller_refund_note' => 'SellerRefundNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetRefundDetails API call - Returns the status of a particular refund. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetRefundDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_refund_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getRefundDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetRefundDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_refund_id' => 'AmazonRefundId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetServiceStatus API Call - Returns the operational status of the Off-Amazon Payments API section + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetServiceStatus.html + * + * The GetServiceStatus operation returns the operational status of the Off-Amazon Payments API + * section of Amazon Marketplace Web Service (Amazon MWS). + * Status values are GREEN, GREEN_I, YELLOW, and RED. + * + * @param requestParameters['merchant_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getServiceStatus($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetServiceStatus'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CreateOrderReferenceForId API Call - Creates an order reference for the given object + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CreateOrderReferenceForId.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['Id'] - [String] + * @optional requestParameters['inherit_shipping_address'] [Boolean] + * @optional requestParameters['ConfirmNow'] - [Boolean] + * @optional Amount (required when confirm_now is set to true) [String] + * @optional requestParameters['currency_code'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function createOrderReferenceForId($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CreateOrderReferenceForId'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'id' => 'Id', + 'id_type' => 'IdType', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'confirm_now' => 'ConfirmNow', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetBillingAgreementDetails API Call - Returns details about the Billing Agreement object and its current state. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetBillingAgreementDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getBillingAgreementDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetBillingAgreementDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'address_consent_token' => 'AddressConsentToken', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* SetBillingAgreementDetails API call - Sets Billing Agreement details such as a description of the agreement and other information about the seller. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_SetBillingAgreementDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @param requestParameters['amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_billing_agreement_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function setBillingAgreementDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'SetBillingAgreementDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'platform_id' => 'BillingAgreementAttributes.PlatformId', + 'seller_note' => 'BillingAgreementAttributes.SellerNote', + 'seller_billing_agreement_id' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.SellerBillingAgreementId', + 'custom_information' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.CustomInformation', + 'store_name' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.StoreName', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ConfirmBillingAgreement API Call - Confirms that the Billing Agreement is free of constraints and all required information has been set on the Billing Agreement. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_ConfirmBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function confirmBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ConfirmBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ValidateBillignAgreement API Call - Validates the status of the Billing Agreement object and the payment method associated with it. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_ValidateBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function validateBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ValidateBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* AuthorizeOnBillingAgreement API call - Reserves a specified amount against the payment method(s) stored in the Billing Agreement. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_AuthorizeOnBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @param requestParameters['authorization_reference_id'] [String] + * @param requestParameters['authorization_amount'] [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['seller_authorization_note'] [String] + * @optional requestParameters['transaction_timeout'] - Defaults to 1440 minutes + * @optional requestParameters['capture_now'] [String] + * @optional requestParameters['soft_descriptor'] - - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['inherit_shipping_address'] [Boolean] - Defaults to true + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function authorizeOnBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'AuthorizeOnBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'capture_now' => 'CaptureNow', + 'soft_descriptor' => 'SoftDescriptor', + 'seller_note' => 'SellerNote', + 'platform_id' => 'PlatformId', + 'custom_information' => 'SellerOrderAttributes.CustomInformation', + 'seller_order_id' => 'SellerOrderAttributes.SellerOrderId', + 'store_name' => 'SellerOrderAttributes.StoreName', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CloseBillingAgreement API Call - Returns details about the Billing Agreement object and its current state. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CloseBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function closeBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CloseBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* charge convenience method + * Performs the API calls + * 1. SetOrderReferenceDetails / SetBillingAgreementDetails + * 2. ConfirmOrderReference / ConfirmBillingAgreement + * 3. Authorize (with Capture) / AuthorizeOnBillingAgreeemnt (with Capture) + * + * @param requestParameters['merchant_id'] - [String] + * + * @param requestParameters['amazon_reference_id'] - [String] : Order Reference ID /Billing Agreement ID + * If requestParameters['amazon_reference_id'] is empty then the following is required, + * @param requestParameters['amazon_order_reference_id'] - [String] : Order Reference ID + * or, + * @param requestParameters['amazon_billing_agreement_id'] - [String] : Billing Agreement ID + * + * @param $requestParameters['charge_amount'] - [String] : Amount value to be captured + * @param requestParameters['currency_code'] - [String] : Currency Code for the Amount + * @param requestParameters['authorization_reference_id'] - [String]- Any unique string that needs to be passed + * @optional requestParameters['charge_note'] - [String] : Seller Note sent to the buyer + * @optional requestParameters['transaction_timeout'] - [String] : Defaults to 1440 minutes + * @optional requestParameters['charge_order_id'] - [String] : Custom Order ID provided + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function charge($requestParameters = array()) { + + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + $requestParameters= $this->trimArray($requestParameters); + + $setParameters = $authorizeParameters = $confirmParameters = $requestParameters; + + $chargeType = ''; + + if (!empty($requestParameters['amazon_order_reference_id'])) + { + $chargeType = 'OrderReference'; + + } elseif(!empty($requestParameters['amazon_billing_agreement_id'])) { + $chargeType = 'BillingAgreement'; + + } elseif (!empty($requestParameters['amazon_reference_id'])) { + switch (substr(strtoupper($requestParameters['amazon_reference_id']), 0, 1)) { + case 'P': + case 'S': + $chargeType = 'OrderReference'; + $setParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + $authorizeParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + $confirmParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + break; + case 'B': + case 'C': + $chargeType = 'BillingAgreement'; + $setParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + $authorizeParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + $confirmParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + break; + default: + throw new \Exception('Invalid Amazon Reference ID'); + } + } else { + throw new \Exception('key amazon_order_reference_id or amazon_billing_agreement_id is null and is a required parameter'); + } + + // Set the other parameters if the values are present + $setParameters['amount'] = !empty($requestParameters['charge_amount']) ? $requestParameters['charge_amount'] : ''; + $authorizeParameters['authorization_amount'] = !empty($requestParameters['charge_amount']) ? $requestParameters['charge_amount'] : ''; + + $setParameters['seller_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + $authorizeParameters['seller_authorization_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + $authorizeParameters['seller_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + + $setParameters['seller_order_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + $setParameters['seller_billing_agreement_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + $authorizeParameters['seller_order_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + + $authorizeParameters['capture_now'] = !empty($requestParameters['capture_now']) ? $requestParameters['capture_now'] : false; + + $response = $this->makeChargeCalls($chargeType, $setParameters, $confirmParameters, $authorizeParameters); + return $response; + } + + /* makeChargeCalls - makes API calls based off the charge type (OrderReference or BillingAgreement) */ + + private function makeChargeCalls($chargeType, $setParameters, $confirmParameters, $authorizeParameters) + { + switch ($chargeType) { + + case 'OrderReference': + + // Get the Order Reference details and feed the response object to the ResponseParser + $responseObj = $this->getOrderReferenceDetails($setParameters); + + // Call the function getOrderReferenceDetailsStatus in ResponseParser.php providing it the XML response + // $oroStatus is an array containing the State of the Order Reference ID + $oroStatus = $responseObj->getOrderReferenceDetailsStatus($responseObj->toXml()); + + if ($oroStatus['State'] === 'Draft') { + $response = $this->setOrderReferenceDetails($setParameters); + if ($this->success) { + $this->confirmOrderReference($confirmParameters); + } + } + + $responseObj = $this->getOrderReferenceDetails($setParameters); + + // Check the Order Reference Status again before making the Authorization. + $oroStatus = $responseObj->getOrderReferenceDetailsStatus($responseObj->toXml()); + + if ($oroStatus['State'] === 'Open') { + if ($this->success) { + $response = $this->Authorize($authorizeParameters); + } + } + if ($oroStatus['State'] != 'Open' && $oroStatus['State'] != 'Draft') { + throw new \Exception('The Order Reference is in the ' . $oroStatus['State'] . " State. It should be in the Draft or Open State"); + } + + return $response; + + case 'BillingAgreement': + + // Get the Billing Agreement details and feed the response object to the ResponseParser + + $responseObj = $this->getBillingAgreementDetails($setParameters); + + // Call the function getBillingAgreementDetailsStatus in ResponseParser.php providing it the XML response + // $baStatus is an array containing the State of the Billing Agreement + $baStatus = $responseObj->getBillingAgreementDetailsStatus($responseObj->toXml()); + + if ($baStatus['State'] === 'Draft') { + $response = $this->setBillingAgreementDetails($setParameters); + if ($this->success) { + $response = $this->confirmBillingAgreement($confirmParameters); + } + } + + // Check the Billing Agreement status again before making the Authorization. + $responseObj = $this->getBillingAgreementDetails($setParameters); + $baStatus = $responseObj->getBillingAgreementDetailsStatus($responseObj->toXml()); + + if ($this->success && $baStatus['State'] === 'Open') { + $response = $this->authorizeOnBillingAgreement($authorizeParameters); + } + + if($baStatus['State'] != 'Open' && $baStatus['State'] != 'Draft') { + throw new \Exception('The Billing Agreement is in the ' . $baStatus['State'] . " State. It should be in the Draft or Open State"); + } + + return $response; + } + } + + /* GetProviderCreditDetails API Call - Get the details of the Provider Credit. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getProviderCreditDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetProviderCreditDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_id' => 'AmazonProviderCreditId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetProviderCreditReversalDetails API Call - Get details of the Provider Credit Reversal. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_reversal_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getProviderCreditReversalDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetProviderCreditReversalDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_reversal_id' => 'AmazonProviderCreditReversalId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ReverseProviderCredit API Call - Reverse the Provider Credit. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_id'] - [String] + * @optional requestParameters['credit_reversal_reference_id'] - [String] + * @param requestParameters['credit_reversal_amount'] - [String] + * @optional requestParameters['currency_code'] - [String] + * @optional requestParameters['credit_reversal_note'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function reverseProviderCredit($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ReverseProviderCredit'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_id' => 'AmazonProviderCreditId', + 'credit_reversal_reference_id' => 'CreditReversalReferenceId', + 'credit_reversal_amount' => 'CreditReversalAmount.Amount', + 'currency_code' => 'CreditReversalAmount.CurrencyCode', + 'credit_reversal_note' => 'CreditReversalNote', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Create an Array of required parameters, sort them + * Calculate signature and invoke the POST to the MWS Service URL + * + * @param AWSAccessKeyId [String] + * @param Version [String] + * @param SignatureMethod [String] + * @param Timestamp [String] + * @param Signature [String] + */ + + private function calculateSignatureAndParametersToString($parameters = array()) + { + $parameters['AWSAccessKeyId'] = $this->config['access_key']; + $parameters['Version'] = self::SERVICE_VERSION; + $parameters['SignatureMethod'] = 'HmacSHA256'; + $parameters['SignatureVersion'] = 2; + $parameters['Timestamp'] = $this->getFormattedTimestamp(); + uksort($parameters, 'strcmp'); + + $this->createServiceUrl(); + + $parameters['Signature'] = $this->signParameters($parameters); + $parameters = $this->getParametersAsString($parameters); + + // Save these parameters in the parameters variable so that it can be returned for unit testing. + $this->parameters = $parameters; + return $parameters; + } + + /* Computes RFC 2104-compliant HMAC signature for request parameters + * Implements AWS Signature, as per following spec: + * + * If Signature Version is 0, it signs concatenated Action and Timestamp + * + * If Signature Version is 1, it performs the following: + * + * Sorts all parameters (including SignatureVersion and excluding Signature, + * the value of which is being created), ignoring case. + * + * Iterate over the sorted list and append the parameter name (in original case) + * and then its value. It will not URL-encode the parameter values before + * constructing this string. There are no separators. + * + * If Signature Version is 2, string to sign is based on following: + * + * 1. The HTTP Request Method followed by an ASCII newline (%0A) + * 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline. + * 3. The URL encoded HTTP absolute path component of the URI + * (up to but not including the query string parameters); + * if this is empty use a forward '/'. This parameter is followed by an ASCII newline. + * 4. The concatenation of all query string components (names and values) + * as UTF-8 characters which are URL encoded as per RFC 3986 + * (hex characters MUST be uppercase), sorted using lexicographic byte ordering. + * Parameter names are separated from their values by the '=' character + * (ASCII character 61), even if the value is empty. + * Pairs of parameter and values are separated by the '&' character (ASCII code 38). + * + */ + + private function signParameters(array $parameters) + { + $signatureVersion = $parameters['SignatureVersion']; + $algorithm = "HmacSHA1"; + $stringToSign = null; + if (2 === $signatureVersion) { + $algorithm = "HmacSHA256"; + $parameters['SignatureMethod'] = $algorithm; + $stringToSign = $this->calculateStringToSignV2($parameters); + } else { + throw new \Exception("Invalid Signature Version specified"); + } + + return $this->sign($stringToSign, $algorithm); + } + + /* Calculate String to Sign for SignatureVersion 2 + * @param array $parameters request parameters + * @return String to Sign + */ + + private function calculateStringToSignV2(array $parameters) + { + $data = 'POST'; + $data .= "\n"; + $data .= $this->mwsEndpointUrl; + $data .= "\n"; + $data .= $this->mwsEndpointPath; + $data .= "\n"; + $data .= $this->getParametersAsString($parameters); + return $data; + } + + /* Convert paremeters to Url encoded query string */ + + private function getParametersAsString(array $parameters) + { + $queryParameters = array(); + foreach ($parameters as $key => $value) { + $queryParameters[] = $key . '=' . $this->urlEncode($value); + } + + return implode('&', $queryParameters); + } + + private function urlEncode($value) + { + return str_replace('%7E', '~', rawurlencode($value)); + } + + /* Computes RFC 2104-compliant HMAC signature */ + + private function sign($data, $algorithm) + { + if ($algorithm === 'HmacSHA1') { + $hash = 'sha1'; + } else if ($algorithm === 'HmacSHA256') { + $hash = 'sha256'; + } else { + throw new \Exception("Non-supported signing method specified"); + } + + return base64_encode(hash_hmac($hash, $data, $this->config['secret_key'], true)); + } + + /* Formats date as ISO 8601 timestamp */ + + private function getFormattedTimestamp() + { + return gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time()); + } + + /* invokePost takes the parameters and invokes the httpPost function to POST the parameters + * Exponential retries on error 500 and 503 + * The response from the POST is an XML which is converted to Array + */ + + private function invokePost($parameters) + { + $response = array(); + $statusCode = 200; + $this->success = false; + + // Submit the request and read response body + try { + $shouldRetry = true; + $retries = 0; + do { + try { + $this->constructUserAgentHeader(); + + $httpCurlRequest = new HttpCurl($this->config); + $response = $httpCurlRequest->httpPost($this->mwsServiceUrl, $this->userAgent, $parameters); + $curlResponseInfo = $httpCurlRequest->getCurlResponseInfo(); + + $statusCode = $curlResponseInfo["http_code"]; + + $response = array( + 'Status' => $statusCode, + 'ResponseBody' => $response + ); + + $statusCode = $response['Status']; + + if ($statusCode == 200) { + $shouldRetry = false; + $this->success = true; + } elseif ($statusCode == 500 || $statusCode == 503) { + + $shouldRetry = true; + if ($shouldRetry && strtolower($this->config['handle_throttle'])) { + $this->pauseOnRetry(++$retries, $statusCode); + } + } else { + $shouldRetry = false; + } + } catch (\Exception $e) { + throw $e; + } + } while ($shouldRetry); + } catch (\Exception $se) { + throw $se; + } + + return $response; + } + + /* Exponential sleep on failed request + * @param retries current retry + * @throws Exception if maximum number of retries has been reached + */ + + private function pauseOnRetry($retries, $status) + { + if ($retries <= self::MAX_ERROR_RETRY) { + $delay = (int) (pow(4, $retries) * 100000); + usleep($delay); + } else { + throw new \Exception('Error Code: '. $status.PHP_EOL.'Maximum number of retry attempts - '. $retries .' reached'); + } + } + + /* Create MWS service URL and the Endpoint path */ + + private function createServiceUrl() + { + $this->modePath = strtolower($this->config['sandbox']) ? 'OffAmazonPayments_Sandbox' : 'OffAmazonPayments'; + + if (!empty($this->config['region'])) { + $region = strtolower($this->config['region']); + if (array_key_exists($region, $this->regionMappings)) { + $this->mwsEndpointUrl = $this->mwsServiceUrls[$this->regionMappings[$region]]; + $this->mwsServiceUrl = 'https://' . $this->mwsEndpointUrl . '/' . $this->modePath . '/' . self::SERVICE_VERSION; + $this->mwsEndpointPath = '/' . $this->modePath . '/' . self::SERVICE_VERSION; + } else { + throw new \Exception($region . ' is not a valid region'); + } + } else { + throw new \Exception("config['region'] is a required parameter and is not set"); + } + } + + /* Based on the config['region'] and config['sandbox'] values get the user profile URL */ + + private function profileEndpointUrl() + { + $profileEnvt = strtolower($this->config['sandbox']) ? "api.sandbox" : "api"; + + if (!empty($this->config['region'])) { + $region = strtolower($this->config['region']); + + if (array_key_exists($region, $this->regionMappings) ) { + $this->profileEndpoint = 'https://' . $profileEnvt . '.' . $this->profileEndpointUrls[$region]; + }else{ + throw new \Exception($region . ' is not a valid region'); + } + } else { + throw new \Exception("config['region'] is a required parameter and is not set"); + } + } + + /* Create the User Agent Header sent with the POST request */ + + private function constructUserAgentHeader() + { + $this->userAgent = $this->quoteApplicationName($this->config['application_name']) . '/' . $this->quoteApplicationVersion($this->config['application_version']); + $this->userAgent .= ' ('; + $this->userAgent .= 'Language=PHP/' . phpversion(); + $this->userAgent .= '; '; + $this->userAgent .= 'Platform=' . php_uname('s') . '/' . php_uname('m') . '/' . php_uname('r'); + $this->userAgent .= '; '; + $this->userAgent .= 'MWSClientVersion=' . self::MWS_CLIENT_VERSION; + $this->userAgent .= ')'; + } + + /* Collapse multiple whitespace characters into a single ' ' and backslash escape '\', + * and '/' characters from a string. + * @param $s + * @return string + */ + + private function quoteApplicationName($s) + { + $quotedString = preg_replace('/ {2,}|\s/', ' ', $s); + $quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString); + $quotedString = preg_replace('/\//', '\\/', $quotedString); + return $quotedString; + } + + /* Collapse multiple whitespace characters into a single ' ' and backslash escape '\', + * and '(' characters from a string. + * + * @param $s + * @return string + */ + + private function quoteApplicationVersion($s) + { + $quotedString = preg_replace('/ {2,}|\s/', ' ', $s); + $quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString); + $quotedString = preg_replace('/\\(/', '\\(', $quotedString); + return $quotedString; + } +} diff --git a/PayWithAmazon/HttpCurl.php b/PayWithAmazon/HttpCurl.php new file mode 100644 index 0000000..f8a8077 --- /dev/null +++ b/PayWithAmazon/HttpCurl.php @@ -0,0 +1,138 @@ +config = $config; + } + + /* Setter for boolean header to get the user info */ + + public function setHttpHeader() + { + $this->header = true; + } + + /* Setter for Access token to get the user info */ + + public function setAccessToken($accesstoken) + { + $this->accessToken = $accesstoken; + } + + /* Add the common Curl Parameters to the curl handler $ch + * Also checks for optional parameters if provided in the config + * config['cabundle_file'] + * config['proxy_port'] + * config['proxy_host'] + * config['proxy_username'] + * config['proxy_password'] + */ + + private function commonCurlParams($url,$userAgent) + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_PORT, 443); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + if (!is_null($this->config['cabundle_file'])) { + curl_setopt($ch, CURLOPT_CAINFO, $this->config['cabundle_file']); + } + + if (!empty($userAgent)) + curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); + + if ($this->config['proxy_host'] != null && $this->config['proxy_port'] != -1) { + curl_setopt($ch, CURLOPT_PROXY, $this->config['proxy_host'] . ':' . $this->config['proxy_port']); + } + + if ($this->config['proxy_username'] != null && $this->config['proxy_password'] != null) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config['proxy_username'] . ':' . $this->config['proxy_password']); + } + + return $ch; + } + + /* POST using curl for the following situations + * 1. API calls + * 2. IPN certificate retrieval + * 3. Get User Info + */ + + public function httpPost($url, $userAgent = null, $parameters = null) + { + $ch = $this->commonCurlParams($url,$userAgent); + + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); + curl_setopt($ch, CURLOPT_HEADER, false); + + $response = $this->execute($ch); + return $response; + } + + /* GET using curl for the following situations + * 1. IPN certificate retrieval + * 2. Get User Info + */ + + public function httpGet($url, $userAgent = null) + { + $ch = $this->commonCurlParams($url,$userAgent); + + // Setting the HTTP header with the Access Token only for Getting user info + if ($this->header) { + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Authorization: bearer ' . $this->accessToken + )); + } + + $response = $this->execute($ch); + return $response; + } + + /* Execute Curl request */ + + private function execute($ch) + { + $response = ''; + $response = curl_exec($ch); + if (!$response = curl_exec($ch)) { + $error_msg = "Unable to post request, underlying exception of " . curl_error($ch); + curl_close($ch); + throw new \Exception($error_msg); + } + else{ + $this->curlResponseInfo = curl_getinfo($ch); + } + curl_close($ch); + return $response; + } + + /* Get the output of Curl Getinfo */ + + public function getCurlResponseInfo() + { + return $this->curlResponseInfo; + } +} diff --git a/PayWithAmazon/Interface.php b/PayWithAmazon/Interface.php new file mode 100644 index 0000000..b0b353c --- /dev/null +++ b/PayWithAmazon/Interface.php @@ -0,0 +1,479 @@ +_response [XML] + */ + + public function toArray(); + + /* Get the status of the BillingAgreement */ + + public function getBillingAgreementDetailsStatus($response); +} diff --git a/PayWithAmazon/IpnHandler.php b/PayWithAmazon/IpnHandler.php new file mode 100644 index 0000000..ac53403 --- /dev/null +++ b/PayWithAmazon/IpnHandler.php @@ -0,0 +1,416 @@ + null, + 'proxy_host' => null, + 'proxy_port' => -1, + 'proxy_username' => null, + 'proxy_password' => null); + + + public function __construct($headers, $body, $ipnConfig = null) + { + $this->headers = array_change_key_case($headers, CASE_LOWER); + $this->body = $body; + + if ($ipnConfig != null) { + $this->checkConfigKeys($ipnConfig); + } + + // Get the list of fields that we are interested in + $this->fields = array( + "Timestamp" => true, + "Message" => true, + "MessageId" => true, + "Subject" => false, + "TopicArn" => true, + "Type" => true + ); + + // Validate the IPN message header [x-amz-sns-message-type] + $this->validateHeaders(); + + // Converts the IPN [Message] to Notification object + $this->getMessage(); + + // Checks if the notification [Type] is Notification and constructs the signature fields + $this->checkForCorrectMessageType(); + + // Verifies the signature against the provided pem file in the IPN + $this->constructAndVerifySignature(); + } + + private function checkConfigKeys($ipnConfig) + { + $ipnConfig = array_change_key_case($ipnConfig, CASE_LOWER); + $ipnConfig = $this->trimArray($ipnConfig); + + foreach ($ipnConfig as $key => $value) { + if (array_key_exists($key, $this->ipnConfig)) { + $this->ipnConfig[$key] = $value; + } else { + throw new \Exception('Key ' . $key . ' is either not part of the configuration or has incorrect Key name. + check the ipnConfig array key names to match your key names of your config array ', 1); + } + } + } + + /* Setter function + * Sets the value for the key if the key exists in ipnConfig + */ + + public function __set($name, $value) + { + if (array_key_exists(strtolower($name), $this->ipnConfig)) { + $this->ipnConfig[$name] = $value; + } else { + throw new \Exception("Key " . $name . " is not part of the configuration", 1); + } + } + + /* Getter function + * Returns the value for the key if the key exists in ipnConfig + */ + + public function __get($name) + { + if (array_key_exists(strtolower($name), $this->ipnConfig)) { + return $this->ipnConfig[$name]; + } else { + throw new \Exception("Key " . $name . " was not found in the configuration", 1); + } + } + + /* Trim the input Array key values */ + + private function trimArray($array) + { + foreach ($array as $key => $value) + { + $array[$key] = trim($value); + } + return $array; + } + + private function validateHeaders() + { + // Quickly check that this is a sns message + if (!array_key_exists('x-amz-sns-message-type', $this->headers)) { + throw new \Exception("Error with message - header " . "does not contain x-amz-sns-message-type header"); + } + + if ($this->headers['x-amz-sns-message-type'] !== 'Notification') { + throw new \Exception("Error with message - header x-amz-sns-message-type is not " . "Notification, is " . $this->headers['x-amz-sns-message-type']); + } + } + + private function getMessage() + { + $this->snsMessage = json_decode($this->body, true); + + $json_error = json_last_error(); + + if ($json_error != 0) { + $errorMsg = "Error with message - content is not in json format" . $this->getErrorMessageForJsonError($json_error) . " " . $this->snsMessage; + throw new \Exception($errorMsg); + } + } + + /* Convert a json error code to a descriptive error message + * + * @param int $json_error message code + * + * @return string error message + */ + + private function getErrorMessageForJsonError($json_error) + { + switch ($json_error) { + case JSON_ERROR_DEPTH: + return " - maximum stack depth exceeded."; + break; + case JSON_ERROR_STATE_MISMATCH: + return " - invalid or malformed JSON."; + break; + case JSON_ERROR_CTRL_CHAR: + return " - control character error."; + break; + case JSON_ERROR_SYNTAX: + return " - syntax error."; + break; + default: + return "."; + break; + } + } + + /* checkForCorrectMessageType() + * + * Checks if the Field [Type] is set to ['Notification'] + * Gets the value for the fields marked true in the fields array + * Constructs the signature string + */ + + private function checkForCorrectMessageType() + { + $type = $this->getMandatoryField("Type"); + if (strcasecmp($type, "Notification") != 0) { + throw new \Exception("Error with SNS Notification - unexpected message with Type of " . $type); + } + + if (strcmp($this->getMandatoryField("Type"), "Notification") != 0) { + throw new \Exception("Error with signature verification - unable to verify " . $this->getMandatoryField("Type") . " message"); + } else { + + // Sort the fields into byte order based on the key name(A-Za-z) + ksort($this->fields); + + // Extract the key value pairs and sort in byte order + $signatureFields = array(); + foreach ($this->fields as $fieldName => $mandatoryField) { + if ($mandatoryField) { + $value = $this->getMandatoryField($fieldName); + } else { + $value = $this->getField($fieldName); + } + + if (!is_null($value)) { + array_push($signatureFields, $fieldName); + array_push($signatureFields, $value); + } + } + + /* Create the signature string - key / value in byte order + * delimited by newline character + ending with a new line character + */ + $this->signatureFields = implode("\n", $signatureFields) . "\n"; + + } + } + + /* Verify that the signature is correct for the given data and + * public key + * + * @param string $data data to validate + * @param string $signature decoded signature to compare against + * @param string $certificatePath path to certificate, can be file or url + * + * @throws Exception if there is an error with the call + * + * @return bool true if valid + */ + + private function constructAndVerifySignature() + { + $signature = base64_decode($this->getMandatoryField("Signature")); + $certificatePath = $this->getMandatoryField("SigningCertURL"); + + $this->certificate = $this->getCertificate($certificatePath); + + $result = $this->verifySignatureIsCorrectFromCertificate($signature); + if (!$result) { + throw new \Exception("Unable to match signature from remote server: signature of " . $this->getCertificate($certificatePath) . " , SigningCertURL of " . $this->getMandatoryField("SigningCertURL") . " , SignatureOf " . $this->getMandatoryField("Signature")); + } + } + + /* getCertificate($certificatePath) + * + * gets the certificate from the $certificatePath using Curl + */ + + private function getCertificate($certificatePath) + { + $httpCurlRequest = new HttpCurl($this->ipnConfig); + + $response = $httpCurlRequest->httpGet($certificatePath); + + return $response; + } + + /* Verify that the signature is correct for the given data and public key + * + * @param string $data data to validate + * @param string $signature decoded signature to compare against + * @param string $certificate certificate object defined in Certificate.php + */ + + public function verifySignatureIsCorrectFromCertificate($signature) + { + $certKey = openssl_get_publickey($this->certificate); + + if ($certKey === False) { + throw new \Exception("Unable to extract public key from cert"); + } + + try { + $certInfo = openssl_x509_parse($this->certificate, true); + $certSubject = $certInfo["subject"]; + + if (is_null($certSubject)) { + throw new \Exception("Error with certificate - subject cannot be found"); + } + } catch (\Exception $ex) { + throw new \Exception("Unable to verify certificate - error with the certificate subject", null, $ex); + } + + if (strcmp($certSubject["CN"], $this->expectedCnName)) { + throw new \Exception("Unable to verify certificate issued by Amazon - error with certificate subject"); + } + + $result = -1; + try { + $result = openssl_verify($this->signatureFields, $signature, $certKey, OPENSSL_ALGO_SHA1); + } catch (\Exception $ex) { + throw new \Exception("Unable to verify signature - error with the verification algorithm", null, $ex); + } + + return ($result > 0); + } + + + /* Extract the mandatory field from the message and return the contents + * + * @param string $fieldName name of the field to extract + * + * @throws Exception if not found + * + * @return string field contents if found + */ + + private function getMandatoryField($fieldName) + { + $value = $this->getField($fieldName); + if (is_null($value)) { + throw new \Exception("Error with json message - mandatory field " . $fieldName . " cannot be found"); + } + return $value; + } + + /* Extract the field if present, return null if not defined + * + * @param string $fieldName name of the field to extract + * + * @return string field contents if found, null otherwise + */ + + private function getField($fieldName) + { + if (array_key_exists($fieldName, $this->snsMessage)) { + return $this->snsMessage[$fieldName]; + } else { + return null; + } + } + + /* returnMessage() - JSON decode the raw [Message] portion of the IPN */ + + public function returnMessage() + { + return json_decode($this->snsMessage['Message'], true); + } + + /* toJson() - Converts IPN [Message] field to JSON + * + * Has child elements + * ['NotificationData'] [XML] - API call XML notification data + * @param remainingFields - consists of remaining IPN array fields that are merged + * Type - Notification + * MessageId - ID of the Notification + * Topic ARN - Topic of the IPN + * @return response in JSON format + */ + + public function toJson() + { + $response = $this->simpleXmlObject(); + + // Merging the remaining fields with the response + $remainingFields = $this->getRemainingIpnFields(); + $responseArray = array_merge($remainingFields,(array)$response); + + // Converting to JSON format + $response = json_encode($responseArray); + + return $response; + } + + /* toArray() - Converts IPN [Message] field to associative array + * @return response in array format + */ + + public function toArray() + { + $response = $this->simpleXmlObject(); + + // Converting the SimpleXMLElement Object to array() + $response = json_encode($response); + $response = json_decode($response, true); + + // Merging the remaining fields with the response array + $remainingFields = $this->getRemainingIpnFields(); + $response = array_merge($remainingFields,$response); + + return $response; + } + + /* addRemainingFields() - Add remaining fields to the datatype + * + * Has child elements + * ['NotificationData'] [XML] - API call XML response data + * Convert to SimpleXML element object + * Type - Notification + * MessageId - ID of the Notification + * Topic ARN - Topic of the IPN + * @return response in array format + */ + + private function simpleXmlObject() + { + $ipnMessage = $this->returnMessage(); + + // Getting the Simple XML element object of the IPN XML Response Body + $response = simplexml_load_string((string) $ipnMessage['NotificationData']); + + // Adding the Type, MessageId, TopicArn details of the IPN to the Simple XML element Object + $response->addChild('Type', $this->snsMessage['Type']); + $response->addChild('MessageId', $this->snsMessage['MessageId']); + $response->addChild('TopicArn', $this->snsMessage['TopicArn']); + + return $response; + } + + /* getRemainingIpnFields() + * Gets the remaining fields of the IPN to be later appended to the return message + */ + + private function getRemainingIpnFields() + { + $ipnMessage = $this->returnMessage(); + + $remainingFields = array( + 'NotificationReferenceId' =>$ipnMessage['NotificationReferenceId'], + 'NotificationType' =>$ipnMessage['NotificationType'], + 'SellerId' =>$ipnMessage['SellerId'], + 'ReleaseEnvironment' =>$ipnMessage['ReleaseEnvironment'] ); + + return $remainingFields; + } +} diff --git a/PayWithAmazon/Regions.php b/PayWithAmazon/Regions.php new file mode 100644 index 0000000..8c2b436 --- /dev/null +++ b/PayWithAmazon/Regions.php @@ -0,0 +1,23 @@ + 'mws-eu.amazonservices.com', + 'na' => 'mws.amazonservices.com', + 'jp' => 'mws.amazonservices.jp'); + + public $profileEndpointUrls = array('uk' => 'amazon.co.uk', + 'us' => 'amazon.com', + 'de' => 'amazon.de', + 'jp' => 'amazon.co.jp'); + + public $regionMappings = array('de' => 'eu', + 'uk' => 'eu', + 'us' => 'na', + 'jp' => 'jp'); +} \ No newline at end of file diff --git a/PayWithAmazon/ResponseParser.php b/PayWithAmazon/ResponseParser.php new file mode 100644 index 0000000..d21f84b --- /dev/null +++ b/PayWithAmazon/ResponseParser.php @@ -0,0 +1,100 @@ +response = $response; + } + + /* Returns the XML portion of the response */ + + public function toXml() + { + return $this->response['ResponseBody']; + } + + /* toJson - converts XML into Json + * @param $response [XML] + */ + + public function toJson() + { + $response = $this->simpleXmlObject(); + + return (json_encode($response)); + } + + /* toArray - converts XML into associative array + * @param $this->response [XML] + */ + + public function toArray() + { + $response = $this->simpleXmlObject(); + + // Converting the SimpleXMLElement Object to array() + $response = json_encode($response); + + return (json_decode($response, true)); + } + + private function simpleXmlObject() + { + $response = $this->response; + + // Getting the HttpResponse Status code to the output as a string + $status = strval($response['Status']); + + // Getting the Simple XML element object of the XML Response Body + $response = simplexml_load_string((string) $response['ResponseBody']); + + // Adding the HttpResponse Status code to the output as a string + $response->addChild('ResponseStatus', $status); + + return $response; + } + + /* Get the status of the Order Reference ID */ + + public function getOrderReferenceDetailsStatus($response) + { + $data= new \SimpleXMLElement($response); + $namespaces = $data->getNamespaces(true); + foreach($namespaces as $key=>$value){ + $namespace = $value; + } + $data->registerXPathNamespace('GetORO', $namespace); + foreach ($data->xpath('//GetORO:OrderReferenceStatus') as $value) { + $oroStatus = json_decode(json_encode((array)$value), TRUE); + } + + return $oroStatus ; + } + + /* Get the status of the BillingAgreement */ + + public function getBillingAgreementDetailsStatus($response) + { + $data= new \SimpleXMLElement($response); + $namespaces = $data->getNamespaces(true); + foreach($namespaces as $key=>$value){ + $namespace = $value; + } + $data->registerXPathNamespace('GetBA', $namespace); + foreach ($data->xpath('//GetBA:BillingAgreementStatus') as $value) { + $baStatus = json_decode(json_encode((array)$value), TRUE); + } + + return $baStatus ; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..b07ba2d --- /dev/null +++ b/README.md @@ -0,0 +1,520 @@ +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> a8d53e9... added latest PHP SDK +# Login and Pay with Amazon PHP SDK +Login and Pay with Amazon API Integration + +## Requirements + +* Login and Pay With Amazon account - [Register here](https://payments.amazon.com/signup) +* PHP 5.3 or higher +* Curl 7.18 or higher + +## Documentation + +* The Integration steps can be found [here](https://payments.amazon.com/documentation) + +## Sample + +* View the sample integration demo [here](https://amzn.github.io/login-and-pay-with-amazon-sdk-samples/) + +## Quick Start + +Instantiating the client: +Client Takes in parameters in the following format + +1. Associative array +2. Path to the JSON file containing configuration information. + +## Installing using Composer +``` +composer create-project amzn/login-and-pay-with-amazon-sdk-php --prefer-dist +``` +## Directory Tree +``` +. +β”œβ”€β”€ composer.json - Configuration for composer +β”œβ”€β”€ LICENSE.txt +β”œβ”€β”€ NOTICE.txt +β”œβ”€β”€ PayWithAmazon +β”‚Β Β  β”œβ”€β”€ Client.php - Main class with the API calls +β”‚Β Β  β”œβ”€β”€ HttpCurl.php - Client class uses this file to execute the GET/POST +β”‚Β Β  β”œβ”€β”€ Interface.php - Shows the public function definitions in the Client.php +β”‚Β Β  β”œβ”€β”€ IpnHandler.php - Class handles verification of the IPN +β”‚Β Β  β”œβ”€β”€ Regions.php - Defines the regions that is supported +β”‚Β Β  └── ResponseParser.php - Parses the API call response +β”œβ”€β”€ README.md +└── UnitTests + β”œβ”€β”€ ClientTest.php + β”œβ”€β”€ config.json + β”œβ”€β”€ coverage.txt + β”œβ”€β”€ IpnHandlerTest.php + └── Signature.php +``` +##Parameters List + +####Mandatory Parameters +| Parameter | variable name | Values | +|--------------|---------------|------------------------------------------------| +| Merchant Id | `merchant_id` | Default : `null` | +| Access Key | `access_key` | Default : `null` | +| Secret Key | `secret_key` | Default : `null` | +| Region | `region` | Default : `null`
Other: `us`,`de`,`uk`,`jp` | + +####Optional Parameters +| Parameter | Variable name | Values | +|---------------------|-----------------------|----------------------------------------------------| +| Currency Code | `currency_code` | Default : `null`
Other: `USD`,`EUR`,`GBP`,`JPY` | +| Environment | `sandbox` | Default : `false`
Other: `true` | +| Platform ID | `platform_id` | Default : `null` | +| CA Bundle File | `cabundle_file` | Default : `null` | +| Application Name | `application_name` | Default : `null` | +| Application Version | `application_version` | Default : `null` | +| Proxy Host | `proxy_host` | Default : `null` | +| Proxy Port | `proxy_port` | Default : `-1` | +| Proxy Username | `proxy_username` | Default : `null` | +| Proxy Password | `proxy_password` | Default : `null` | +| LWA Client ID | `client_id` | Default : `null` | +| Handle Throttle | `handle_throttle` | Default : `true`
Other: `false` | + +## Setting Configuration + +Setting configuration while instantiating the Client object +```php + 'YOUR_MERCHANT_ID', + 'access_key' => 'YOUR_ACCESS_KEY', + 'secret_key' => 'YOUR_SECRET_KEY', + 'client_id' => 'YOUR_LOGIN_WITH_AMAZON_CLIENT_ID', + 'region' => 'REGION'); + +// JSON file path +$config = 'PATH_TO_JSON_FILE'; + +// Instantiate the client class with the config type +$client = new Client($config); +``` +### Testing in Sandbox Mode + +The sandbox parameter is defaulted to false if not specified: +```php + 'YOUR_MERCHANT_ID', + 'access_key' => 'YOUR_ACCESS_KEY', + 'secret_key' => 'YOUR_SECRET_KEY', + 'client_id' => 'YOUR_LOGIN_WITH_AMAZON_CLIENT_ID', + 'region' => 'REGION', + 'sandbox' => true ); + +$client = new Client($config); + +// Also you can set the sandbox variable in the config() array of the Client class by + +$client->setSandbox(true); +``` +### Setting Proxy values +Proxy parameters can be set after Instantiating the Client Object with the following setter +```php +$proxy = array(); +$proxy['proxy_user_host'] // Hostname for the proxy +$proxy['proxy_user_port'] // Hostname for the proxy +$proxy['proxy_user_name'] // If your proxy requires a username +$proxy['proxy_user_password'] // If your proxy requires a password + +$client->setProxy($proxy); +``` + +### Making an API Call + +Below is an example on how to make the GetOrderReferenceDetails API call: + +```php +getOrderReferenceDetails($requestParameters); + +``` +See the [API Response](https://github.com/amzn/login-and-pay-with-amazon-sdk-php#api-response) section for information on parsing the API response. + +### IPN Handling + +1. To receive IPN's successfully you will need an valid SSL on your domain. +2. You can set up your Notification endpoints in Seller Central by accessing the Integration Settings page in the Settings tab. +3. IpnHandler.php class handles verification of the source and the data of the IPN + +Add the below code into any file and set the URL to the file location in Merchant/Integrator URL by accessing Integration Settings page in the Settings tab. + +```php + **Capture Now** can be set to `true` for digital goods . For Physical goods it's highly recommended to set the Capture Now to `false` +and the amount captured by making the `capture` API call after the shipment is complete. + + +| Parameter | Variable Name | Mandatory | Values | +|----------------------------|------------------------------|-----------|-----------------------------------------------------------------------------------------------------------| +| Amazon Reference ID | `amazon_reference_id` | yes | OrderReference ID (`starts with P01 or S01`) or
Billing Agreement ID (`starts with B01 or C01`) | +| Amazon OrderReference ID | `amazon_order_reference_id` | no | OrderReference ID (`starts with P01 or S01`) if no Amazon Reference ID is provided | +| Amazon Billing Agreement ID| `amazon_billing_agreement_id`| no | Billing Agreement ID (`starts with B01 or C01`) if no Amazon Reference ID is provided | +| Merchant ID | `merchant_id` | no | Value taken from config array in Client.php | +| Charge Amount | `charge_amount` | yes | Amount that needs to be captured.
Maps to API call variables `amount` , `authorization_amount` | +| Currency code | `currency_code` | no | If no value is provided, value is taken from the config array in Client.php | +| Authorization Reference ID | `authorization_reference_id` | yes | Unique string to be passed | +| Transaction Timeout | `transaction_timeout` | no | Timeout for Authorization - Defaults to 1440 minutes | +| Capture Now | `capture_now` | no | Will capture the payment automatically when set to `true`. Defaults to `false` | +| Charge Note | `charge_note` | no | Note that is sent to the buyer.
Maps to API call variables `seller_note` , `seller_authorization_note`| +| Charge Order ID | `charge_order_id` | no | Custom order ID provided
Maps to API call variables `seller_order_id` , `seller_billing_agreement_id` | +| Store Name | `store_name` | no | Name of the store | +| Platform ID | `platform_id` | no | Platform ID of the Solution provider | +| Custom Information | `custom_information` | no | Any custom string | +| MWS Auth Token | `mws_auth_token` | no | MWS Auth Token required if API call is made on behalf of the seller | + +```php +// Create an array that will contain the parameters for the charge API call +$requestParameters = array(); + +// Adding the parameters values to the respective keys in the array +$requestParameters['amazon_reference_id'] = 'AMAZON_REFERENCE_ID'; + +// Or +// If $requestParameters['amazon_reference_id'] is not provided, +// either one of the following ID input is needed +$requestParameters['amazon_order_reference_id'] = 'AMAZON_ORDER_REFERENCE_ID'; +$requestParameters['amazon_billing_agreement_id'] = 'AMAZON_BILLING_AGREEMENT_ID'; + +$requestParameters['seller_id'] = null; +$requestParameters['charge_amount'] = '100.50'; +$requestParameters['currency_code'] = 'USD'; +$requestParameters['authorization_reference_id'] = 'UNIQUE STRING'; +$requestParameters['transaction_timeout'] = 0; +$requestParameters['capture_now'] = false; //`true` for Digital goods +$requestParameters['charge_note'] = 'Example item note'; +$requestParameters['charge_order_id'] = '1234-Example-Order'; +$requestParameters['store_name'] = 'Example Store'; +$requestParameters['platform_Id'] = null; +$requestParameters['custom_information'] = 'Any_Custom_String'; +$requestParameters['mws_auth_token'] = null; + +// Get the Authorization response from the charge method +$response = $client->charge($requestParameters); +``` +See the [API Response](https://github.com/amzn/login-and-pay-with-amazon-sdk-php#api-response) section for information on parsing the API response. + +#####Obtain profile information (getUserInfo method) +1. obtains the user's profile information from Amazon using the access token returned by the Button widget. +2. An access token is granted by the authorization server when a user logs in to a site. +3. An access token is specific to a client, a user, and an access scope. A client must use an access token to retrieve customer profile data. + +| Parameter | Variable Name | Mandatory | Values | +|---------------------|-----------------------|-----------|------------------------------------------------------------------------------------------| +| Access Token | `access_token` | yes | Retrieved as GET parameter from the URL | +| Region | `region` | yes | Default :`null`
Other:`us`,`de`,`uk`,`jp`
Value is set in config['region'] array | +| LWA Client ID | `client_id` | yes | Default: null
Value should be set in config array | + +```php + 'YOUR_LWA_CLIENT_ID', + 'region' => 'REGION' ); + +$client = new Client($config); + +// Client ID can also be set using the setter function setClientId($client_id) +$client->setClientId(β€˜YOUR_LWA_CLIENT_ID’); + +// Get the Access Token from the URL +$access_token = 'ACCESS_TOKEN'; +// Calling the function getUserInfo with the access token parameter returns object +$userInfo = $client->getUserInfo($access_token); + +// Buyer name +$userInfo['name']; +// Buyer Email +$userInfo['email']; +// Buyer User Id +$userInfo['user_id']; +``` +### Response Parsing + +Responses are provided in 3 formats + +1. XML/Raw response +2. Associative array +3. JSON format + +#####API Response +```php +// Returns an object($response) of the class ResponseParser.php +$response = $client->getOrderReferenceDetails($requestParameters); + +// XML response +$response->toXml(); + +// Associative array response +$response->toArray(); + +// JSON response +$response->toJson(); +``` + +#####IPN Response +```php +$ipnHandler = new IpnHandler($headers, $body); + +// Raw message response +$ipnHandler->returnMessage(); + +// Associative array response +$ipnHandler->toArray(); + +// JSON response +$ipnHandler->toJson(); +<<<<<<< HEAD +<<<<<<< HEAD +``` +======= +login-and-pay-with-amazon-sdk-php +================================= +>>>>>>> 73e09fb... Initial commit +======= +****************************************************************************** +Login and Pay with Amazon PHP Library +Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +****************************************************************************** + +****************************************************************************** + INTRODUCTION +****************************************************************************** + + Please understand that by using the Login and Pay with Amazon sample code, + you are agreeing to understand and abide by the terms of the license, + as written in NOTICE.txt & LICENSE.txt accompanying this archive. + This sample code has been tested with PHP 5.3.8 and Curl 7.18.1 + +****************************************************************************** + INCLUDED FILES +****************************************************************************** + + * src/ * PHP files required to execute the code + * LICENSE.txt * The Apache License this code is licensed under + * NOTICE.txt * Notice file. + * README.txt * This file. + * CHANGES.txt * List of changes to the SDK. + +****************************************************************************** + PREREQUISITES +****************************************************************************** +In US, if you have registered with Amazon Payments API Integration prior to +October 7th, 2013, you will need to register with Login with Amazon (LWA) and +get a Login with Amazon (LWA) Client ID. To register with LWA visit +https://login.amazon.com/ and click on "Sign Up" button. + +In EU, if you register with Amazon Payments API Integration prior to +September 9th, 2014, you will need to register with Login with Amazon (LWA) and +get a Login with Amazon (LWA) Client ID. To register with LWA contact Amazon +Payments seller support through Seller Central and request an LwA registration. + + +Once registered for LWA to get your LWA Client ID, go to Seller Central, select +the ???Login with Amazon??? marketplace on the top right switcher, click on "Register +Your Application" button on LWA Seller Central page. For additional information, +please see the following step by step guide to get your Login with Amazon Client +ID: https://amazonpayments.s3.amazonaws.com/documents/Get_Your_Login_with_Amazon +_Client_ID.pdf + +****************************************************************************** + USAGE INSTRUCTIONS +****************************************************************************** +Note: The following steps are for a UNIX based operating environment (and can +be easily modified to suite other operating systems) + +This SDK includes two sets of samples - a command line based example that +requires a minimal setup in order to run, and a webserver based sample that +demonstrates notification processing. + +To run the command line based examples: + + (1) Open the src/OffAmazonPaymentsService/OffAmazonPaymentsService.config + .inc.php file and fill out the merchant Id, access key and secret key fields. + Please also fill out LWA client Id field if Login with Amazon service is + available in your region. Also make sure that the environment and region + keys are configured to the right values for your test. + + (2) Navigate to the src/OffAmazonPaymentsService/Samples directory, and run + the selected PHP sample in the console. An Amazon Order Reference Id is + required for the examples. If Automatic Payments service is available in your + region, an Amazon Billing Agreement Id is required for the example + AutomaticPaymentsSimpleCheckoutExampleCLI.php + + (a) An Amazon Order Reference Id/Amazon Billing Agreement Id can be + generated by using the OffAmazonPayments wallet widget. There are sample + html pages titled signin.php, address.php and wallet.php that contain + widgets ready to host on a php webserver. Please see + src/OffAmazonPaymentsNotifications/Samples directory for the sample pages. + + (b) For the RefundExample.php sample, you will also need to provide the + Amazon Capture Id as a second argument which can be obtained from an earlier + service call. + + (3) The result of the operation can be viewed by looking at the console output + from the script. + +To run the notification based examples: + + (1) Open the src/OffAmazonPaymentsService/OffAmazonPaymentsService.config + .inc.php file and fill out the merchant Id, access key and secret key fields. + Please also fill out LWA client Id field if Login with Amazon service is + available in your region. Also make sure that the environment and region + keys are configured to the right values for your test. + + (2) Open the src/OffAmazonPaymentsNotifciations/Samples/OffAmazonPaymentsNotifications + .config.inc.php file and fill out a directory to log notification information to. + This folder must be a directory to which the webserver user has write permissions. + + (3) Deploy the SDK src folder to your webserver environment, and modify your + webserver php include path to include this src directory in your default + include path. + + (4) Login to seller central and setup the Instant Notification merchant URL on + the Integration Settings page. For the samples, the notification url will + be http://(optional /)/IpnHandler.php and this needs + to be accessible to the internet for notification delivery to function. + + (5) In your browser, navigate to the Samples.html file in the + src/OffAmazonPaymentsNotifications/Samples directory to execute the samples. + +Using the client API: + +To make service calls from your scripts: + + (1) Include the src/OffAmazonPaymentsService/Client.php file + + (2) Create a new instance of the OffAmazonPaymentsService_Client class - by + default this will use the values defined in the + src/OffAmazonPaymentsService/OffAmazonPaymentsService.config.inc.php file, + which you can optionally override in the constructor call. + +To handle notifications from your scripts: + + (1) Configure your webserver to accept incoming connections over HTTPS - the + webserver based samples make use of notification processing which requires + a HTTPS connection, and requires a server certificate that is signed by a + trusted CA authority. The listed of trusted CA authorities can be found at + http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.https.ca.html + + (2) Include the src/OffAmazonPaymentsNotifications/Client.php file + + (3) Create a new instance of the OffAmazonPaymentsNotifications_Client class + + (4) Read in the http headers and body in your script and invoke the method on + the client class + + (5) If the client throws an NotificationsException, return a http status code + of 500 to indicate that the notification cannot be processed. Otherwise + return a http 200 code. + +Note: If the status code is not returned within 15 seconds of the notification +being transmitted, it will be treated as a failed delivery. + +Note that these samples use the cURL library to make calls to remote servers, +and on some may systems requires additional configuration in order to +sucessfully verify the remote server. If you are encountering SSL connection +errors then follow these steps: + + (1) Follow the instruction on http://curl.haxx.se/docs/sslcerts.html to obtain + a bundled ca cert file, and ensure that it includes the signing cert for + "VeriSign Class 3 Secure Server CA - G3" + + (2) Save the file to the following path: + - Windows - C:\certs\caCertFile.crt + - Unix - /etc/conf/certs/caCertFile.crt + + (3) Uncomment the $caBundleFile line in the + OffAmazonPaymentsService.config.php property file and restart the service + +If you save to a different path then change the value of the $caBundleFile +property to point to this directory. + +Note: When deploying the SDK packages to production systems, make sure that +the Samples directories are not deployed as they are not mandatory in order to +use the client code. + +****************************************************************************** + SUPPORT & PROJECT HOME +****************************************************************************** +The latest documentation on the Login and Pay with Amazon can be found at the LINKS +section below: + +US Amazon Seller Central: https://sellercentral.amazon.com +EU Amazon Seller Central: https://sellercentral-europe.amazon.com + + 1. Login to the site and navigate to the integration central tab to view + available resources. + +>>>>>>> 97c1ac4... - +======= +login-and-pay-with-amazon +>>>>>>> 95107e4... - +======= +``` +>>>>>>> a8d53e9... added latest PHP SDK +======= +``` +>>>>>>> 643790a... added a semicolon diff --git a/UnitTests/ClientTest.php b/UnitTests/ClientTest.php new file mode 100644 index 0000000..a569700 --- /dev/null +++ b/UnitTests/ClientTest.php @@ -0,0 +1,817 @@ + 'test', + 'access_key' => 'test', + 'secret_key' => "test", + 'currency_code' => 'usd', + 'client_id' => 'test', + 'region' => 'us', + 'sandbox' => true, + 'platform_id' => 'test', + 'cabundle_file' => null, + 'application_name' => 'sdk testing', + 'application_version' => '1.0', + 'proxy_host' => null, + 'proxy_port' => -1, + 'proxy_username' => null, + 'proxy_Password' => null + ); + + public function testConfigArray() + { + try { + $client = new Client($this->configParams); + } catch (\Exception $expected) { + $this->assertRegExp('/is not a Json File or the Json File./i', strval($expected)); + } + + try { + $configParams = array( + 'a' => 'A', + 'b' => 'B' + ); + $client = new Client($configParams); + } catch (\Exception $expected) { + $this->assertRegExp('/is either not part of the configuration or has incorrect Key name./i', strval($expected)); + } + + try { + $configParams = array(); + $client = new Client($configParams); + } catch (\Exception $expected) { + $this->assertRegExp('/$config cannot be null./i', strval($expected)); + } + } + + public function testJsonFile() + { + try { + $configParams = "config.json"; + $client = new Client($configParams); + } catch (\Exception $expected) { + $this->assertRegExp('/Error with message - content is not in json format./i', strval($expected)); + } + + try { + $configParams = "abc.json"; + $client = new Client($configParams); + } catch (\Exception $expected) { + $this->assertRegExp('/is not a Json File path or the Json File./i', strval($expected)); + } + } + + public function testSandboxSetter() + { + $client = new Client($this->configParams); + try { + $client->setSandbox(true); + } catch (\Exception $expected) { + $this->assertRegExp('/and should be a boolean value./i', strval($expected)); + } + + try { + $client->setSandbox('string value'); + } catch (\Exception $expected) { + $this->assertRegExp('/and should be a boolean value./i', strval($expected)); + } + } + + public function testGetOrderReferenceDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'address_consent_token' => 'AddressConsentToken', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'GetOrderReferenceDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->getOrderReferenceDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testSetOrderReferenceDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'Merchant_Id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'SetOrderReferenceDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->setOrderReferenceDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testConfirmOrderReference() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'ConfirmOrderReference'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->confirmOrderReference($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCancelOrderReference() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'cancelation_reason' => 'CancelationReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'CancelOrderReference'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->cancelOrderReference($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCloseOrderReference() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'CloseOrderReference'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->closeOrderReference($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCloseAuthorization() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'CloseAuthorization'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->CloseAuthorization($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testAuthorize() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'capture_now' => 'CaptureNow', + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'Authorize'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->authorize($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testGetAuthorizationDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'GetAuthorizationDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->getAuthorizationDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCapture() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'capture_amount' => 'CaptureAmount.Amount', + 'currency_code' => 'CaptureAmount.CurrencyCode', + 'capture_reference_id' => 'CaptureReferenceId', + 'seller_capture_note' => 'SellerCaptureNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'Capture'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->capture($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testGetCaptureDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'GetCaptureDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->getCaptureDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testRefund() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'refund_reference_id' => 'RefundReferenceId', + 'refund_amount' => 'RefundAmount.Amount', + 'currency_code' => 'RefundAmount.CurrencyCode', + 'seller_refund_note' => 'SellerRefundNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'Refund'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->refund($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testGetRefundDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_refund_id' => 'AmazonRefundId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'GetRefundDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->getRefundDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testGetServiceStatus() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'GetServiceStatus'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->getServiceStatus($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCreateOrderReferenceForId() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'id' => 'Id', + 'id_type' => 'IdType', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'confirm_now' => 'ConfirmNow', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'CreateOrderReferenceForId'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->createOrderReferenceForId($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testGetBillingAgreementDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'address_consent_token' => 'AddressConsentToken', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'GetBillingAgreementDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->getBillingAgreementDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testSetBillingAgreementDetails() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'platform_id' => 'BillingAgreementAttributes.PlatformId', + 'seller_note' => 'BillingAgreementAttributes.SellerNote', + 'seller_billing_agreement_id' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.SellerBillingAgreementId', + 'custom_information' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.CustomInformation', + 'store_name' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.StoreName', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'SetBillingAgreementDetails'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->setBillingAgreementDetails($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testConfirmBillingAgreement() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'ConfirmBillingAgreement'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->confirmBillingAgreement($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testValidateBillingAgreement() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'ValidateBillingAgreement'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->validateBillingAgreement($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testAuthorizeOnBillingAgreement() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'capture_now' => 'CaptureNow', + 'soft_descriptor' => 'SoftDescriptor', + 'seller_note' => 'SellerNote', + 'platform_id' => 'PlatformId', + 'custom_information' => 'SellerOrderAttributes.CustomInformation', + 'seller_order_id' => 'SellerOrderAttributes.SellerOrderId', + 'store_name' => 'SellerOrderAttributes.StoreName', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'AuthorizeOnBillingAgreement'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->authorizeOnBillingAgreement($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCloseBillingAgreement() + { + $client = new Client($this->configParams); + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $action = 'CloseBillingAgreement'; + + $parameters = $this->setParametersAndPost($fieldMappings, $action); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedStringParams = $this->callPrivateMethod($client, 'calculateSignatureAndParametersToString', $expectedParameters); + + $response = $client->closeBillingAgreement($apiCallParams); + + $apiParametersString = $client->getParameters(); + + $this->assertEquals($apiParametersString, $expectedStringParams); + } + + public function testCharge() + { + $client = new Client($this->configParams); + $apiCallParams = array('amazon_reference_id' => 'S01-TEST'); + + $client->charge($apiCallParams); + + try { + $client = new Client($this->configParams); + $apiCallParams = array('amazon_reference_id' => ''); + $client->charge($apiCallParams); + } catch (\Exception $expected) { + $this->assertRegExp('/amazon_reference_id is null and is a required parameter./i', strval($expected)); + } + + try { + $client = new Client($this->configParams); + $apiCallParams = array('amazon_reference_id' => 'T01'); + $client->charge($apiCallParams); + } catch (\Exception $expected) { + $this->assertRegExp('/Invalid Amazon Reference ID./i', strval($expected)); + } + } + + public function testGetUserInfo() + { + try { + $this->configParams['region'] = ''; + $client = new Client($this->configParams); + $client->getUserInfo('Atza'); + } catch (\Exception $expected) { + $this->assertRegExp('/is a required parameter./i', strval($expected)); + } + + try { + $this->configParams['region'] = 'us'; + $client = new Client($this->configParams); + $client->getUserInfo(null); + } catch (\Exception $expected) { + $this->assertRegExp('/Access Token is a required parameter and is not set./i', strval($expected)); + } + } + + public function testSignature() + { + $client = new Client($this->configParams); + + $parameters['SellerId'] = $this->configParams['merchant_id']; + $parameters['AWSAccessKeyId'] = $this->configParams['access_key']; + $parameters['Version'] = 'test'; + $parameters['SignatureMethod'] = 'HmacSHA256'; + $parameters['SignatureVersion'] = 2; + $parameters['Timestamp'] = $this->getFormattedTimestamp(); + uksort($parameters, 'strcmp'); + + $signatureObj = new Signature($this->configParams,$parameters); + $expectedSignature = $signatureObj->getSignature(); + + $this->callPrivateMethod($client,'createServiceUrl', null); + + $signature = $this->callPrivateMethod($client,'signParameters', $parameters); + + $this->assertEquals($signature, $expectedSignature); + } + + public function test500or503() + { + try { + $client = new Client($this->configParams); + + $url = 'https://www.justcharge.me/OffAmazonPayments_Sandbox/2013-01-01'; + $client->setMwsServiceUrl($url); + $this->callPrivateMethod($client, 'invokePost', null); + + } catch (\Exception $expected) { + $this->assertRegExp('/Maximum number of retry attempts./i', strval($expected)); + } + + } + + public function testXmlResponse() + { + $response = array(); + $response['ResponseBody'] = + ' + S01-5806490-2147504 + 2015-09-27T02:18:33.408Z + This is testing API call + '; + + $responseObj = new ResponseParser($response); + $xmlResponse = $responseObj->toXml(); + + $this->assertEquals($xmlResponse, $response['ResponseBody']); + } + + public function testJsonResponse() + { + $response = array('Status' => '200'); + $response['ResponseBody'] = + ' + S01-5806490-2147504 + 2015-09-27T02:18:33.408Z + This is testing API call + '; + + $json = + '{"AmazonOrderReferenceId":"S01-5806490-2147504","ExpirationTimestamp":"2015-09-27T02:18:33.408Z","SellerNote":"This is testing API call","ResponseStatus":"200"}'; + + $responseObj = new ResponseParser($response); + $jsonResponse = $responseObj->toJson(); + + $this->assertEquals($json, $jsonResponse); + } + + public function testArrayResponse() + { + $response = array('Status' => '200'); + $response['ResponseBody'] = + ' + S01-5806490-2147504 + 2015-09-27T02:18:33.408Z + This is testing API call + '; + + $array = array('AmazonOrderReferenceId' => 'S01-5806490-2147504', + 'ExpirationTimestamp' => '2015-09-27T02:18:33.408Z', + 'SellerNote' => 'This is testing API call', + 'ResponseStatus' => '200'); + + $responseObj = new ResponseParser($response); + $arrayResponse = $responseObj->toArray(); + + $this->assertEquals($array, $arrayResponse); + } + + private function setParametersAndPost($fieldMappings, $action) + { + $expectedParameters = array(); + $apiCallParams = array(); + + $parameters = $this->setDefaultValues($fieldMappings); + $expectedParameters = $parameters['expectedParameters']; + $apiCallParams = $parameters['apiCallParams']; + + $expectedParameters['Action'] = $action; + + foreach ($fieldMappings as $parm => $value) { + if(!isset($expectedParameters[$value])) + { + $expectedParameters[$value] = 'test'; + $apiCallParams[$parm] = 'test'; + } + } + + return array('expectedParameters' => $expectedParameters, + 'apiCallParams' =>$apiCallParams); + } + + private function setDefaultValues($fieldMappings) + { + $expectedParameters = array(); + $apiCallParams = array(); + + if (array_key_exists('platform_id', $fieldMappings)) { + $expectedParameters[$fieldMappings['platform_id']] = $this->configParams['platform_id']; + $apiCallParams['platform_id'] = $this->configParams['platform_id']; + } + + if (array_key_exists('currency_code', $fieldMappings)) { + $expectedParameters[$fieldMappings['currency_code']] = 'TEST'; + $apiCallParams['currency_code'] = 'TEST'; + } + + return array('expectedParameters' => $expectedParameters, + 'apiCallParams' => $apiCallParams); + } + + /* Formats date as ISO 8601 timestamp */ + + private function getFormattedTimestamp() + { + return gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time()); + } + + private function callPrivateMethod($client, $methodName, $parameters) + { + $reflectionClass = new \ReflectionClass("PayWithAmazon\Client"); + $reflectionMethod = $reflectionClass->getMethod($methodName); + $reflectionMethod->setAccessible(true); + $expectedStringParams = $reflectionMethod->invoke($client, $parameters); + return $expectedStringParams; + } +} diff --git a/UnitTests/IpnHandlerTest.php b/UnitTests/IpnHandlerTest.php new file mode 100644 index 0000000..fb9d407 --- /dev/null +++ b/UnitTests/IpnHandlerTest.php @@ -0,0 +1,49 @@ + null, + 'proxy_host' => null, + 'proxy_port' => -1, + 'proxy_username' => null, + 'proxy_Password' => null + ); + + public function testConstructor() + { + try { + $headers = array(); + $headers = array('ab'=>'abc'); + $body = 'abctest'; + + $ipnHandler = new IpnHandler($headers,$body,$this->configParams); + + } catch (\Exception $expected) { + $this->assertRegExp('/Error with message - header./i', strval($expected)); + } + try { + $headers['x-amz-sns-message-type'] = 'Notification'; + $body = 'abctest'; + + $ipnHandler = new IpnHandler($headers,$body,$this->configParams); + + } catch (\Exception $expected) { + $this->assertRegExp('/Error with message - content is not in json format./i', strval($expected)); + } + try { + $ConfigParams = array( + 'a' => 'A', + 'b' => 'B' + ); + + $ipnHandler = new IpnHandler(array(),null,$ConfigParams); + + } catch (\Exception $expected) { + $this->assertRegExp('/is either not part of the configuration or has incorrect Key name./i', strval($expected)); + } + } +} diff --git a/UnitTests/Signature.php b/UnitTests/Signature.php new file mode 100644 index 0000000..3f7ff9c --- /dev/null +++ b/UnitTests/Signature.php @@ -0,0 +1,173 @@ + 'mws-eu.amazonservices.com', + 'na' => 'mws.amazonservices.com', + 'jp' => 'mws.amazonservices.jp'); + + private $regionMappings = array('de' => 'eu', + 'uk' => 'eu', + 'us' => 'na', + 'jp' => 'jp'); + + public function __construct($config = array(),$parameters = array()) + { + $config = array_change_key_case($config, CASE_LOWER); + $this->config = $config; + $this->signature = $this->calculateSignature($parameters); + } + + public function getSignature() + { + return trim($this->signature); + } + + /* Create an Array of required parameters, sort them + * Calculate signature and invoke the POST them to the MWS Service URL + * + * @param AWSAccessKeyId [String] + * @param Version [String] + * @param SignatureMethod [String] + * @param Timestamp [String] + * @param Signature [String] + */ + + private function calculateSignature($parameters) + { + $this->createServiceUrl(); + $signature = $this->signParameters($parameters); + return $signature; + } + + /* Computes RFC 2104-compliant HMAC signature for request parameters + * Implements AWS Signature, as per following spec: + * + * If Signature Version is 0, it signs concatenated Action and Timestamp + * + * If Signature Version is 1, it performs the following: + * + * Sorts all parameters (including SignatureVersion and excluding Signature, + * the value of which is being created), ignoring case. + * + * Iterate over the sorted list and append the parameter name (in original case) + * and then its value. It will not URL-encode the parameter values before + * constructing this string. There are no separators. + * + * If Signature Version is 2, string to sign is based on following: + * + * 1. The HTTP Request Method followed by an ASCII newline (%0A) + * 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline. + * 3. The URL encoded HTTP absolute path component of the URI + * (up to but not including the query string parameters); + * if this is empty use a forward '/'. This parameter is followed by an ASCII newline. + * 4. The concatenation of all query string components (names and values) + * as UTF-8 characters which are URL encoded as per RFC 3986 + * (hex characters MUST be uppercase), sorted using lexicographic byte ordering. + * Parameter names are separated from their values by the '=' character + * (ASCII character 61), even if the value is empty. + * Pairs of parameter and values are separated by the '&' character (ASCII code 38). + * + */ + + private function signParameters(array $parameters) + { + $signatureVersion = $parameters['SignatureVersion']; + $algorithm = "HmacSHA1"; + $stringToSign = null; + if (2 === $signatureVersion) { + $algorithm = "HmacSHA256"; + $parameters['SignatureMethod'] = $algorithm; + $stringToSign = $this->calculateStringToSignV2($parameters); + } else { + throw new Exception("Invalid Signature Version specified"); + } + + return $this->sign($stringToSign, $algorithm); + } + + /* Calculate String to Sign for SignatureVersion 2 + * @param array $parameters request parameters + * @return String to Sign + */ + + private function calculateStringToSignV2(array $parameters) + { + $data = 'POST'; + $data .= "\n"; + $data .= $this->mwsEndpointUrl; + $data .= "\n"; + $data .= $this->mwsEndpointPath; + $data .= "\n"; + $data .= $this->getParametersAsString($parameters); + return $data; + } + + /* Convert paremeters to Url encoded query string */ + + private function getParametersAsString(array $parameters) + { + $queryParameters = array(); + foreach ($parameters as $key => $value) { + $queryParameters[] = $key . '=' . $this->urlEncode($value); + } + + return implode('&', $queryParameters); + } + + private function urlEncode($value) + { + return str_replace('%7E', '~', rawurlencode($value)); + } + + /* Computes RFC 2104-compliant HMAC signature.*/ + + private function sign($data, $algorithm) + { + if ($algorithm === 'HmacSHA1') { + $hash = 'sha1'; + } else if ($algorithm === 'HmacSHA256') { + $hash = 'sha256'; + } else { + throw new Exception("Non-supported signing method specified"); + } + + return base64_encode(hash_hmac($hash, $data, $this->config['secret_key'], true)); + } + + /* Formats date as ISO 8601 timestamp */ + + private function getFormattedTimestamp() + { + return gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time()); + } + + private function createServiceUrl() + { + $this->modePath = strtolower($this->config['sandbox']) ? 'OffAmazonPayments_Sandbox' : 'OffAmazonPayments'; + + if (!empty($this->config['region'])) { + $region = strtolower($this->config['region']); + if (array_key_exists($region, $this->regionMappings)) { + $this->mwsEndpointUrl = $this->mwsServiceUrls[$this->regionMappings[$region]]; + $this->mwsServiceUrl = 'https://' . $this->mwsEndpointUrl . '/' . $this->modePath . '/' . self::SERVICE_VERSION; + $this->mwsEndpointPath = '/' . $this->modePath . '/' . self::SERVICE_VERSION; + } else { + throw new Exception($region . ' is not a valid region'); + } + } else { + throw new Exception("config['region'] is a required parameter and is not set"); + } + } +} diff --git a/UnitTests/config.json b/UnitTests/config.json new file mode 100755 index 0000000..e39bd38 --- /dev/null +++ b/UnitTests/config.json @@ -0,0 +1,15 @@ +{ + "merchant_id": "test", + "access_Key": "test", + "secret_Key": "test", + "currency_Code": "USD", + "client_Id": "test", + "region": "us", + "sandbox": true, + "application_Name": "sdk testing", + "application_Version": "1.0", + "proxy_Host": null, + "proxy_Port": -1, + "proxy_Username": null, + "proxy_Password": null +} \ No newline at end of file diff --git a/UnitTests/coverage.txt b/UnitTests/coverage.txt new file mode 100644 index 0000000..ada2318 --- /dev/null +++ b/UnitTests/coverage.txt @@ -0,0 +1,18 @@ + + +Code Coverage Report + 2015-04-08 22:26:32 + + Summary: + Classes: 0.00% (0/4) + Methods: 70.42% (50/71) + Lines: 81.59% (514/630) + +\PayWithAmazon::Client + Methods: 91.67% (44/48) Lines: 84.51% (431/510) +\PayWithAmazon::HttpCurl + Methods: 57.14% ( 4/ 7) Lines: 53.33% ( 24/ 45) +\PayWithAmazon::ResponseParser + Methods: 83.33% ( 5/ 6) Lines: 56.52% ( 13/ 23) +\PayWithAmazon::Signature + Methods: 90.00% ( 9/10) Lines: 88.46% ( 46/ 52) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a34204b --- /dev/null +++ b/composer.json @@ -0,0 +1,30 @@ +{ + "name": "amzn/login-and-pay-with-amazon-sdk-php", + "type": "library", + "description": "Pay with Amazon SDK", + "keywords": [ + "amazon", + "pay", + "payment", + "payments", + "pay with amazon", + "amazon payments" + ], + "homepage": "https://github.com/amzn/login-and-pay-with-amazon-sdk-php", + "license": "Apache OSL-2", + "authors": [ + { + "name": "Amazon Payments", + "email": "eps-dse@amazon.com" + } + ], + "autoload": { + "psr-4": { + "PayWithAmazon\\": "PayWithAmazon/" + } + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.0" + } +}