├── .github └── PULL_REQUEST_TEMPLATE.md ├── Amazon └── Pay │ └── API │ ├── AccountManagementClientInterface.php │ ├── Client.php │ ├── ClientInterface.php │ ├── Constants │ ├── DisputeFilingReason.php │ ├── DisputeReasonCode.php │ ├── DisputeResolution.php │ ├── DisputeState.php │ └── EvidenceType.php │ ├── DisputeClientInterface.php │ ├── HttpCurl.php │ ├── MerchantOnboardingClientInterface.php │ └── ReportingClientInterface.php ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── composer.json └── tests └── unit ├── ClientTest.php ├── unit_test_key_private.txt └── unit_test_key_public.txt /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /Amazon/Pay/API/AccountManagementClientInterface.php: -------------------------------------------------------------------------------- 1 | 'pay-api.amazon.eu', 33 | 'na' => 'pay-api.amazon.com', 34 | 'jp' => 'pay-api.amazon.jp'); 35 | 36 | private $regionMappings = array( 37 | 'eu' => 'eu', 38 | 'de' => 'eu', 39 | 'uk' => 'eu', 40 | 'us' => 'na', 41 | 'na' => 'na', 42 | 'jp' => 'jp'); 43 | 44 | public function __construct($config = null) { 45 | if (isset($config)) { 46 | 47 | if (!is_array($config)) { 48 | throw new \Exception('$config is of the incorrect type ' . gettype($config) . ' and should be of the type array'); 49 | } 50 | 51 | //V2 Algorithm accepts only 'eu', 'na' and 'jp' as region 52 | $config['region'] = $this->regionMappings[strtolower($config['region'])]; 53 | $this->config = $config; 54 | 55 | if (!empty($config['sandbox'])) { 56 | // setSandbox verifies boolean 57 | $this->setSandbox($config['sandbox']); 58 | } else { 59 | $this->setSandbox(false); 60 | } 61 | 62 | } else { 63 | throw new \Exception('Expecting config array{region, sandbox, public_key_id, private_key}', 1); 64 | } 65 | } 66 | 67 | /* Getter 68 | * Gets the value for the key if the key exists in config 69 | */ 70 | public function __get($name) 71 | { 72 | if (array_key_exists(strtolower($name), $this->config)) { 73 | return $this->config[strtolower($name)]; 74 | } else { 75 | 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); 76 | } 77 | } 78 | 79 | /* Get signature algorithm from config, use AMZN-PAY-RSASSA-PSS by default if not specified */ 80 | private function getAlgorithm() { 81 | if (!empty($this->config['algorithm'])) { 82 | if ($this->config['algorithm'] === 'AMZN-PAY-RSASSA-PSS' || $this->config['algorithm'] === 'AMZN-PAY-RSASSA-PSS-V2') { 83 | return $this->config['algorithm']; 84 | } else { 85 | throw new \Exception($this->config['algorithm'] . ' is not a valid algorithm'); 86 | } 87 | } else { 88 | return 'AMZN-PAY-RSASSA-PSS'; 89 | } 90 | } 91 | 92 | /* Create API service URL and the Endpoint path */ 93 | private function createServiceUrl() 94 | { 95 | // Comprehensive SDK use requires 'sandbox' and 'region' parameters to be specified in the config array 96 | // These attributes are not needed for Signature-only SDK usage 97 | 98 | $modePath = strtolower($this->config['sandbox']) ? 'sandbox' : 'live'; 99 | 100 | if (!empty($this->config['region'])) { 101 | $region = strtolower($this->config['region']); 102 | if (array_key_exists($region, $this->regionMappings)) { 103 | 104 | if (isset($this->config['override_service_url'])) { 105 | $apiEndpointUrl = preg_replace("(https?://)", "", $this->config['override_service_url']); 106 | } else { 107 | $apiEndpointUrl = $this->apiServiceUrls[$this->regionMappings[$region]]; 108 | } 109 | if($this->isEnvSpecificPublicKeyId()){ 110 | return 'https://' . $apiEndpointUrl . '/'; 111 | } 112 | return 'https://' . $apiEndpointUrl . '/' . $modePath . '/'; 113 | } else { 114 | throw new \Exception($region . ' is not a valid region'); 115 | } 116 | } else { 117 | throw new \Exception("config['region'] is a required parameter and is not set"); 118 | } 119 | } 120 | 121 | // Method used to validate whether PublicKeyId starts with prefix LIVE or SANDBOX 122 | private function isEnvSpecificPublicKeyId() { 123 | return $this->startsWith($this->config['public_key_id'], 'LIVE') || $this->startsWith($this->config['public_key_id'], 'SANDBOX'); 124 | } 125 | 126 | // Method used to validate whether String starts with Prefix or not 127 | private function startsWith($string, $prefix) 128 | { 129 | $length = strlen($prefix); 130 | return (substr(strtoupper($string), 0, $length) === $prefix); 131 | } 132 | 133 | /* canonicalURI 134 | * Strips the http and https off the URL 135 | * Strips the Host off the URL 136 | * Removes data after ? 137 | * Replaces special characters with their URL encoding 138 | */ 139 | private function getCanonicalURI($unEncodedURI) 140 | { 141 | if ($unEncodedURI === '') { 142 | return '/'; 143 | } 144 | $urlArray = parse_url($unEncodedURI); 145 | if (empty($urlArray['path'])) { 146 | return '/'; 147 | } 148 | else { 149 | return $urlArray['path']; 150 | } 151 | } 152 | 153 | /* sortCanonicalArray 154 | * Sorts given array 155 | * Breaks out values in array that are arrays themselves and 156 | returns a new array with that data 157 | * Creates new sorted array with all data 158 | */ 159 | private function sortCanonicalArray($canonicalArray) 160 | { 161 | $sortedCanonicalArray = array(); 162 | foreach ($canonicalArray as $key => $val) { 163 | if (is_array($val)) { 164 | foreach ($this->subArrays($val, $key) as $newKey => $subVal) { 165 | $sortedCanonicalArray[$newKey] = $subVal; 166 | } 167 | } 168 | else if ((is_null($val)) || ($val === '')) {} 169 | else { 170 | $sortedCanonicalArray[$key] = $val; 171 | } 172 | } 173 | ksort($sortedCanonicalArray); 174 | 175 | return $sortedCanonicalArray; 176 | } 177 | 178 | /* subArrays - helper function used to break out arrays in an array */ 179 | private function subArrays($parameters, $category) 180 | { 181 | $categoryIndex = 0; 182 | $newParameters = array(); 183 | $categoryString = "$category."; 184 | foreach ($parameters as $value) { 185 | $categoryIndex++; 186 | $newParameters[$categoryString . $categoryIndex] = $value; 187 | } 188 | 189 | return $newParameters; 190 | } 191 | 192 | /* createCanonicalQuery 193 | * Returns a string of request parameters 194 | */ 195 | private function createCanonicalQuery($requestParameters) 196 | { 197 | $sortedRequestParameters = $this->sortCanonicalArray($requestParameters); 198 | return $this->getParametersAsString($sortedRequestParameters); 199 | } 200 | 201 | /* Convert parameters to Url encoded query string */ 202 | private function getParametersAsString(array $parameters) 203 | { 204 | $queryParameters = array(); 205 | foreach ($parameters as $key => $value) { 206 | $queryParameters[] = $key . '=' . $this->urlEncode($value); 207 | } 208 | return implode('&', $queryParameters); 209 | } 210 | 211 | private function urlEncode($value) 212 | { 213 | return str_replace('%7E', '~', rawurlencode($value)); 214 | } 215 | 216 | /* HexEncode and hash data */ 217 | private function hexAndHash($data) 218 | { 219 | $hash = self::HASH_ALGORITHM; 220 | return bin2hex(hash($hash, isset($data) ? $data : '', true)); 221 | } 222 | 223 | /* Formats date as ISO 8601 timestamp */ 224 | private function getFormattedTimestamp() 225 | { 226 | return gmdate("Ymd\THis\\Z", time()); 227 | } 228 | 229 | private function getCanonicalHeaders($headers) 230 | { 231 | $sortedCanonicalArray = array(); 232 | foreach ($headers as $key => $val) { 233 | if ((is_null($val)) || ($val === '')) {} 234 | else { 235 | $sortedCanonicalArray[strtolower($key)] = $val; 236 | } 237 | } 238 | ksort($sortedCanonicalArray); 239 | 240 | return $sortedCanonicalArray; 241 | } 242 | 243 | /* getCanonicalHeaderNames 244 | * Returns a string of the header names 245 | */ 246 | private function getCanonicalHeadersNames($headers) 247 | { 248 | $sortedHeader = $this->getCanonicalHeaders($headers); 249 | foreach (array_keys($sortedHeader) as $key) { 250 | $parameters[] = $key; 251 | } 252 | ksort($parameters); 253 | return implode(';', $parameters); 254 | } 255 | 256 | /* getHost 257 | * Returns the host 258 | */ 259 | private function getHost($unEncodedURI) 260 | { 261 | if ($unEncodedURI === '') { 262 | return '/'; 263 | } 264 | 265 | $urlArray = parse_url($unEncodedURI); 266 | if (empty($urlArray['host'])) { 267 | return '/'; 268 | } 269 | else { 270 | return $urlArray['host']; 271 | } 272 | } 273 | 274 | /* getHeaderString 275 | * Returns the Canonical Headers as a string 276 | */ 277 | public function getHeaderString($headers) 278 | { 279 | $queryParameters = array(); 280 | $sortedHeaders = $this->getCanonicalHeaders($headers); 281 | 282 | foreach ($sortedHeaders as $key => $value) { 283 | if (is_array($value)) { 284 | $value = $this->collectSubVals($value); 285 | } 286 | else { 287 | $queryParameters[] = $key . ':' . $value; 288 | } 289 | } 290 | $returnString = implode("\n", $queryParameters); 291 | 292 | return $returnString . "\n"; 293 | } 294 | 295 | /* collectSubVals 296 | * Helper function to take an array and return the values as a string 297 | */ 298 | private function collectSubVals($parameters) 299 | { 300 | $categoryIndex = 0; 301 | $collectedValues = ''; 302 | 303 | foreach ($parameters as $value) { 304 | if ($categoryIndex != 0) { 305 | $collectedValues .= ' '; 306 | } 307 | $collectedValues .= $value; 308 | $categoryIndex++; 309 | } 310 | 311 | return $collectedValues; 312 | } 313 | 314 | /* stringFromArray - helper function used to check if parameters is an array. 315 | * If it is an array it returns all the values as a string 316 | * Otherwise it returns parameters 317 | */ 318 | private function stringFromArray($parameters) 319 | { 320 | if (is_array($parameters)) { 321 | return $this->collectSubVals($parameters); 322 | } 323 | else { 324 | return $parameters; 325 | } 326 | } 327 | 328 | /* Create the User Agent Header sent with the POST request */ 329 | /* Protected because of PSP module usage */ 330 | protected function constructUserAgentHeader() 331 | { 332 | return 'amazon-pay-api-sdk-php/' . self::SDK_VERSION . ' (' 333 | . 'PHP/' . phpversion() . '; ' 334 | . self::getPhpUname('s') . '/' . self::getPhpUname('m') . '/' . self::getPhpUname('r') . ')'; 335 | } 336 | 337 | /** 338 | * Retrieves system information using the php_uname function if it's enabled 339 | * 340 | * @param mode - A mode specifying the information to retrieve (e.g., 's' for the System name) 341 | * @return string - System information or '(disabled)' if php_uname is disabled 342 | */ 343 | public function getPhpUname($mode) { 344 | $uname_disabled = self::_isDisabled(ini_get('disable_functions'), 'php_uname'); 345 | return $uname_disabled ? '(disabled)' : php_uname($mode); 346 | } 347 | 348 | /** 349 | * Checks if a given function is disabled based on a comma-seperated string of disabled functions. 350 | * 351 | * @param string $disableFunctionsOutput - String value of the 'disable_function' setting, as output by ini_get('disable_functions') 352 | * @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled 353 | * 354 | * @return bool - True if the function is disabled, false otherwise 355 | */ 356 | private static function _isDisabled($disableFunctionsOutput, $functionName) { 357 | $disabledFunctions = explode(',', $disableFunctionsOutput); 358 | if(in_array($functionName, $disabledFunctions)) { 359 | return true; 360 | } 361 | return false; 362 | } 363 | 364 | public function getPostSignedHeaders($http_request_method, $request_uri, $request_parameters, $request_payload, $other_presigned_headers = null) 365 | { 366 | $preSignedHeaders = array(); 367 | $preSignedHeaders['accept'] = 'application/json'; 368 | $preSignedHeaders['content-type'] = 'application/json'; 369 | $preSignedHeaders['X-Amz-Pay-Region'] = $this->config['region']; 370 | 371 | // Header x-amz-pay-idempotency-key is a special user-supplied header that must be pre-signed if used 372 | if (isset($other_presigned_headers)) { 373 | foreach ($other_presigned_headers as $key => $val) { 374 | if (strtolower($key) === 'x-amz-pay-idempotency-key') { 375 | if (isset($val) && ($val !== '')) { 376 | $preSignedHeaders['x-amz-pay-idempotency-key'] = $val; 377 | } 378 | } 379 | } 380 | } 381 | 382 | $timeStamp = $this->getFormattedTimestamp(); 383 | $signature = $this->createSignature($http_request_method, $request_uri, $request_parameters, $preSignedHeaders, $request_payload, $timeStamp); 384 | $public_key_id = $this->config['public_key_id']; 385 | 386 | $headers = $this->getCanonicalHeaders($preSignedHeaders); 387 | $headers['X-Amz-Pay-Date'] = $timeStamp; 388 | $headers['X-Amz-Pay-Host'] = $this->getHost($request_uri); 389 | $signedHeaders = "SignedHeaders=" . $this->getCanonicalHeadersNames($headers) . ", Signature=" . $signature; 390 | 391 | // Do not add x-amz-pay-idempotency-key header here, as user-supplied headers get added later 392 | $headerArray = array( 393 | 'accept' => $this->stringFromArray($headers['accept']), 394 | 'content-type' => $this->stringFromArray($headers['content-type']), 395 | 'x-amz-pay-host' => $this->getHost($request_uri), 396 | 'x-amz-pay-date' => $timeStamp, 397 | 'x-amz-pay-region' => $this->config['region'], 398 | 'x-amz-pay-sdk-type' => self::SDK_LANGUAGE, 399 | 'x-amz-pay-sdk-version' => self::SDK_VERSION, 400 | 'x-amz-pay-language-version' => PHP_VERSION, 401 | 'authorization' => $this->getAlgorithm() . " PublicKeyId=" . $public_key_id . ", " . $signedHeaders, 402 | 'user-agent' => $this->constructUserAgentHeader() 403 | ); 404 | 405 | if(isset($this->config['integrator_id'])){ 406 | $headerArray['x-amz-pay-integrator-id'] = $this->config['integrator_id']; 407 | } 408 | 409 | if(isset($this->config['integrator_version'])){ 410 | $headerArray['x-amz-pay-integrator-version'] = $this->config['integrator_version']; 411 | } 412 | 413 | if(isset($this->config['platform_version'])){ 414 | $headerArray['x-amz-pay-platform-version'] = $this->config['platform_version']; 415 | } 416 | 417 | ksort($headerArray); 418 | foreach ($headerArray as $key => $value) { 419 | $queryParameters[] = $key . ':' . $value; 420 | } 421 | 422 | return $queryParameters; 423 | } 424 | 425 | public function createSignature($http_request_method, $request_uri, $request_parameters, $pre_signed_headers, $request_payload, $timeStamp) 426 | { 427 | $rsa = $this->setupRSA(); 428 | 429 | $pre_signed_headers['X-Amz-Pay-Date'] = $timeStamp; 430 | $pre_signed_headers['X-Amz-Pay-Host'] = $this->getHost($request_uri); 431 | 432 | $hashedPayload = $this->hexAndHash($request_payload); 433 | $canonicalURI = $this->getCanonicalURI($request_uri); 434 | $canonicalQueryString = $this->createCanonicalQuery($request_parameters); 435 | $canonicalHeader = $this->getHeaderString($pre_signed_headers); 436 | $signedHeaders = $this->getCanonicalHeadersNames($pre_signed_headers); 437 | 438 | $canonicalRequest = ( 439 | $http_request_method . "\n" . 440 | $canonicalURI . "\n" . 441 | $canonicalQueryString . "\n" . 442 | $canonicalHeader . "\n" . 443 | $signedHeaders . "\n" . 444 | $hashedPayload 445 | ); 446 | 447 | $hashedCanonicalRequest = $this->getAlgorithm() . "\n" . $this->hexAndHash($canonicalRequest); 448 | 449 | $signature = $rsa->sign($hashedCanonicalRequest); 450 | if ($signature === false) { 451 | throw new \Exception('Unable to sign request, is your RSA private key valid?'); 452 | } 453 | 454 | return base64_encode($signature); 455 | } 456 | 457 | 458 | public function generateButtonSignature($payload) { 459 | $rsa = $this->setupRSA(); 460 | 461 | // if array is passed in, developer will need to ensure same json_encode function is used on website, 462 | // otherwise the signed payload may not match if a different JSON to string function is used which may 463 | // generate a string with different whitespace 464 | if (is_array($payload)) { 465 | $payload = json_encode($payload); 466 | } 467 | 468 | // stripcslashes function is used on payload to unescape sequences like http:\/\/ to http:// 469 | // and \"hello\" to "hello" 470 | $hashedButtonRequest = $this->getAlgorithm() . "\n" . $this->hexAndHash(isset($payload) ? stripcslashes($payload) : ''); 471 | $signature = $rsa->sign($hashedButtonRequest); 472 | if ($signature === false) { 473 | throw new \Exception('Unable to sign request, is your RSA private key valid?'); 474 | } 475 | 476 | return base64_encode($signature); 477 | } 478 | 479 | 480 | private function setupRSA() { 481 | $key_spec = $this->config['private_key']; 482 | $salt_length = $this->getAlgorithm() === 'AMZN-PAY-RSASSA-PSS' ? 20 : 32; 483 | if ((strpos($key_spec, 'BEGIN RSA PRIVATE KEY') === false) && (strpos($key_spec, 'BEGIN PRIVATE KEY') === false)) { 484 | $contents = file_get_contents($key_spec); 485 | if ($contents === false) { 486 | throw new \Exception('Unable to load file: ' . $key_spec); 487 | } 488 | $rsa = RSA::loadPrivateKey($contents)->withSaltLength($salt_length); 489 | } else { 490 | $rsa = RSA::loadPrivateKey($key_spec)->withSaltLength($salt_length); 491 | } 492 | return $rsa; 493 | } 494 | 495 | 496 | public function testPrivateKeyIntegrity() { 497 | echo "Testing private key: "; 498 | 499 | $rsa = $this->setupRSA(); 500 | 501 | for ($i = 0; $i < 100; $i++) { 502 | echo ($i+1) . " "; 503 | $rsa->sign("TESTING PRIVATE KEY"); 504 | } 505 | echo "[Passed]\n"; 506 | 507 | return true; 508 | } 509 | 510 | private function enhanceResponseWithShippingAddressList( &$response ) { 511 | $responsePayload = json_decode($response['response'], true); 512 | 513 | if(isset($responsePayload['shippingAddressList'])) { 514 | $responsePayload['shippingAddressList'] = array_map(function ($shippingAddress) { 515 | return json_decode($shippingAddress, true); 516 | }, $responsePayload['shippingAddressList']); 517 | $response['response'] = json_encode($responsePayload, JSON_UNESCAPED_UNICODE); 518 | } 519 | 520 | return $response; 521 | } 522 | 523 | public function apiCall($method, $urlFragment, $payload, $headers = null, $queryParams = null) { 524 | if (is_array($payload)) { 525 | 526 | // json_encode will fail if non-UTF-8 encodings are present, need to convert them to UTF-8 527 | array_walk_recursive($payload, function (&$item) { 528 | if (is_string($item) && mb_detect_encoding($item, 'UTF-8', true) === false) { 529 | $item = mb_convert_encoding($item, 'UTF-8'); 530 | } 531 | }); 532 | 533 | $payload = json_encode($payload); 534 | } 535 | 536 | $url = $this->createServiceUrl() . $urlFragment; 537 | if (isset($queryParams)) { 538 | if (!is_array($queryParams)) { 539 | throw new \Exception('queryParameters must be a key-value array; e.g. array(\'accountId\' => \'ABCD1234XYZIJK\')'); 540 | } 541 | $url = $url . '?' . $this->createCanonicalQuery($queryParams); 542 | $requestParameters = $queryParams; 543 | } else { 544 | $requestParameters = array(); 545 | } 546 | 547 | $postSignedHeaders = $this->getPostSignedHeaders($method, $url, $requestParameters, $payload, $headers); 548 | if (isset($headers)) { 549 | if (!is_array($headers)) { 550 | throw new \Exception('headers must be a key-value array; e.g. array(\'x-amz-pay-authtoken\' => \'abcd1234xyzIJK\')'); 551 | } 552 | foreach ($headers as $key => $value) { 553 | $postSignedHeaders[] = $key . ':' . $value; 554 | } 555 | } 556 | 557 | $httpCurlRequest = new HttpCurl(isset($this->config['proxy']) ? $this->config['proxy'] : []); 558 | return $httpCurlRequest->invokeCurl($method, $url, $payload, $postSignedHeaders); 559 | } 560 | 561 | /* Setter for sandbox 562 | * @param value - [boolean] 563 | * Sets the boolean value for config['sandbox'] variable 564 | */ 565 | public function setSandbox($value) 566 | { 567 | if (is_bool($value)) { 568 | $this->config['sandbox'] = $value; 569 | } else { 570 | throw new \Exception('sandbox value ' . $value . ' is of type ' . gettype($value) . ' and should be a boolean value'); 571 | } 572 | } 573 | 574 | // ----------------------------------- DELIVERY NOTIFICATIONS API ----------------------------------- 575 | 576 | 577 | public function deliveryTrackers($payload, $headers = null) 578 | { 579 | // Current implementation on deliveryTrackers API does not support the use of auth token 580 | return $this->apiCall('POST', self::API_VERSION . '/deliveryTrackers', $payload, $headers); 581 | } 582 | 583 | 584 | // ----------------------------------- AUTHORIZATION TOKENS API ----------------------------------- 585 | 586 | public function getAuthorizationToken($mwsAuthToken, $merchantId, $headers = null) 587 | { 588 | 589 | $queryParams = array('merchantId' => $merchantId); 590 | return $this->apiCall('GET', self::API_VERSION . '/authorizationTokens/' . $mwsAuthToken, null, $headers, $queryParams); 591 | } 592 | 593 | 594 | // ----------------------------------- IN-STORE API ----------------------------------- 595 | 596 | 597 | public function instoreMerchantScan($payload, $headers = null) 598 | { 599 | return $this->apiCall('POST', self::API_VERSION . '/in-store/merchantScan', $payload, $headers); 600 | } 601 | 602 | 603 | public function instoreCharge($payload, $headers = null) 604 | { 605 | return $this->apiCall('POST', self::API_VERSION . '/in-store/charge', $payload, $headers); 606 | } 607 | 608 | 609 | public function instoreRefund($payload, $headers = null) 610 | { 611 | return $this->apiCall('POST', self::API_VERSION . '/in-store/refund', $payload, $headers); 612 | } 613 | 614 | 615 | // ----------------------------------- Amazon Checkout v2 API ----------------------------------- 616 | 617 | 618 | public function getBuyer($buyerToken, $headers = null) { 619 | return $this->apiCall('GET', self::API_VERSION . '/buyers/' . $buyerToken, null, $headers); 620 | } 621 | 622 | public function createCheckoutSession($payload, $headers) 623 | { 624 | return $this->apiCall('POST', self::API_VERSION . '/checkoutSessions', $payload, $headers); 625 | } 626 | 627 | 628 | public function getCheckoutSession($checkoutSessionId, $headers = null) 629 | { 630 | $response = $this->apiCall('GET', self::API_VERSION . '/checkoutSessions/' . $checkoutSessionId, null, $headers); 631 | return $this->enhanceResponseWithShippingAddressList($response); 632 | } 633 | 634 | 635 | public function updateCheckoutSession($checkoutSessionId, $payload, $headers = null) 636 | { 637 | $response = $this->apiCall('PATCH', self::API_VERSION . '/checkoutSessions/' . $checkoutSessionId, $payload, $headers); 638 | return $this->enhanceResponseWithShippingAddressList($response); 639 | } 640 | 641 | 642 | public function completeCheckoutSession($checkoutSessionId, $payload, $headers = null) 643 | { 644 | $response = $this->apiCall('POST', self::API_VERSION . '/checkoutSessions/' . $checkoutSessionId . '/complete', $payload, $headers); 645 | return $this->enhanceResponseWithShippingAddressList($response); 646 | } 647 | 648 | 649 | public function getChargePermission($chargePermissionId, $headers = null) 650 | { 651 | return $this->apiCall('GET', self::API_VERSION . '/chargePermissions/' . $chargePermissionId, null, $headers); 652 | } 653 | 654 | 655 | public function updateChargePermission($chargePermissionId, $payload, $headers = null) 656 | { 657 | return $this->apiCall('PATCH', self::API_VERSION . '/chargePermissions/' . $chargePermissionId, $payload, $headers); 658 | } 659 | 660 | 661 | public function closeChargePermission($chargePermissionId, $payload, $headers = null) 662 | { 663 | return $this->apiCall('DELETE', self::API_VERSION . '/chargePermissions/' . $chargePermissionId . '/close', $payload, $headers); 664 | } 665 | 666 | 667 | public function createCharge($payload, $headers) 668 | { 669 | return $this->apiCall('POST', self::API_VERSION . self::CHARGES, $payload, $headers); 670 | } 671 | 672 | public function updateCharge($chargeId, $payload, $headers) 673 | { 674 | return $this->apiCall('PATCH', self::API_VERSION . self::CHARGES . '/' . $chargeId, $payload, $headers); 675 | } 676 | 677 | 678 | public function getCharge($chargeId, $headers = null) 679 | { 680 | return $this->apiCall('GET', self::API_VERSION . self::CHARGES . '/' . $chargeId, null, $headers); 681 | } 682 | 683 | 684 | public function captureCharge($chargeId, $payload, $headers) 685 | { 686 | return $this->apiCall('POST', self::API_VERSION . self::CHARGES . '/' . $chargeId . '/capture', $payload, $headers); 687 | } 688 | 689 | 690 | public function cancelCharge($chargeId, $payload, $headers = null) 691 | { 692 | return $this->apiCall('DELETE', self::API_VERSION . self::CHARGES . '/' . $chargeId . '/cancel', $payload, $headers); 693 | } 694 | 695 | 696 | public function createRefund($payload, $headers) 697 | { 698 | return $this->apiCall('POST', self::API_VERSION . '/refunds', $payload, $headers); 699 | } 700 | 701 | 702 | public function getRefund($refundId, $headers = null) 703 | { 704 | return $this->apiCall('GET', self::API_VERSION . '/refunds/' . $refundId, null, $headers); 705 | } 706 | 707 | // ----------------------------------- CV2 REPORTING APIS ----------------------------------- 708 | 709 | 710 | public function getReports($queryParameters = null, $headers = null) 711 | { 712 | return $this->apiCall('GET', self::API_VERSION . '/reports', null, $headers, $queryParameters); 713 | } 714 | 715 | 716 | public function getReportById($reportId, $headers = null) 717 | { 718 | return $this->apiCall('GET', self::API_VERSION . '/reports/' . $reportId, null, $headers); 719 | } 720 | 721 | 722 | public function getReportDocument($reportDocumentId, $headers = null) 723 | { 724 | return $this->apiCall('GET', self::API_VERSION . '/report-documents/' . $reportDocumentId, null, $headers); 725 | } 726 | 727 | 728 | public function getReportSchedules($reportTypes = null, $headers = null) 729 | { 730 | $queryParameters = array('reportTypes' => $reportTypes); 731 | return $this->apiCall('GET', self::API_VERSION . '/report-schedules', null, $headers, $queryParameters); 732 | } 733 | 734 | 735 | public function getReportScheduleById($reportScheduleId, $headers = null) 736 | { 737 | return $this->apiCall('GET', self::API_VERSION . '/report-schedules/' . $reportScheduleId, null, $headers); 738 | } 739 | 740 | 741 | public function createReport($requestPayload, $headers = null) 742 | { 743 | return $this->apiCall('POST', self::API_VERSION . '/reports' , $requestPayload, $headers); 744 | } 745 | 746 | 747 | public function createReportSchedule($requestPayload, $headers = null) 748 | { 749 | return $this->apiCall('POST', self::API_VERSION . '/report-schedules' , $requestPayload, $headers); 750 | } 751 | 752 | 753 | public function cancelReportSchedule($reportScheduleId, $headers = null) 754 | { 755 | return $this->apiCall('DELETE', self::API_VERSION . '/report-schedules/' . $reportScheduleId, null, $headers); 756 | } 757 | 758 | public function getDisbursements($queryParameters, $headers = null) 759 | { 760 | return $this->apiCall('GET', self::API_VERSION . self::DISBURSEMENTS, null, $headers, $queryParameters); 761 | } 762 | 763 | /* 764 | * FinalizeCheckoutSession API which enables Pay to validate payment critical attributes and also update book-keeping attributes present in merchantMetadata 765 | */ 766 | public function finalizeCheckoutSession($checkoutSessionId, $payload, $headers = null) 767 | { 768 | return $this->apicall('POST', self::API_VERSION . '/checkoutSessions/' . $checkoutSessionId . '/finalize', $payload , $headers); 769 | } 770 | 771 | // ----------------------------------- Merchant Onboarding & Account Management APIs ----------------------------------- 772 | 773 | public function registerAmazonPayAccount($payload, $headers = null) 774 | { 775 | return $this->apiCall('POST', self::API_VERSION . self::ACCOUNT_MANAGEMENT, $payload, $headers); 776 | } 777 | 778 | public function updateAmazonPayAccount($merchantAccountId, $payload, $headers = null) 779 | { 780 | return $this->apiCall('PATCH', self::API_VERSION . self::ACCOUNT_MANAGEMENT . '/' . $merchantAccountId, $payload, $headers); 781 | } 782 | 783 | public function deleteAmazonPayAccount($merchantAccountId, $headers = null) 784 | { 785 | return $this->apiCall('DELETE', self::API_VERSION . self::ACCOUNT_MANAGEMENT . '/' . $merchantAccountId, null, $headers); 786 | } 787 | 788 | // ----------------------------------- Account Management APIs ----------------------------------- 789 | 790 | public function createMerchantAccount($payload, $headers) 791 | { 792 | return $this->apiCall('POST', self::API_VERSION . self::ACCOUNT_MANAGEMENT, $payload, $headers); 793 | } 794 | 795 | public function updateMerchantAccount($merchantAccountId, $payload, $headers) 796 | { 797 | return $this->apiCall('PATCH', self::API_VERSION . self::ACCOUNT_MANAGEMENT . '/' . $merchantAccountId, $payload, $headers); 798 | } 799 | 800 | public function claimMerchantAccount($merchantAccountId, $payload, $headers = null) 801 | { 802 | return $this->apiCall('POST', self::API_VERSION . self::ACCOUNT_MANAGEMENT . '/' . $merchantAccountId . self::CLAIM, $payload, $headers); 803 | } 804 | 805 | // ----------------------------------- Dispute APIs ----------------------------------- 806 | 807 | public function createDispute($payload, $headers) { 808 | return $this->apiCall('POST', self::API_VERSION . self::DISPUTES, $payload, $headers); 809 | } 810 | 811 | public function getDispute($disputeId, $headers = null){ 812 | return $this->apiCall('GET', self::API_VERSION . self::DISPUTES . '/' . $disputeId, null, $headers); 813 | } 814 | 815 | public function updateDispute($disputeId, $payload, $headers = null) { 816 | return $this->apiCall('PATCH', self::API_VERSION . self::DISPUTES . '/' . $disputeId, $payload, $headers); 817 | } 818 | 819 | public function contestDispute($disputeId, $payload, $headers = null) { 820 | return $this->apiCall('POST', self::API_VERSION . self::DISPUTES . '/' . $disputeId . self::CONTEST, $payload, $headers); 821 | } 822 | 823 | // ----------------------------------- File APIs ----------------------------------- 824 | public function uploadFile($payload, $headers) { 825 | return $this->apiCall('POST', self::API_VERSION . self::FILES, $payload, $headers); 826 | } 827 | } 828 | -------------------------------------------------------------------------------- /Amazon/Pay/API/ClientInterface.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Amazon/Pay/API/HttpCurl.php: -------------------------------------------------------------------------------- 1 | proxyConfig = $proxyConfig; 22 | } 23 | 24 | private function header_callback($ch, $header_line) 25 | { 26 | $headers[] = $header_line; 27 | 28 | foreach($headers as $part) { 29 | $middle = explode(":", $part, 2); 30 | if (isset($middle[1])) { 31 | $key = strtolower(trim($middle[0])); 32 | if ($key === 'x-amz-pay-request-id') { 33 | $this->requestId = trim($middle[1]); 34 | } 35 | if ($key === 'location') { 36 | $this->location = trim($middle[1]); 37 | } 38 | } 39 | } 40 | 41 | return strlen($header_line); 42 | } 43 | 44 | private function commonCurlParams($url) 45 | { 46 | $ch = curl_init(); 47 | curl_setopt($ch, CURLOPT_URL, $url); 48 | curl_setopt($ch, CURLOPT_PORT, 443); 49 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 50 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); 51 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 52 | curl_setopt($ch, CURLOPT_HEADER, false); 53 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'header_callback')); 54 | 55 | if ($this->useProxy()) { 56 | curl_setopt($ch, CURLOPT_PROXY, $this->proxyConfig['host'] . ':' . $this->proxyConfig['port']); 57 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxyConfig['username'] . ':' . $this->proxyConfig['password']); 58 | } 59 | 60 | return $ch; 61 | } 62 | 63 | 64 | /* Send using curl */ 65 | private function httpSend($method, $url, $payload, $postSignedHeaders) 66 | { 67 | // Ensure we never send the "Expect: 100-continue" header by adding 68 | // an 'Expect:' header to the end of the headers 69 | $postSignedHeaders[] = 'Expect:'; 70 | 71 | $ch = $this->commonCurlParams($url); 72 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); 73 | curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); 74 | curl_setopt($ch, CURLOPT_HTTPHEADER, $postSignedHeaders); 75 | 76 | return $this->execute($ch); 77 | } 78 | 79 | /* Execute Curl request */ 80 | private function execute($ch) 81 | { 82 | $response = curl_exec($ch); 83 | if ($response === false) { 84 | $error_msg = "Unable to send request, underlying exception of " . curl_error($ch); 85 | curl_close($ch); 86 | throw new \Exception($error_msg); 87 | } else { 88 | $this->curlResponseInfo = curl_getinfo($ch); 89 | } 90 | curl_close($ch); 91 | return $response; 92 | } 93 | 94 | /* invokeCurl takes the parameters and invokes the httpSend function to transmit the parameters 95 | * Exponential retries on error 429, 500, and 503 96 | * Function returns an array of troubleshooting data, response from the request is in ['response'] 97 | */ 98 | public function invokeCurl($method, $url, $payload, $postSignedHeaders) 99 | { 100 | $curtime = microtime(true); 101 | $response = array(); 102 | $statusCode = 200; 103 | 104 | // Submit the request and read response body 105 | try { 106 | $shouldRetry = true; 107 | $retries = 0; 108 | do { 109 | try { 110 | $response = $this->httpSend($method, $url, $payload, $postSignedHeaders); 111 | $curlResponseInfo = $this->curlResponseInfo; 112 | $statusCode = $curlResponseInfo["http_code"]; 113 | $response = array( 114 | 'status' => $statusCode, 115 | 'method' => $method, 116 | 'location' => $this->location, 117 | 'url' => $url, 118 | 'headers' => $postSignedHeaders, 119 | 'request' => $payload, 120 | 'response' => $response, 121 | 'request_id' => $this->requestId, 122 | 'retries' => $retries, 123 | 'duration' => intval(round((microtime(true)-$curtime) * 1000)) 124 | ); 125 | 126 | $statusCode = $response['status']; 127 | $shouldRetry = false; 128 | $retryCodes = array(408, 425,429, 500, 502, 503, 504); 129 | if (in_array($statusCode, $retryCodes)) { 130 | $this->pauseOnRetry(++$retries); 131 | $shouldRetry = $retries <= self::MAX_ERROR_RETRY; 132 | } 133 | } catch (\Exception $e) { 134 | throw $e; 135 | } 136 | } while ($shouldRetry); 137 | } catch (\Exception $se) { 138 | throw $se; 139 | } 140 | 141 | return $response; 142 | } 143 | 144 | /* Exponential sleep on failed request 145 | * Up to three retries will occur if first request fails 146 | * after 1.0 second, 2.2 seconds, and finally 7.0 seconds 147 | * @param retries current retry 148 | * @throws Exception if maximum number of retries has been reached 149 | */ 150 | private function pauseOnRetry($retries) 151 | { 152 | if ($retries <= self::MAX_ERROR_RETRY) { 153 | // PHP delays are in microseconds (1 million microsecond = 1 sec) 154 | // 1st delay is (4^1) * 100000 + 600000 = 0.4 + 0.6 second = 1.0 sec 155 | // 2nd delay is (4^2) * 100000 + 600000 = 1.6 + 0.6 second = 2.2 sec 156 | // 3rd delay is (4^3) * 100000 + 600000 = 6.4 + 0.6 second = 7.0 sec 157 | $delay = (int) (pow(4, $retries) * 100000) + 600000; 158 | usleep($delay); 159 | } 160 | } 161 | 162 | private function useProxy() { 163 | return !empty($this->proxyConfig['username']) && !empty($this->proxyConfig['password']) && !empty($this->proxyConfig['host']) && !empty($this->proxyConfig['port']); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /Amazon/Pay/API/MerchantOnboardingClientInterface.php: -------------------------------------------------------------------------------- 1 | 29 | ``` 30 | 31 | ## Public and Private Keys 32 | 33 | MWS access keys, MWS secret keys, and MWS authorization tokens from previous MWS integrations cannot be used with this SDK. 34 | 35 | You will need to generate your own public/private key pair to make API calls with this SDK. 36 | 37 | In Windows 10 this can be done with ssh-keygen commands: 38 | 39 | ``` 40 | ssh-keygen -t rsa -b 2048 -f private.pem 41 | ssh-keygen -f private.pem -e -m PKCS8 > public.pub 42 | ``` 43 | 44 | In Linux or macOS this can be done using openssl commands: 45 | 46 | ``` 47 | openssl genrsa -out private.pem 2048 48 | openssl rsa -in private.pem -pubout > public.pub 49 | ``` 50 | 51 | The first command above generates a private key and the second line uses the private key to generate a public key. 52 | 53 | To associate the key with your account, follow the instructions here to 54 | [Get your Public Key ID](https://developer.amazon.com/docs/amazon-pay-checkout/get-set-up-for-integration.html#5-get-your-public-key-id). 55 | 56 | ## Namespace 57 | 58 | Namespace for this package is Amazon\Pay\API so that there are no conflicts with the original Amazon Pay MWS SDK's that use the AmazonPay namespace. 59 | 60 | ## Configuration Array 61 | 62 | ```php 63 | $amazonpay_config = array( 64 | 'public_key_id' => 'ABC123DEF456XYZ', // RSA Public Key ID (this is not the Merchant or Seller ID) 65 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 66 | 'sandbox' => true, // true (Sandbox) or false (Production) boolean 67 | 'region' => 'us', // Must be one of: 'us', 'eu', 'jp' 68 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' //Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified 69 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 70 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 71 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 72 | ); 73 | ``` 74 | If you have created environment specific keys (i.e Public Key Starts with LIVE or SANDBOX) in Seller Central, then use those PublicKeyId & PrivateKey. In this case, there is no need to pass the Sandbox parameter to the ApiConfiguration. 75 | 76 | ```php 77 | $amazonpay_config = array( 78 | 'public_key_id' => 'MY_PUBLIC_KEY_ID', // LIVE-XXXXX or SANDBOX-XXXXX 79 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 80 | 'region' => 'us', // Must be one of: 'us', 'eu', 'jp' 81 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' //Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified 82 | ); 83 | ``` 84 | 85 | If you have want to enable proxy support, you can set it in the $amazonpay_config in the following way: 86 | ```php 87 | $amazonpay_config = array( 88 | 'public_key_id' => 'ABC123DEF456XYZ', // RSA Public Key ID (this is not the Merchant or Seller ID) 89 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 90 | 'sandbox' => true, // true (Sandbox) or false (Production) boolean 91 | 'region' => 'us', // Must be one of: 'us', 'eu', 'jp' 92 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', //Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified 93 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 94 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 95 | 'platform_version' => '0.0.4', // (optional) Solution Provider Platform Version in Semantic Versioning Format 96 | 'proxy' => [ 97 | 'host' => 'proxy_host', 98 | 'port' => 'proxy_port', 99 | 'username' => 'proxy_username', 100 | 'password' => 'proxy_password', 101 | ] 102 | ); 103 | ``` 104 | # Versioning 105 | 106 | The pay-api.amazon.com|eu|jp endpoint uses versioning to allow future updates. The major version of this SDK will stay aligned with the API version of the endpoint. 107 | 108 | If you have downloaded version 1.x.y of this SDK, $version in below examples would be "v1". 2.x.y would be "v2", etc. 109 | 110 | # Convenience Functions (Overview) 111 | 112 | Make use of the built-in convenience functions to easily make API calls. Scroll down further to see example code snippets. 113 | 114 | When using the convenience functions, the request payload will be signed using the provided private key, and a HTTPS request is made to the correct regional endpoint. 115 | In the event of request throttling, the HTTPS call will be attempted up to three times using an exponential backoff approach. 116 | 117 | ## Alexa Delivery Trackers API 118 | Use this API to provide shipment tracking information to Amazon Pay so that Amazon Pay can notify buyers on Alexa when shipments are out for delivery and when they are delivered. Please refer to the [Delivery Trackers API documentation](https://developer.amazon.com/docs/amazon-pay-onetime/delivery-order-notifications.html) for additional information. 119 | 120 | * **deliveryTrackers**($payload, $headers = null) → POST to "$version/deliveryTrackers" 121 | 122 | ## Authorization Tokens API 123 | Please note that your solution provider account must have a pre-existing relationship (valid and active MWS authorization token) with the merchant account in order to use this function. 124 | 125 | * **getAuthorizationToken**($mwsAuthToken, $merchantId, $headers = null) → GET to "$version/authorizationTokens/$mwsAuthToken?merchantId=$merchantId" 126 | 127 | ## Amazon Checkout v2 API 128 | [API Integration Guide](https://developer.amazon.com/docs/amazon-pay-api-v2/introduction.html) 129 | 130 | The $headers field is not optional for create/POST calls below because it requires, at a minimum, the x-amz-pay-idempotency-key header: 131 | 132 | ```php 133 | $headers = array('x-amz-pay-idempotency-key' => uniqid()); 134 | ``` 135 | 136 | ### Amazon Checkout v2 Buyer APIs 137 | * **getBuyer**($buyerToken, $headers = null) → GET to "$version/buyers/$buyerToken" 138 | 139 | ### Amazon Checkout v2 CheckoutSession APIs 140 | * **createCheckoutSession**($payload, $headers) → POST to "$version/checkoutSessions" 141 | * **getCheckoutSession**($checkoutSessionId, $headers = null) → GET to "$version/checkoutSessions/$checkoutSessionId" 142 | * **updateCheckoutSession**($checkoutSessionId, $payload, $headers = null) → PATCH to "$version/checkoutSessions/$checkoutSessionId" 143 | * **completeCheckoutSession**($checkoutSessionId, $payload, $headers = null) → POST to "$version/checkoutSessions/$checkoutSessionId/complete" 144 | 145 | ### Amazon Checkout v2 ChargePermission APIs 146 | * **getChargePermission**($chargePermissionId, $headers = null) → GET to "$version/chargePermissions/$chargePermissionId" 147 | * **updateChargePermission**($chargePermissionId, $payload, $headers = null) → PATCH to "$version/chargePermissions/$chargePermissionId" 148 | * **closeChargePermission**($chargePermissionId, $payload, $headers = null) → DELETE to "$version/chargePermissions/$chargePermissionId/close" 149 | 150 | ### Amazon Checkout v2 Charge APIs 151 | * **createCharge**($payload, $headers) → POST to "$version/charges" 152 | * **getCharge**($chargeId, $headers = null) → GET to "$version/charges/$chargeId" 153 | * **updateCharge**($chargeId, $payload, $headers) → PATCH to "$version/charges/$chargeId" 154 | * **captureCharge**($chargeId, $payload, $headers) → POST to "$version/charges/$chargeId/capture" 155 | * **cancelCharge**($chargeId, $payload, $headers = null) → DELETE to "$version/charges/$chargeId/cancel" 156 | 157 | ### Amazon Checkout v2 Refund APIs 158 | * **createRefund**($payload, $headers) → POST to "$version/refunds" 159 | * **getRefund**($refundId, $headers = null) → GET to "$version/refunds/$refundId" 160 | 161 | ### Amazon Pay v2 Reporting APIs 162 | * **getReports**($queryParameters = null, $headers = null) → GET to $version/reports 163 | * **getReportById**($reportId, $headers = null) → GET to $version/reports/&reportId 164 | * **getReportDocument**($reportDocumentId, $headers = null) → GET to $version/report-documents/$reportDocumentI 165 | * **getReportSchedules**($reportTypes = null, $headers = null) → GET to $version/report-schedules 166 | * **getReportScheduleById**($reportScheduleId, $headers = null) → GET to $version/report-schedules/$reportScheduleId 167 | * **createReport**($requestPayload, $headers = null) → POST to $version/reports 168 | * **createReportSchedule**($requestPayload, $headers = null) → POST to $version/report-schedules 169 | * **cancelReportSchedule**($reportScheduleId, $headers = null) → DELETE to $version/report-schedules/$reportScheduleId 170 | * **getDisbursements**($queryParameters, $headers = null) → GET to $version/disbursements 171 | 172 | ## In-Store APIs 173 | Please contact your Amazon Pay Account Manager before using the In-Store API calls in a Production environment to obtain a copy of the In-Store Integration Guide. 174 | 175 | * **instoreMerchantScan**($payload, $headers = null) → POST to "$version/in-store/merchantScan" 176 | * **instoreCharge**($payload, $headers = null) → POST to "$version/in-store/charge" 177 | * **instoreRefund**($payload, $headers = null) → POST to "$version/in-store/refund" 178 | 179 | ### Amazon Checkout v2 SPC APIs 180 | * **finalizeCheckoutSession**($checkoutSessionId, $payload, $headers = null) → POST to "$version/checkoutSessions/$checkoutSessionId/finalize" 181 | 182 | ### Amazon Checkout v2 Merchant Onboarding & Account Management APIs 183 | * **registerAmazonPayAccount**($payload, $headers = null) → POST to "$version/merchantAccounts" 184 | * **updateAmazonPayAccount**($merchantAccountId, $payload, $headers = null) → PATCH to "$version/merchantAccounts/$merchantAccountId" 185 | * **deleteAmazonPayAccount**($merchantAccountId, $headers = null) → DELETE to "$version/merchantAccounts/$merchantAccountId" 186 | 187 | ### Amazon Checkout v2 Account Management APIs 188 | * **createMerchantAccount**($payload, $headers) → POST to "$version/merchantAccounts" 189 | * **updateMerchantAccount**($merchantAccountId, $payload, $headers) → PATCH to "$version/merchantAccounts/$merchantAccountId" 190 | * **claimMerchantAccount**($merchantAccountId, $payload, $headers) → POST to "$version/merchantAccounts/$merchantAccountId/claim" 191 | 192 | ### Amazon Checkout v2 Dispute APIs 193 | * **createDispute**($payload, $headers) → POST to $version/disputes 194 | * **getDispute**($disputeId, $headers = null) → GET to $version/disputes/$disputeId 195 | * **updateDispute**($disputeId, $payload, $headers = null) → PATCH to $version/disputes/$disputeId 196 | * **contestDispute**($disputeId, $payload, $headers = null) → POST to $version/disputes/$disputeId/contest 197 | 198 | ### Amazon Checkout v2 File APIs 199 | * **uploadFile**($payload, $headers) → POST to $version/files 200 | 201 | # Using Convenience Functions 202 | 203 | Four quick steps are needed to make an API call: 204 | 205 | Step 1. Construct a Client (using the previously defined Config Array). 206 | 207 | ```php 208 | $client = new Amazon\Pay\API\Client($amazonpay_config); 209 | ``` 210 | 211 | Step 2. Generate the payload. 212 | 213 | ```php 214 | $payload = '{"scanData":"UKhrmatMeKdlfY6b","scanReferenceId":"0b8fb271-2ae2-49a5-b35d7","merchantCOE":"US","ledgerCurrency":"USD","chargeTotal":{"currencyCode":"USD","amount":"2.00"},"metadata":{"merchantNote":"Merchant Name","communicationContext":{"merchantStoreName":"Store Name","merchantOrderId":"789123"}}}'; 215 | ``` 216 | 217 | Step 3. Execute the call. 218 | 219 | ```php 220 | $result = $client->instoreMerchantScan($payload); 221 | ``` 222 | 223 | Step 4. Check the result. 224 | 225 | The $result will be an array with the following keys: 226 | 227 | * '**status**' - integer HTTP status code (200, 201, etc.) 228 | * '**response**' - the JSON response body 229 | * '**request_id**' - the Request ID from Amazon API gateway 230 | * '**url**' - the URL for the REST call the SDK calls, for troubleshooting purposes 231 | * '**method** - POST, GET, PATCH, or DELETE 232 | * '**headers**' - an array containing the various headers generated by the SDK, for troubleshooting purposes 233 | * '**request**' - the JSON request payload 234 | * '**retries**' - usually 0, but reflects the number of times a request was retried due to throttling or other server-side issue 235 | * '**duration**' - duration in milliseconds of SDK function call 236 | 237 | The first two items (status, response) are critical. The remaining items are useful in troubleshooting situations. 238 | 239 | To parse the response in PHP, you can use the PHP json_decode() function: 240 | 241 | ```php 242 | $response = json_decode($result['response'], true); 243 | $id = $response['chargePermissionId']; 244 | ``` 245 | 246 | If you are a Solution Provider and need to make an API call on behalf of a different merchant account, you will need to pass along an extra authentication token parameter into the API call. 247 | 248 | ```php 249 | $headers = array('x-amz-pay-authtoken' => 'other_merchant_super_secret_token'); 250 | $result = $client->instoreMerchantScan($payload, $headers); 251 | ``` 252 | 253 | An alternate way to do Step 2 would be to use PHP arrays and programmatically generate the JSON payload: 254 | 255 | ```php 256 | $payload = array( 257 | 'scanData' => 'UKhrmatMeKdlfY6b', 258 | 'scanReferenceId' => uniqid(), 259 | 'merchantCOE' => 'US', 260 | 'ledgerCurrency' => 'USD', 261 | 'chargeTotal' => array( 262 | 'currencyCode' => 'USD', 263 | 'amount' => '2.00' 264 | ), 265 | 'metadata' => array( 266 | 'merchantNote' => 'Merchant Name', 267 | 'communicationContext' => array( 268 | 'merchantStoreName' => 'Store Name', 269 | 'merchantOrderId' => '789123' 270 | ) 271 | ) 272 | ); 273 | $payload = json_encode($payload); 274 | ``` 275 | # Convenience Functions Code Samples 276 | 277 | ## Alexa Delivery Notifications 278 | 279 | ```php 280 | 'MY_PUBLIC_KEY_ID', 285 | 'private_key' => 'keys/private.pem', 286 | 'region' => 'US', 287 | 'sandbox' => false, 288 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 289 | ); 290 | $payload = array( 291 | 'amazonOrderReferenceId' => 'P01-0000000-0000000', 292 | 'deliveryDetails' => array(array( 293 | 'trackingNumber' => '01234567890', 294 | 'carrierCode' => 'FEDEX' 295 | )) 296 | ); 297 | try { 298 | $client = new Amazon\Pay\API\Client($amazonpay_config); 299 | $result = $client->deliveryTrackers($payload); 300 | if ($result['status'] === 200) { 301 | // success 302 | echo $result['response'] . "\n"; 303 | } else { 304 | // check the error 305 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 306 | } 307 | } catch (\Exception $e) { 308 | // handle the exception 309 | echo $e . "\n"; 310 | } 311 | ?> 312 | ``` 313 | 314 | ## Amazon Checkout v2 - Create Checkout Session (AJAX service example) 315 | 316 | ```php 317 | 'MY_PUBLIC_KEY_ID', 324 | 'private_key' => 'keys/private.pem', 325 | 'region' => 'US', 326 | 'sandbox' => true, 327 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 328 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 329 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 330 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 331 | ); 332 | $payload = array( 333 | 'webCheckoutDetails' => array( 334 | 'checkoutReviewReturnUrl' => 'https://localhost/store/checkout_review', 335 | 'checkoutResultReturnUrl' => 'https://localhost/store/checkout_result' 336 | ), 337 | 'storeId' => 'amzn1.application-oa2-client.000000000000000000000000000000000' 338 | ); 339 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 340 | try { 341 | $client = new Amazon\Pay\API\Client($amazonpay_config); 342 | $result = $client->createCheckoutSession($payload, $headers); 343 | 344 | header("Content-type:application/json; charset=utf-8"); 345 | echo $result['response']; 346 | if ($result['status'] !== 201) { 347 | http_response_code(500); 348 | } 349 | 350 | } catch (\Exception $e) { 351 | // handle the exception 352 | echo $e . "\n"; 353 | http_response_code(500); 354 | } 355 | ?> 356 | ``` 357 | 358 | 359 | ## Amazon Checkout v2 - Create Checkout Session (standalone script example) 360 | 361 | ```php 362 | 'YOUR_PUBLIC_KEY_ID', 367 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 368 | 'region' => 'YOUR_REGION_CODE', 369 | 'sandbox' => true, 370 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 371 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 372 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 373 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 374 | ); 375 | $payload = array( 376 | 'webCheckoutDetails' => array( 377 | 'checkoutReviewReturnUrl' => 'https://localhost/store/checkout_review', 378 | 'checkoutResultReturnUrl' => 'https://localhost/store/checkout_result' 379 | ), 380 | 'storeId' => 'amzn1.application-oa2-client.000000000000000000000000000000000' 381 | ); 382 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 383 | try { 384 | $client = new Amazon\Pay\API\Client($amazonpay_config); 385 | $result = $client->createCheckoutSession($payload, $headers); 386 | if ($result['status'] === 201) { 387 | // created 388 | $response = json_decode($result['response'], true); 389 | $checkoutSessionId = $response['checkoutSessionId']; 390 | echo "checkoutSessionId=$checkoutSessionId\n"; 391 | } else { 392 | // check the error 393 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 394 | } 395 | } catch (\Exception $e) { 396 | // handle the exception 397 | echo $e . "\n"; 398 | } 399 | ?> 400 | ``` 401 | 402 | ## Amazon Checkout v2 - Get Checkout Session 403 | 404 | ```php 405 | 'YOUR_PUBLIC_KEY_ID', 410 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 411 | 'region' => 'YOUR_REGION_CODE', 412 | 'sandbox' => true, 413 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 414 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 415 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 416 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 417 | ); 418 | 419 | try { 420 | $checkoutSessionId = '00000000-0000-0000-0000-000000000000'; 421 | $client = new Amazon\Pay\API\Client($amazonpay_config); 422 | $result = $client->getCheckoutSession($checkoutSessionId); 423 | if ($result['status'] === 200) { 424 | $response = json_decode($result['response'], true); 425 | $checkoutSessionState = $response['statusDetails']['state']; 426 | $chargeId = $response['chargeId']; 427 | $chargePermissionId = $response['chargePermissionId']; 428 | 429 | // NOTE: Once Checkout Session moves to a "Completed" state, buyer and shipping 430 | // details must be obtained from the getCharges() function call instead 431 | $buyerName = $response['buyer']['name']; 432 | $buyerEmail = $response['buyer']['email']; 433 | $shipName = $response['shippingAddress']['name']; 434 | $shipAddrLine1 = $response['shippingAddress']['addressLine1']; 435 | $shipCity = $response['shippingAddress']['city']; 436 | $shipState = $response['shippingAddress']['stateOrRegion']; 437 | $shipZip = $response['shippingAddress']['postalCode']; 438 | $shipCounty = $response['shippingAddress']['countryCode']; 439 | 440 | echo "checkoutSessionState=$checkoutSessionState\n"; 441 | echo "chargeId=$chargeId; chargePermissionId=$chargePermissionId\n"; 442 | echo "buyer=$buyerName ($buyerEmail)\n"; 443 | echo "shipName=$shipName\n"; 444 | echo "address=$shipAddrLine1; $shipCity $shipState $shipZip ($shipCounty)\n"; 445 | } else { 446 | // check the error 447 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 448 | } 449 | } catch (\Exception $e) { 450 | // handle the exception 451 | echo $e . "\n"; 452 | } 453 | ?> 454 | ``` 455 | 456 | ## Amazon Checkout v2 - Update Checkout Session 457 | 458 | ```php 459 | 'YOUR_PUBLIC_KEY_ID', 464 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 465 | 'region' => 'YOUR_REGION_CODE', 466 | 'sandbox' => true, 467 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 468 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 469 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 470 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 471 | ); 472 | 473 | $payload = array( 474 | 'paymentDetails' => array( 475 | 'paymentIntent' => 'Authorize', 476 | 'canHandlePendingAuthorization' => false, 477 | 'chargeAmount' => array( 478 | 'amount' => '1.23', 479 | 'currencyCode' => 'USD' 480 | ), 481 | ), 482 | 'merchantMetadata' => array( 483 | 'merchantReferenceId' => '2020-00000001', 484 | 'merchantStoreName' => 'Store Name', 485 | 'noteToBuyer' => 'Thank you for your order!' 486 | ) 487 | ); 488 | 489 | try { 490 | $checkoutSessionId = '00000000-0000-0000-0000-000000000000'; 491 | $client = new Amazon\Pay\API\Client($amazonpay_config); 492 | $result = $client->updateCheckoutSession($checkoutSessionId, $payload); 493 | if ($result['status'] === 200) { 494 | $response = json_decode($result['response'], true); 495 | $amazonPayRedirectUrl = $response['webCheckoutDetails']['amazonPayRedirectUrl']; 496 | echo "amazonPayRedirectUrl=$amazonPayRedirectUrl\n"; 497 | } else { 498 | // check the error 499 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 500 | } 501 | } catch (\Exception $e) { 502 | // handle the exception 503 | echo $e . "\n"; 504 | } 505 | ?> 506 | ``` 507 | 508 | ## Amazon Checkout v2 - Complete Checkout Session API 509 | 510 | ```php 511 | 'YOUR_PUBLIC_KEY_ID', 516 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 517 | 'region' => 'YOUR_REGION_CODE', 518 | 'sandbox' => true, 519 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 520 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 521 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 522 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 523 | ); 524 | 525 | $payload = array( 526 | 'chargeAmount' => array( 527 | 'amount' => '14.00', 528 | 'currencyCode' => 'USD' 529 | ) 530 | ); 531 | 532 | try { 533 | $checkoutSessionId = '00000000-0000-0000-0000-000000000000'; 534 | $client = new Amazon\Pay\API\Client($amazonpay_config); 535 | $result = $client->completeCheckoutSession($checkoutSessionId, $payload); 536 | 537 | if ($result['status'] === 202) { 538 | // Charge Permission is in AuthorizationInitiated state 539 | $response = json_decode($result['response'], true); 540 | $checkoutSessionState = $response['statusDetails']['state']; 541 | $chargeId = $response['chargeId']; 542 | $chargePermissionId = $response['chargePermissionId']; 543 | } 544 | else if ($result['status'] === 200) { 545 | $response = json_decode($result['response'], true); 546 | $checkoutSessionState = $response['statusDetails']['state']; 547 | $chargePermissionId = $response['chargePermissionId']; 548 | } else { 549 | // check the error 550 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 551 | } 552 | } catch (Exception $e) { 553 | // handle the exception 554 | echo $e; 555 | } 556 | ?> 557 | ``` 558 | 559 | ## Amazon Checkout v2 - Get Charge Permission API 560 | 561 | ```php 562 | 'YOUR_PUBLIC_KEY_ID', 567 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 568 | 'region' => 'YOUR_REGION_CODE', 569 | 'sandbox' => true, 570 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 571 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 572 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 573 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 574 | ); 575 | 576 | try { 577 | $chargePermissionId = 'S01-0000000-0000000'; 578 | $client = new Amazon\Pay\API\Client($amazonpay_config); 579 | $result = $client->getChargePermission($chargePermissionId); 580 | 581 | if ($result['status'] === 200) { 582 | $response = json_decode($result['response'], true); 583 | $chargePermissionState = $response['statusDetails']['state']; 584 | 585 | } else { 586 | // check the error 587 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 588 | } 589 | } catch (Exception $e) { 590 | // handle the exception 591 | echo $e; 592 | } 593 | ?> 594 | ``` 595 | 596 | ## Amazon Checkout v2 - Update Charge Permission API 597 | 598 | ```php 599 | 'YOUR_PUBLIC_KEY_ID', 604 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 605 | 'region' => 'YOUR_REGION_CODE', 606 | 'sandbox' => true, 607 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 608 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 609 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 610 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 611 | ); 612 | 613 | $payload = array( 614 | 'merchantMetadata' => array( 615 | 'merchantReferenceId' => '32-41-323141-32', 616 | 'merchantStoreName' => 'AmazonTestStoreFront', 617 | 'noteToBuyer' => 'Some Note to buyer', 618 | 'customInformation' => '' 619 | ) 620 | ); 621 | 622 | try { 623 | $chargePermissionId = 'S01-0000000-0000000'; 624 | $client = new Amazon\Pay\API\Client($amazonpay_config); 625 | $result = $client->updateChargePermission($chargePermissionId, $payload); 626 | 627 | if ($result['status'] === 200) { 628 | $response = json_decode($result['response'], true); 629 | 630 | } else { 631 | // check the error 632 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 633 | } 634 | } catch (Exception $e) { 635 | // handle the exception 636 | echo $e; 637 | } 638 | ?> 639 | ``` 640 | 641 | ## Amazon Checkout v2 - Close Charge Permission API 642 | 643 | ```php 644 | 'YOUR_PUBLIC_KEY_ID', 649 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 650 | 'region' => 'YOUR_REGION_CODE', 651 | 'sandbox' => true, 652 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 653 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 654 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 655 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 656 | ); 657 | 658 | $payload = array( 659 | 'closureReason' => 'No more charges required', 660 | 'cancelPendingCharges' => false 661 | ); 662 | 663 | try { 664 | $chargePermissionId = 'S01-0000000-0000000'; 665 | $client = new Amazon\Pay\API\Client($amazonpay_config); 666 | $result = $client->closeChargePermission($chargePermissionId, $payload); 667 | 668 | if ($result['status'] === 200) { 669 | $response = json_decode($result['response'], true); 670 | $chargePermissionState = $response['statusDetails']['state']; 671 | 672 | } else { 673 | // check the error 674 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 675 | } 676 | } catch (Exception $e) { 677 | // handle the exception 678 | echo $e; 679 | } 680 | ?> 681 | ``` 682 | 683 | ## Amazon Checkout v2 - Create Charge API 684 | 685 | ```php 686 | 'YOUR_PUBLIC_KEY_ID', 692 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 693 | 'region' => 'YOUR_REGION_CODE', 694 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 695 | 'sandbox' => true, 696 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 697 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 698 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 699 | ); 700 | 701 | $payload = array( 702 | 'chargePermissionId' => 'S01-0000000-0000000', 703 | 'chargeAmount' => array( 704 | 'amount' => '14.00', 705 | 'currencyCode' => 'USD' 706 | ), 707 | 'captureNow' => true, 708 | 'softDescriptor' => 'Descriptor', 709 | 'canHandlePendingAuthorization' => false 710 | ); 711 | 712 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 713 | 714 | try { 715 | $client = new Amazon\Pay\API\Client($amazonpay_config); 716 | $result = $client->createCharge($payload, $headers); 717 | 718 | if ($result['status'] === 201) { 719 | $response = json_decode($result['response'], true); 720 | $chargeState = $response['statusDetails']['state']; 721 | $chargeId = $response['chargeId']; 722 | 723 | } else { 724 | // check the error 725 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 726 | } 727 | } catch (Exception $e) { 728 | // handle the exception 729 | echo $e; 730 | } 731 | ?> 732 | ``` 733 | 734 | ## Amazon Checkout v2 - Get Charge API 735 | 736 | ```php 737 | 'YOUR_PUBLIC_KEY_ID', 743 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 744 | 'region' => 'YOUR_REGION_CODE', 745 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 746 | 'sandbox' => true, 747 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 748 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 749 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 750 | ); 751 | 752 | try { 753 | $chargeId = 'S01-0000000-0000000-C000000'; 754 | $client = new Amazon\Pay\API\Client($amazonpay_config); 755 | $result = $client->getCharge($chargeId); 756 | 757 | if ($result['status'] === 200) { 758 | $response = json_decode($result['response'], true); 759 | $chargeState = $response['statusDetails']['state']; 760 | 761 | } else { 762 | // check the error 763 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 764 | } 765 | } catch (Exception $e) { 766 | // handle the exception 767 | echo $e; 768 | } 769 | ?> 770 | ``` 771 | 772 | ## Amazon Checkout v2 - Update Charge API 773 | **Please note that is API is supported only for PSPs (Payment Service Provider)** 774 | 775 | ```php 776 | 'YOUR_PUBLIC_KEY_ID', 782 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 783 | 'region' => 'YOUR_REGION_CODE', 784 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 785 | 'sandbox' => true, 786 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 787 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 788 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 789 | ); 790 | 791 | $payload = array( 792 | 'statusDetails' => array( 793 | 'state' => 'Canceled', 794 | 'reasonCode' => 'ExpiredUnused' 795 | ) 796 | ); 797 | 798 | $headers = [ 799 | 'x-amz-pay-idempotency-key' => uniqid() 800 | ]; 801 | 802 | try { 803 | $client = new Amazon\Pay\API\Client($amazonpay_config); 804 | $result = $client->updateCharge('S03-XXXXXX-XXXXXX-XXXXXX', $payload, $headers); 805 | 806 | if ($result['status'] === 200) { 807 | // success 808 | $response = $result['response']; 809 | } else { 810 | // check the error 811 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 812 | } 813 | } catch (\Exception $e) { 814 | echo $e . "\n"; 815 | http_response_code(500); 816 | } 817 | ?> 818 | ``` 819 | 820 | ## Amazon Checkout v2 - Capture Charge API 821 | 822 | ```php 823 | 'YOUR_PUBLIC_KEY_ID', 828 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 829 | 'region' => 'YOUR_REGION_CODE', 830 | 'sandbox' => true, 831 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 832 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 833 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 834 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 835 | ); 836 | 837 | $payload = array( 838 | 'captureAmount' => array( 839 | 'amount' => '1.23', 840 | 'currencyCode' => 'USD' 841 | ), 842 | 'softDescriptor' => 'For CC Statement' 843 | ); 844 | 845 | try { 846 | $chargeId = 'S01-0000000-0000000-C000000'; 847 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 848 | $client = new Amazon\Pay\API\Client($amazonpay_config); 849 | $result = $client->captureCharge($chargeId, $payload, $headers); 850 | 851 | if ($result['status'] === 200) { 852 | $response = json_decode($result['response'], true); 853 | $state = $response['statusDetails']['state']; 854 | $reasonCode = $response['statusDetails']['reasonCode']; 855 | $reasonDescription = $response['statusDetails']['reasonDescription']; 856 | echo "state=$state; reasonCode=$reasonCode; reasonDescription=$reasonDescription\n"; 857 | } else { 858 | // check the error 859 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 860 | } 861 | } catch (\Exception $e) { 862 | // handle the exception 863 | echo $e . "\n"; 864 | } 865 | ?> 866 | ``` 867 | 868 | ## Amazon Checkout v2 - Cancel Charge API 869 | 870 | ```php 871 | 'YOUR_PUBLIC_KEY_ID', 877 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 878 | 'region' => 'YOUR_REGION_CODE', 879 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 880 | 'sandbox' => true, 881 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 882 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 883 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 884 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 885 | ); 886 | 887 | $payload = array( 888 | 'cancellationReason' => 'REASON DESCRIPTION' 889 | ); 890 | 891 | try { 892 | $chargeId = 'S01-0000000-0000000-C000000'; 893 | $client = new Amazon\Pay\API\Client($amazonpay_config); 894 | $result = $client->cancelCharge($chargeId, $payload); 895 | 896 | if ($result['status'] === 200) { 897 | $response = json_decode($result['response'], true); 898 | $chargeState = $response['statusDetails']['state']; 899 | 900 | } else { 901 | // check the error 902 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 903 | } 904 | } catch (Exception $e) { 905 | // handle the exception 906 | echo $e; 907 | } 908 | ?> 909 | ``` 910 | 911 | ## Amazon Checkout v2 - Create Refund API 912 | 913 | ```php 914 | 'YOUR_PUBLIC_KEY_ID', 920 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 921 | 'region' => 'YOUR_REGION_CODE', 922 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 923 | 'sandbox' => true, 924 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 925 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 926 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 927 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 928 | ); 929 | 930 | $payload = array( 931 | 'chargeId' => 'S01-0000000-0000000-C000000', 932 | 'refundAmount' => array( 933 | 'amount' => '14.00', 934 | 'currencyCode' => 'USD' 935 | ), 936 | 'softDescriptor' => 'Descriptor' 937 | ); 938 | 939 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 940 | 941 | try { 942 | $client = new Amazon\Pay\API\Client($amazonpay_config); 943 | $result = $client->createRefund($payload, $headers); 944 | 945 | if ($result['status'] === 201) { 946 | $response = json_decode($result['response'], true); 947 | $refundState = $response['statusDetails']['state']; 948 | $refundId = $response['refundId']; 949 | 950 | } else { 951 | // check the error 952 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 953 | } 954 | } catch (Exception $e) { 955 | // handle the exception 956 | echo $e; 957 | } 958 | ?> 959 | ``` 960 | 961 | ## Amazon Checkout v2 - Get Refund API 962 | 963 | ```php 964 | 'YOUR_PUBLIC_KEY_ID', 970 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 971 | 'region' => 'YOUR_REGION_CODE', 972 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 973 | 'sandbox' => true, 974 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 975 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 976 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 977 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 978 | ); 979 | 980 | try { 981 | $refundId = 'S01-0000000-0000000-R000000' 982 | $client = new Amazon\Pay\API\Client($amazonpay_config); 983 | $result = $client->getRefund($refundId); 984 | 985 | if ($result['status'] === 200) { 986 | $response = json_decode($result['response'], true); 987 | $chargeState = $response['statusDetails']['state']; 988 | 989 | } else { 990 | // check the error 991 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 992 | } 993 | } catch (Exception $e) { 994 | // handle the exception 995 | echo $e; 996 | } 997 | ?> 998 | ``` 999 | 1000 | ## Amazon Checkout v2 - Get Buyer API 1001 | 1002 | ```php 1003 | 'YOUR_PUBLIC_KEY_ID', 1008 | 'private_key' => 'keys/private.pem', // Path to RSA Private Key (or a string representation) 1009 | 'region' => 'YOUR_REGION_CODE', 1010 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 1011 | 'sandbox' => true, 1012 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 1013 | 'integrator_id' => 'AXXXXXXXXXXXXX', // (optional) Solution Provider Platform Id in Amz UID Format 1014 | 'integrator_version' => '1.2.3', // (optional) Solution Provider Plugin Version in Semantic Versioning Format 1015 | 'platform_version' => '0.0.4' // (optional) Solution Provider Platform Version in Semantic Versioning Format 1016 | ); 1017 | 1018 | try { 1019 | $buyerToken = 'BUYER_TOKEN'; 1020 | 1021 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1022 | $result = $client->getBuyer($buyerToken); 1023 | 1024 | if ($result['status'] === 200) { 1025 | $response = json_decode($result['response'], true); 1026 | 1027 | } else { 1028 | // check the error 1029 | echo 'status=' . $result['status'] . '; response=' . $result['response']; 1030 | } 1031 | } catch (Exception $e) { 1032 | // handle the exception 1033 | echo $e; 1034 | } 1035 | ?> 1036 | ``` 1037 | 1038 | # Generate Button Signature (helper function) 1039 | 1040 | The signatures generated by this helper function are only valid for the Checkout v2 front-end buttons. Unlike API signing, no timestamps are involved, so the result of this function can be considered a static signature that can safely be placed in your website JS source files and used repeatedly (as long as your payload does not change). 1041 | 1042 | ```php 1043 | 'MY_PUBLIC_KEY_ID', 1048 | 'private_key' => 'keys/private.pem', 1049 | 'region' => 'US', 1050 | 'sandbox' => true, 1051 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 1052 | ); 1053 | 1054 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1055 | $payload = '{"storeId":"amzn1.application-oa2-client.xxxxx","webCheckoutDetails":{"checkoutReviewReturnUrl":"https://localhost/test/CheckoutReview.php","checkoutResultReturnUrl":"https://localhost/test/CheckoutResult.php"}}'; 1056 | $signature = $client->generateButtonSignature($payload); 1057 | echo $signature . "\n"; 1058 | ?> 1059 | ``` 1060 | 1061 | # Manual Signing (Advanced Use-Cases Only) 1062 | 1063 | This SDK provides the ability to help you manually sign your API requests if you want to use your own code for sending the HTTPS request over the Internet. 1064 | 1065 | Example call to getPostSignedHeaders function with values: 1066 | 1067 | ```php 1068 | /* getPostSignedHeaders convenience – Takes values for canonical request sorts and parses it and 1069 | * returns a signature for the request being sent 1070 | * @param $http_request_method [String] 1071 | * @param $request_uri [String] 1072 | * @param $request_parameters [array()] 1073 | * @param $request_payload [string] 1074 | */ 1075 | ``` 1076 | 1077 | Example request method: 1078 | 1079 | ```php 1080 | $method = 'POST'; 1081 | 1082 | // API Merchant Scan 1083 | $url = 'https://pay-api.amazon.com/sandbox/' . $versiom . '/in-store/merchantScan'; 1084 | 1085 | $payload = array( 1086 | 'scanData' => 'UKhrmatMeKdlfY6b', 1087 | 'scanReferenceId' => '0b8fb271-2ae2-49a5-b35d4', 1088 | 'merchantCOE' => 'US', 1089 | 'ledgerCurrency' => 'USD', 1090 | 'chargeTotal' => array( 1091 | 'currencyCode' => 'USD', 1092 | 'amount' => '2.00' 1093 | ), 1094 | 'metadata' => array( 1095 | 'merchantNote' => 'Ice Cream', 1096 | 'customInformation' => 'In-store Ice Cream', 1097 | 'communicationContext' => array( 1098 | 'merchantStoreName' => 'Store Name', 1099 | 'merchantOrderId' => '789123' 1100 | ) 1101 | ) 1102 | ); 1103 | 1104 | // Convert to json string 1105 | $payload = json_encode($payload); 1106 | 1107 | $requestParameters = array(); 1108 | 1109 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1110 | 1111 | $postSignedHeaders = $client->getPostSignedHeaders($method, $url, $requestParameters, $payload); 1112 | ``` 1113 | 1114 | Example call to createSignature function with values: 1115 | 1116 | (This will only be used if you don't use getPostSignedHeaders and want to create your own custom headers.) 1117 | 1118 | ```php 1119 | /* createSignature convenience – Takes values for canonical request sorts and parses it and 1120 | * returns a signature for the request being sent 1121 | * @param $http_request_method [String] 1122 | * @param $request_uri [String] 1123 | * @param $request_parameters [Array()] 1124 | * @param $pre_signed_headers [Array()] 1125 | * @param $request_payload [String] 1126 | * @param $timeStamp [String] 1127 | */ 1128 | 1129 | // Example request method: 1130 | 1131 | $method = 'POST'; 1132 | 1133 | // API Merchant Scan 1134 | $url = 'https://pay-api.amazon.com/sandbox/in-store/' . $version . '/merchantScan'; 1135 | 1136 | $payload = array( 1137 | 'scanData' => 'ScanData', 1138 | 'scanReferenceId' => '0b8fb271-2ae2-49a5-b35d4', 1139 | 'merchantCOE' => 'US', 1140 | 'ledgerCurrency' => 'USD', 1141 | 'chargeTotal' => array( 1142 | 'currencyCode' => 'USD', 1143 | 'amount' => '2.00' 1144 | ), 1145 | 'metadata' => array( 1146 | 'merchantNote' => 'Ice Cream', 1147 | 'customInformation' => 'In-store Ice Cream', 1148 | 'communicationContext' => array( 1149 | 'merchantStoreName' => 'Store Name', 1150 | 'merchantOrderId' => '789123' 1151 | ) 1152 | ) 1153 | ); 1154 | 1155 | // Convert to json string 1156 | $payload = json_encode($payload); 1157 | 1158 | $requestParameters = array(); 1159 | 1160 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1161 | 1162 | // Create an array that will contain the parameters for the charge API call 1163 | $pre_signed_headers = array(); 1164 | $pre_signed_headers['Accept'] = 'application/json'; 1165 | $pre_signed_headers['Content-Type'] = 'application/json'; 1166 | $pre_signed_headers['X-Amz-Pay-Region'] = 'na'; 1167 | 1168 | $client = new Client($amazonpay_config); 1169 | $signedInput = $client->createSignature($method, $url, $requestParameters, $pre_signed_headers, $payload, '20180326T203730Z'); 1170 | ``` 1171 | 1172 | # Reporting APIs code samples 1173 | 1174 | ## Amazon Checkout v2 Reporting APIs - GetReport API 1175 | 1176 | ```php 1177 | 'MY_PUBLIC_KEY_ID', 1182 | 'private_key' => 'keys/private.pem', 1183 | 'region' => 'US', 1184 | 'sandbox' => false 1185 | ); 1186 | 1187 | $requestPayload = array( 1188 | 'reportTypes' => '_GET_FLAT_FILE_OFFAMAZONPAYMENTS_ORDER_REFERENCE_DATA_', 1189 | 'processingStatuses' => 'COMPLETED', 1190 | 'pageSize' => '10' 1191 | ); 1192 | 1193 | try { 1194 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1195 | $result = $client->getReports($requestPayload); 1196 | 1197 | if ($result['status'] === 200) { 1198 | // success 1199 | $response = $result['response']; 1200 | echo $response; 1201 | } else { 1202 | // check the error 1203 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1204 | } 1205 | } catch (\Exception $e) { 1206 | // handle the exception 1207 | echo $e . "\n"; 1208 | } 1209 | ?> 1210 | ``` 1211 | 1212 | 1213 | ## Amazon Checkout v2 Reporting APIs - GetReportById API 1214 | 1215 | ```php 1216 | 'MY_PUBLIC_KEY_ID', 1221 | 'private_key' => 'keys/private.pem', 1222 | 'region' => 'US', 1223 | 'sandbox' => true 1224 | ); 1225 | 1226 | try { 1227 | $reportId = "1234567890"; 1228 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1229 | $result = $client->getReportById($reportId); 1230 | 1231 | if ($result['status'] === 200) { 1232 | // success 1233 | $response = $result['response']; 1234 | echo $response; 1235 | } else { 1236 | // check the error 1237 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1238 | } 1239 | } catch (\Exception $e) { 1240 | // handle the exception 1241 | echo $e . "\n"; 1242 | } 1243 | ?> 1244 | ``` 1245 | 1246 | 1247 | ## Amazon Checkout v2 Reporting APIs - GetReportDocument API 1248 | 1249 | ```php 1250 | 'MY_PUBLIC_KEY_ID', 1255 | 'private_key' => 'keys/private.pem', 1256 | 'region' => 'US', 1257 | 'sandbox' => true 1258 | ); 1259 | 1260 | try { 1261 | $reportDocumentId = "amzn1.tortuga.0.000000000-0000-0000-0000-000000000000.00000000000000"; 1262 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1263 | $result = $client->getReportDocument($reportDocumentId); 1264 | 1265 | if ($result['status'] === 200) { 1266 | // success 1267 | $response = $result['response']; 1268 | echo $response; 1269 | } else { 1270 | // check the error 1271 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1272 | } 1273 | } catch (\Exception $e) { 1274 | // handle the exception 1275 | echo $e . "\n"; 1276 | } 1277 | ?> 1278 | ``` 1279 | 1280 | 1281 | ## Amazon Checkout v2 Reporting APIs - GetReportSchedules API 1282 | 1283 | ```php 1284 | 'MY_PUBLIC_KEY_ID', 1289 | 'private_key' => 'keys/private.pem', 1290 | 'region' => 'US', 1291 | 'sandbox' => true 1292 | ); 1293 | 1294 | try { 1295 | $reportTypes = "_GET_FLAT_FILE_OFFAMAZONPAYMENTS_ORDER_REFERENCE_DATA_,_GET_FLAT_FILE_OFFAMAZONPAYMENTS_BILLING_AGREEMENT_DATA_"; 1296 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1297 | $result = $client->getReportSchedules($reportTypes); 1298 | 1299 | if ($result['status'] === 200) { 1300 | // success 1301 | $response = $result['response']; 1302 | echo $response; 1303 | } else { 1304 | // check the error 1305 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1306 | } 1307 | } catch (\Exception $e) { 1308 | // handle the exception 1309 | echo $e . "\n"; 1310 | } 1311 | ?> 1312 | ``` 1313 | 1314 | 1315 | ## Amazon Checkout v2 Reporting APIs - GetReportScheduleById API 1316 | 1317 | ```php 1318 | 'MY_PUBLIC_KEY_ID', 1323 | 'private_key' => 'keys/private.pem', 1324 | 'region' => 'US', 1325 | 'sandbox' => true 1326 | ); 1327 | 1328 | try { 1329 | $reportScheduleId = "1234567890"; 1330 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1331 | $result = $client->getReportScheduleById($reportScheduleId); 1332 | 1333 | if ($result['status'] === 200) { 1334 | // success 1335 | $response = $result['response']; 1336 | echo $response; 1337 | } else { 1338 | // check the error 1339 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1340 | } 1341 | } catch (\Exception $e) { 1342 | // handle the exception 1343 | echo $e . "\n"; 1344 | } 1345 | ?> 1346 | ``` 1347 | 1348 | 1349 | ## Amazon Checkout v2 Reporting APIs - CreateReport API 1350 | 1351 | ```php 1352 | 'MY_PUBLIC_KEY_ID', 1357 | 'private_key' => 'keys/private.pem', 1358 | 'region' => 'US', 1359 | 'sandbox' => true 1360 | ); 1361 | 1362 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 1363 | 1364 | try { 1365 | $requestPayload = array( 1366 | 'reportType' => '_GET_FLAT_FILE_OFFAMAZONPAYMENTS_AUTHORIZATION_DATA_', 1367 | 'startTime' => '20221114T074550Z', 1368 | 'endTime' => '20221114T074550Z' 1369 | ); 1370 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1371 | $result = $client->createReport($requestPayload); 1372 | 1373 | if ($result['status'] === 200) { 1374 | // success 1375 | $response = $result['response']; 1376 | echo $response; 1377 | } else { 1378 | // check the error 1379 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1380 | } 1381 | } catch (\Exception $e) { 1382 | // handle the exception 1383 | echo $e . "\n"; 1384 | } 1385 | ?> 1386 | ``` 1387 | 1388 | 1389 | ## Amazon Checkout v2 Reporting APIs - CreateReportSchedule API 1390 | 1391 | ```php 1392 | 'MY_PUBLIC_KEY_ID', 1397 | 'private_key' => 'keys/private.pem', 1398 | 'region' => 'US', 1399 | 'sandbox' => true 1400 | ); 1401 | 1402 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 1403 | 1404 | try { 1405 | $requestPayload = array( 1406 | 'reportType' => '_GET_FLAT_FILE_OFFAMAZONPAYMENTS_ORDER_REFERENCE_DATA_', 1407 | 'scheduleFrequency' => 'P1D', 1408 | 'nextReportCreationTime' => '20221114T074550Z', 1409 | 'deleteExistingSchedule' => false 1410 | ); 1411 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1412 | $result = $client->createReportSchedule($requestPayload); 1413 | 1414 | if ($result['status'] === 200) { 1415 | // success 1416 | $response = $result['response']; 1417 | echo $response; 1418 | } else { 1419 | // check the error 1420 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1421 | } 1422 | } catch (\Exception $e) { 1423 | // handle the exception 1424 | echo $e . "\n"; 1425 | } 1426 | ?> 1427 | ``` 1428 | 1429 | 1430 | ## Amazon Checkout v2 Reporting APIs - CancelReportSchedule API 1431 | 1432 | ```php 1433 | 'MY_PUBLIC_KEY_ID', 1438 | 'private_key' => 'keys/private.pem', 1439 | 'region' => 'US', 1440 | 'sandbox' => true 1441 | ); 1442 | 1443 | try { 1444 | $reportScheduleId = "1234567890"; 1445 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1446 | $result = $client->cancelReportSchedule($reportScheduleId); 1447 | 1448 | if ($result['status'] === 200) { 1449 | // success 1450 | $response = $result['response']; 1451 | echo $response; 1452 | } else { 1453 | // check the error 1454 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1455 | } 1456 | } catch (\Exception $e) { 1457 | // handle the exception 1458 | echo $e . "\n"; 1459 | } 1460 | ?> 1461 | 1462 | ``` 1463 | ## Amazon Checkout v2 Reporting APIs - GetDisbursements API 1464 | 1465 | ```php 1466 | 'MY_PUBLIC_KEY_ID', 1471 | 'private_key' => 'keys/private.pem', 1472 | 'region' => 'US', 1473 | 'sandbox' => false 1474 | ); 1475 | 1476 | try { 1477 | $queryParameters = array( 1478 | 'startTime' => '20240301T224539Z', 1479 | 'endTime' => '20240330T230345Z', 1480 | 'pageSize' => '10', 1481 | 'nextToken' => '' 1482 | ); 1483 | 1484 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 1485 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1486 | $result = $client->getDisbursements($queryParameters, $headers); 1487 | print_r($result); 1488 | } catch (\Exception $e) { 1489 | // handle the exception 1490 | echo $e . "\n"; 1491 | } 1492 | ?> 1493 | ``` 1494 | 1495 | ## Amazon Checkout v2 SPC - finalizeCheckoutSession API 1496 | 1497 | ```php 1498 | 'MY_PUBLIC_KEY_ID', 1503 | 'private_key' => 'keys/private.pem', 1504 | 'region' => 'US', 1505 | 'sandbox' => true, 1506 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 1507 | ); 1508 | try{ 1509 | $payload = array( 1510 | "shippingAddress" => array( 1511 | "name" => "Susie Smith", 1512 | "addressLine1" => "10 Ditka Ave", 1513 | "addressLine2" => "Suite 2500", 1514 | "city" => "Chicago", 1515 | "county" => null, 1516 | "district" => null, 1517 | "stateOrRegion" => "IL", 1518 | "postalCode" => "60602", 1519 | "countryCode" => "US", 1520 | "phoneNumber" => "800-000-0000" 1521 | ), 1522 | "billingAddress" => null, 1523 | "chargeAmount" => array( 1524 | "amount" => "10", 1525 | "currencyCode" => "USD" 1526 | ), 1527 | "totalOrderAmount" => array( 1528 | "amount" => "10", 1529 | "currencyCode" => "USD" 1530 | ), 1531 | "paymentIntent" => "Confirm", 1532 | "canHandlePendingAuthorization" => "false" 1533 | ); 1534 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 1535 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1536 | $checkoutSessionId = "your-checkout-session-id"; 1537 | $result = $client->finalizeCheckoutSession($checkoutSessionId,$payload, $headers); 1538 | if ($result['status'] === 200) { 1539 | // success 1540 | $response = $result['response']; 1541 | echo $response; 1542 | } else { 1543 | // check the error 1544 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1545 | } 1546 | } catch (\Exception $e) { 1547 | // handle the exception 1548 | echo $e . "\n"; 1549 | } 1550 | ?> 1551 | ``` 1552 | # Sample codes for Account Management APIs 1553 | For more details related to Account Management APIs, please refer to this [Integration Guide](https://developer.amazon.com/docs/amazon-pay-registration/jp-merchant-onboarding-and-account-management-APIs.html). 1554 | 1555 | ## Amazon Checkout v2 Account Management APIs - createMerchantAccount API 1556 | 1557 | ```php 1558 | 'YOUR_PUBLIC_KEY_ID', 1564 | 'private_key' => 'keys/private.pem', 1565 | 'region' => 'JP', 1566 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1567 | ); 1568 | 1569 | try { 1570 | 1571 | $payload = array( 1572 | "uniqueReferenceId" => "Unique_Reference_Id", // Mandatory 1573 | "ledgerCurrency" => "JPY", 1574 | "businessInfo" => array( 1575 | "email" => "abhi@abc.com", 1576 | "businessType" => "CORPORATE", 1577 | "businessLegalName" => "密林コーヒー", 1578 | "businessCategory" => "Beauty", 1579 | "businessAddress" => array( 1580 | "addressLine1" => "扇町4丁目5-1", 1581 | "addressLine2" => "フルフィルメントセンタービル", 1582 | "city" => "小田原市", 1583 | "stateOrRegion" => "神奈川県", 1584 | "postalCode" => "250-0001", 1585 | "countryCode" => "JP", 1586 | "phoneNumber" => array( 1587 | "countryCode" => "81", 1588 | "number" => "2062062061" 1589 | ) 1590 | ), 1591 | "businessDisplayName" => "Abhi's Cafe", 1592 | "annualSalesVolume" => array( 1593 | "amount" => "100000", 1594 | "currencyCode" => "JPY" 1595 | ), 1596 | "countryOfEstablishment" => "JP", 1597 | "customerSupportInformation" => array( 1598 | "customerSupportEmail" => "test.merchant_abhi@abc.com", 1599 | "customerSupportPhoneNumber" => array( 1600 | "countryCode" => "1", 1601 | "number" => "1234567", 1602 | "extension" => "123" 1603 | ) 1604 | ) 1605 | ), 1606 | "beneficiaryOwners" => [array( 1607 | "personFullName" => "Abhishek Kumar", 1608 | "residentialAddress" => array( 1609 | "addressLine1" => "扇町4丁目5-1", 1610 | "addressLine2" => "フルフィルメントセンタービル", 1611 | "city" => "小田原市", 1612 | "stateOrRegion" => "神奈川県", 1613 | "postalCode" => "250-0001", 1614 | "countryCode" => "JP", 1615 | "phoneNumber" => array( 1616 | "countryCode" => "81", 1617 | "number" => "2062062061" 1618 | ) 1619 | ) 1620 | )], 1621 | "primaryContactPerson" => array( 1622 | "personFullName" => "Abhishek Kumar" 1623 | ), 1624 | "integrationInfo" => array( 1625 | "ipnEndpointUrls" => array( 1626 | "https://yourdomainname.com/ipnendpoint1", 1627 | "https://yourdomainname.com/ipnendpoint2" 1628 | ) 1629 | ), 1630 | "stores" => array( 1631 | array( 1632 | "domainUrls" => array( 1633 | "https://yourdomainname.com" 1634 | ), 1635 | "storeName" => "Rufus's Cafe", 1636 | "privacyPolicyUrl" => "https://yourdomainname.com/privacy", 1637 | "storeStatus" => array( 1638 | "state" => "ACTIVE", 1639 | "reasonCode" => null 1640 | ) 1641 | ) 1642 | ), 1643 | "merchantStatus" => array( 1644 | "statusProvider" => "Ayden", 1645 | "state" => "ACTIVE", 1646 | "reasonCode" => null 1647 | ) 1648 | ); 1649 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1650 | $result = $client->createMerchantAccount($payload, $headers); 1651 | print_r($result); 1652 | 1653 | if ($result['status'] === 201) { 1654 | // success 1655 | $response = $result['response']; 1656 | print_r($response); 1657 | } else { 1658 | // check the error 1659 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1660 | } 1661 | } catch (\Exception $e) { 1662 | // handle the exception 1663 | echo $e . "\n"; 1664 | } 1665 | ?> 1666 | ``` 1667 | 1668 | ## Amazon Checkout v2 Account Management APIs - updateMerchantAccount API 1669 | 1670 | ```php 1671 | 'YOUR_PUBLIC_KEY_ID', 1677 | 'private_key' => 'keys/private.pem', 1678 | 'region' => 'JP', 1679 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1680 | ); 1681 | 1682 | try { 1683 | 1684 | $payload = array( 1685 | "businessInfo" => array( 1686 | "email" => "abhi_updated@abc.com", 1687 | "businessType" => "CORPORATE", 1688 | "businessLegalName" => "密林コーヒー", 1689 | "businessCategory" => "Beauty", 1690 | "businessAddress" => array( 1691 | "addressLine1" => "扇町4丁目5-1", 1692 | "addressLine2" => "フルフィルメントセンタービル", 1693 | "city" => "小田原市", 1694 | "stateOrRegion" => "神奈川県", 1695 | "postalCode" => "250-0025", 1696 | "countryCode" => "JP", 1697 | "phoneNumber" => array( 1698 | "countryCode" => "81", 1699 | "number" => "2062062065" 1700 | ) 1701 | ), 1702 | "businessDisplayName" => "Abhi's Golden Cafe", 1703 | "annualSalesVolume" => array( 1704 | "amount" => "500000", 1705 | "currencyCode" => "JPY" 1706 | ), 1707 | "countryOfEstablishment" => "JP", 1708 | "customerSupportInformation" => array( 1709 | "customerSupportEmail" => "test.merchant_abhi@abc.com", 1710 | "customerSupportPhoneNumber" => array( 1711 | "countryCode" => "1", 1712 | "number" => "9999999", 1713 | "extension" => "123" 1714 | ) 1715 | ) 1716 | ) 1717 | ); 1718 | 1719 | $headers = array('x-amz-pay-authtoken' => 'AUTH_TOKEN'); 1720 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1721 | $merchantAccountId = "YOUR_MERCHANT_ID"; 1722 | $result = $client->updateMerchantAccount($merchantAccountId, $payload, $headers); 1723 | 1724 | if ($result['status'] === 200) { 1725 | // success 1726 | $response = $result['response']; 1727 | } else { 1728 | // check the error 1729 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1730 | } 1731 | } catch (\Exception $e) { 1732 | // handle the exception 1733 | echo $e . "\n"; 1734 | } 1735 | ?> 1736 | ``` 1737 | 1738 | ## Amazon Checkout v2 Account Management APIs - claimMerchantAccount API 1739 | 1740 | ```php 1741 | 'YOUR_PUBLIC_KEY_ID', 1747 | 'private_key' => 'keys/private.pem', 1748 | 'region' => 'JP', 1749 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1750 | ); 1751 | 1752 | try { 1753 | 1754 | $payload = array( 1755 | "uniqueReferenceId" => "Unique_Reference_Id" // Mandatory 1756 | ); 1757 | 1758 | $headers = array('x-amz-pay-Idempotency-Key' => uniqid()); 1759 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1760 | $merchantAccountId = "YOUR_MERCHANT_ID"; 1761 | $result = $client->claimMerchantAccount($merchantAccountId, $payload, $headers = null); 1762 | 1763 | if ($result['status'] === 303 || $result['status'] === 200) { 1764 | // success 1765 | $response = $result['response']; 1766 | } else { 1767 | // check the error 1768 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1769 | } 1770 | } catch (\Exception $e) { 1771 | // handle the exception 1772 | echo $e . "\n"; 1773 | } 1774 | ?> 1775 | ``` 1776 | 1777 | ## Amazon Checkout v2 Dispute APIs - Create Dispute API 1778 | 1779 | ```php 1780 | 'YOUR_PUBLIC_KEY_ID', 1787 | 'private_key' => 'keys/private.pem', 1788 | 'region' => 'JP', 1789 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1790 | ); 1791 | 1792 | $creationTimestamp = time(); // Equivalent to System.currentTimeMillis() / 1000L in Java 1793 | $merchantResponseDeadline = $creationTimestamp + (14 * 24 * 60 * 60); // Adding 14 days in seconds 1794 | 1795 | $payload = [ 1796 | "chargeId" => 'S03-XXXXXX-XXXXXX-XXXXXX', 1797 | "providerMetadata" => [ 1798 | "providerDisputeId" => "AXXXXXXXXX" 1799 | ], 1800 | "disputeAmount" => [ 1801 | "amount" => "1", 1802 | "currencyCode" => "JPY" 1803 | ], 1804 | "filingReason" => DisputeFilingReason::PRODUCT_NOT_RECEIVED, 1805 | "creationTimestamp" => $creationTimestamp, 1806 | "statusDetails" => [ 1807 | "state" => "ActionRequired" 1808 | ], 1809 | "merchantResponseDeadline" => $merchantResponseDeadline 1810 | ]; 1811 | 1812 | $headers = [ 1813 | 'x-amz-pay-idempotency-key' => uniqid() 1814 | ]; 1815 | 1816 | try { 1817 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1818 | $result = $client->createDispute($payload, $headers); 1819 | 1820 | if ($result['status'] === 200) { 1821 | // success 1822 | $response = $result['response']; 1823 | } else { 1824 | // check the error 1825 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1826 | } 1827 | } catch (\Exception $e) { 1828 | echo $e . "\n"; 1829 | http_response_code(500); 1830 | } 1831 | ?> 1832 | ``` 1833 | 1834 | ### Amazon Checkout v2 Dispute APIs - Get Dispute API 1835 | 1836 | ```php 1837 | 'YOUR_PUBLIC_KEY_ID', 1843 | 'private_key' => 'keys/private.pem', 1844 | 'region' => 'JP', 1845 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1846 | ); 1847 | 1848 | $disputeId = 'DIPSUTE_ID'; 1849 | 1850 | try { 1851 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1852 | $result = $client->getDispute($disputeId); 1853 | 1854 | if ($result['status'] === 200) { 1855 | // success 1856 | $response = $result['response']; 1857 | } else { 1858 | // check the error 1859 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1860 | } 1861 | } catch (\Exception $e) { 1862 | echo $e . "\n"; 1863 | http_response_code(500); 1864 | } 1865 | ?> 1866 | ``` 1867 | 1868 | ## Amazon Checkout v2 Dispute APIs - Update Dispute API 1869 | 1870 | ```php 1871 | 'YOUR_PUBLIC_KEY_ID', 1880 | 'private_key' => 'keys/private.pem', 1881 | 'region' => 'JP', 1882 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1883 | ); 1884 | 1885 | $currentTimestamp = time(); 1886 | $disputeId = 'DIPSUTE_ID'; 1887 | 1888 | $statusDetails = [ 1889 | "resolution" => DisputeResolution::MERCHANT_WON, 1890 | "state" => DisputeState::RESOLVED, 1891 | "reasonCode" => DisputeReasonCode::MERCHANT_ACCEPTED_DISPUTE, 1892 | "reasonDescription" => "Merchant accepted the dispute request" 1893 | ]; 1894 | 1895 | $payload = [ 1896 | "statusDetails" => $statusDetails, 1897 | "closureTimestamp" => $currentTimestamp 1898 | ]; 1899 | 1900 | try { 1901 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1902 | $result = $client->updateDispute($disputeId, $payload); 1903 | 1904 | if ($result['status'] === 200) { 1905 | // success 1906 | $response = $result['response']; 1907 | } else { 1908 | // check the error 1909 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1910 | } 1911 | } catch (\Exception $e) { 1912 | echo $e . "\n"; 1913 | http_response_code(500); 1914 | } 1915 | ?> 1916 | ``` 1917 | 1918 | ## Amazon Checkout v2 Dispute APIs - Contest Dispute API 1919 | 1920 | ```php 1921 | 'YOUR_PUBLIC_KEY_ID', 1928 | 'private_key' => 'keys/private.pem', 1929 | 'region' => 'JP', 1930 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1931 | ); 1932 | 1933 | $payload = []; 1934 | 1935 | $merchantEvidences[] = [ 1936 | "evidenceType" => EvidenceType.TRACKING_NUMBER, 1937 | "fileId" => "FILE_ID", 1938 | "evidenceText" => "raw text supporting merchant evidence" 1939 | ]; 1940 | 1941 | $payload['merchantEvidences'] = $merchantEvidences; 1942 | 1943 | try { 1944 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1945 | $result = $client->contestDispute('DIPSUTE_ID', $payload); 1946 | 1947 | if ($result['status'] === 200) { 1948 | // success 1949 | $response = $result['response']; 1950 | } else { 1951 | // check the error 1952 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 1953 | } 1954 | } catch (\Exception $e) { 1955 | echo $e . "\n"; 1956 | http_response_code(500); 1957 | } 1958 | ?> 1959 | ``` 1960 | 1961 | ## Amazon Checkout v2 File APIs - Upload File API 1962 | 1963 | ```php 1964 | 'YOUR_PUBLIC_KEY_ID', 1970 | 'private_key' => 'keys/private.pem', 1971 | 'region' => 'JP', 1972 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2' 1973 | ); 1974 | 1975 | $payload = []; 1976 | 1977 | $merchantEvidences[] = [ 1978 | "evidenceType" => EvidenceType.TRACKING_NUMBER, 1979 | "fileId" => "FILE_ID", 1980 | "evidenceText" => "raw text supporting merchant evidence" 1981 | ]; 1982 | 1983 | $payload = [ 1984 | "type" => "jpg", 1985 | "purpose" => "disputeEvidence" 1986 | ]; 1987 | 1988 | $headers = [ 1989 | 'x-amz-pay-idempotency-key' => uniqid() 1990 | ]; 1991 | 1992 | try { 1993 | $client = new Amazon\Pay\API\Client($amazonpay_config); 1994 | $result = $client->uploadFile($payload, $headers); 1995 | 1996 | if ($result['status'] === 200) { 1997 | // success 1998 | $response = $result['response']; 1999 | } else { 2000 | // check the error 2001 | echo 'status=' . $result['status'] . '; response=' . $result['response'] . "\n"; 2002 | } 2003 | } catch (\Exception $e) { 2004 | echo $e . "\n"; 2005 | http_response_code(500); 2006 | } 2007 | ?> 2008 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amzn/amazon-pay-api-sdk-php", 3 | "type": "library", 4 | "description": "Amazon Pay API SDK (PHP)", 5 | "version": "2.7.0", 6 | "keywords": [ 7 | "amazon", 8 | "pay", 9 | "payment", 10 | "payments", 11 | "pay with amazon", 12 | "amazon payments", 13 | "amazon pay" 14 | ], 15 | "homepage": "https://github.com/amzn/amazon-pay-api-sdk-php", 16 | "license": "Apache-2.0", 17 | "authors": [ 18 | { 19 | "name": "Amazon Pay API SDK", 20 | "email": "amazon-pay-sdk@amazon.com" 21 | } 22 | ], 23 | "autoload": { 24 | "psr-4": { 25 | "Amazon\\Pay\\API\\": "Amazon/Pay/API/" 26 | } 27 | }, 28 | "require": { 29 | "ext-curl": "*", 30 | "php": ">=5.6.1", 31 | "phpseclib/phpseclib": ">=3.0.34" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "^8" 35 | } 36 | } -------------------------------------------------------------------------------- /tests/unit/ClientTest.php: -------------------------------------------------------------------------------- 1 | 'ABC123DEF456XYZ789IJK000', 20 | 'private_key' => 'tests/unit/unit_test_key_private.txt', 21 | 'sandbox' => true, 22 | 'region' => 'na', 23 | 'integrator_id' => self::INTEGRATOR_ID, 24 | 'integrator_version' => self::INTEGRATOR_VERSION, 25 | 'platform_version' => self::PLATFORM_VERSION 26 | ), 27 | //config with algorithm as a parameter 28 | array( 29 | 'public_key_id' => 'ABC123DEF456XYZ789IJK000', 30 | 'private_key' => 'tests/unit/unit_test_key_private.txt', 31 | 'sandbox' => true, 32 | 'region' => 'na', 33 | 'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2', 34 | 'integrator_id' => self::INTEGRATOR_ID, 35 | 'integrator_version' => self::INTEGRATOR_VERSION, 36 | 'platform_version' => self::PLATFORM_VERSION 37 | 38 | ), 39 | //config with proxy parameters 40 | array( 41 | 'public_key_id' => 'ABC123DEF456XYZ789IJK000', 42 | 'private_key' => 'tests/unit/unit_test_key_private.txt', 43 | 'sandbox' => true, 44 | 'region' => 'na', 45 | 'integrator_id' => self::INTEGRATOR_ID, 46 | 'integrator_version' => self::INTEGRATOR_VERSION, 47 | 'platform_version' => self::PLATFORM_VERSION, 48 | 'proxy' => [ 49 | 'host' => 'proxy_host', 50 | 'port' => 'proxy_port', 51 | 'username' => 'proxy_username', 52 | 'password' => 'proxy_password', 53 | ] 54 | ) 55 | ); 56 | 57 | private $requestParameters = array( 58 | 'chargePermissionId' => 'P03-0772540-6944847', 59 | 'chargeReferenceId' => 'chargeReferenceId-1', 60 | 'chargeTotal' => '100.50', 61 | 'currencyCode' => 'JPY', 62 | 'amount' => '100.50', 63 | 'softDescriptor' => 'chargeTest-1', 64 | 'metadata' => array('shoe sale', 'Information about order'), 65 | 'merchantNote' => '', 66 | 'customInformation' => 'Information about order', 67 | 'communicationContext' => array(), 68 | 'merchantStoreName' => 'Name of Store', 69 | 'merchantOrderId' => 'Order 123' 70 | ); 71 | 72 | private $requestHeaders = array( 73 | 'Accept' => 'application/json', 74 | 'Content-Type' => 'application/json', 75 | 'X-Amz-Pay-Host' => 'pay-api.amazon.jp', 76 | 'User-Agent' => '' 77 | ); 78 | 79 | private $uri = "http://pay-api.amazon.jp/sandbox/in-store/v999/charge?extradata"; 80 | private $expectedUnitedStatesURL = 'https://pay-api.amazon.com/'; 81 | private $expectedEuropeURL = 'https://pay-api.amazon.eu/'; 82 | private $expectedJapanURL = 'https://pay-api.amazon.jp/'; 83 | 84 | public function testConfigArray() 85 | { 86 | $client = new Client($this->configArray[0]); 87 | 88 | $this->assertEquals($this->configArray[0]['public_key_id'], $client->__get('public_key_id')); 89 | $this->assertEquals($this->configArray[0]['private_key'], $client->__get('private_key')); 90 | $this->assertEquals($this->configArray[0]['sandbox'], $client->__get('sandbox')); 91 | $this->assertEquals($this->configArray[0]['region'], $client->__get('region')); 92 | $this->assertEquals($this->configArray[0]['integrator_id'], $client->__get('integrator_id')); 93 | $this->assertEquals($this->configArray[0]['integrator_version'], $client->__get('integrator_version')); 94 | $this->assertEquals($this->configArray[0]['platform_version'], $client->__get('platform_version')); 95 | } 96 | 97 | public function testConfigArrayWithAlgorithm() 98 | { 99 | $client = new Client($this->configArray[1]); 100 | 101 | $this->assertEquals($this->configArray[1]['public_key_id'], $client->__get('public_key_id')); 102 | $this->assertEquals($this->configArray[1]['private_key'], $client->__get('private_key')); 103 | $this->assertEquals($this->configArray[1]['sandbox'], $client->__get('sandbox')); 104 | $this->assertEquals($this->configArray[1]['region'], $client->__get('region')); 105 | $this->assertEquals($this->configArray[1]['algorithm'], $client->__get('algorithm')); 106 | $this->assertEquals($this->configArray[1]['integrator_id'], $client->__get('integrator_id')); 107 | $this->assertEquals($this->configArray[1]['integrator_version'], $client->__get('integrator_version')); 108 | $this->assertEquals($this->configArray[1]['platform_version'], $client->__get('platform_version')); 109 | } 110 | 111 | public function testConfigArrayWithProxy() { 112 | $client = new Client($this->configArray[2]); 113 | 114 | $this->assertEquals($this->configArray[2]['public_key_id'], $client->__get('public_key_id')); 115 | $this->assertEquals($this->configArray[2]['private_key'], $client->__get('private_key')); 116 | $this->assertEquals($this->configArray[2]['sandbox'], $client->__get('sandbox')); 117 | $this->assertEquals($this->configArray[2]['region'], $client->__get('region')); 118 | $this->assertEquals($this->configArray[2]['proxy']['host'], $client->__get('proxy')['host']); 119 | $this->assertEquals($this->configArray[2]['proxy']['port'], $client->__get('proxy')['port']); 120 | $this->assertEquals($this->configArray[2]['proxy']['username'], $client->__get('proxy')['username']); 121 | $this->assertEquals($this->configArray[2]['proxy']['password'], $client->__get('proxy')['password']); 122 | $this->assertEquals($this->configArray[2]['integrator_id'], $client->__get('integrator_id')); 123 | $this->assertEquals($this->configArray[2]['integrator_version'], $client->__get('integrator_version')); 124 | $this->assertEquals($this->configArray[2]['platform_version'], $client->__get('platform_version')); 125 | } 126 | 127 | public function testGetCanonicalURI() 128 | { 129 | for ( $i=0; $i<3; $i++ ) { 130 | $client = new Client($this->configArray[$i]); 131 | $class = new \ReflectionClass($client); 132 | $method = $class->getMethod('getCanonicalURI'); 133 | $method->setAccessible(true); 134 | 135 | $uriTrue = "/sandbox/in-store/v999/charge"; 136 | 137 | $this->assertEquals($uriTrue, $method->invoke($client, $this->uri)); 138 | } 139 | } 140 | 141 | public function testSortCanonicalArray() 142 | { 143 | for ( $i=0; $i<3; $i++ ) { 144 | $client = new Client($this->configArray[$i]); 145 | $class = new \ReflectionClass($client); 146 | $method = $class->getMethod('sortCanonicalArray'); 147 | $method->setAccessible(true); 148 | 149 | $canonicalArrayTrue = array( 150 | 'amount' => '100.50', 151 | 'chargePermissionId' => 'P03-0772540-6944847', 152 | 'chargeReferenceId' => 'chargeReferenceId-1', 153 | 'chargeTotal' => '100.50', 154 | 'currencyCode' => 'JPY', 155 | 'customInformation' => 'Information about order', 156 | 'merchantOrderId' => 'Order 123', 157 | 'merchantStoreName' => 'Name of Store', 158 | 'metadata.1' => 'shoe sale', 159 | 'metadata.2' => 'Information about order', 160 | 'softDescriptor' => 'chargeTest-1' 161 | ); 162 | 163 | $this->assertEquals($canonicalArrayTrue, $method->invoke($client, $this->requestParameters)); 164 | } 165 | } 166 | 167 | public function testCreateCanonicalQuery() 168 | { 169 | for ( $i=0; $i<3; $i++ ) { 170 | $client = new Client($this->configArray[$i]); 171 | $class = new \ReflectionClass($client); 172 | $method = $class->getMethod('createCanonicalQuery'); 173 | $method->setAccessible(true); 174 | 175 | $canonicalQueryTrue = ("amount=100.50" . 176 | "&chargePermissionId=P03-0772540-6944847" . 177 | "&chargeReferenceId=chargeReferenceId-1" . 178 | "&chargeTotal=100.50" . 179 | "¤cyCode=JPY" . 180 | "&customInformation=Information%20about%20order" . 181 | "&merchantOrderId=Order%20123" . 182 | "&merchantStoreName=Name%20of%20Store" . 183 | "&metadata.1=shoe%20sale" . 184 | "&metadata.2=Information%20about%20order" . 185 | "&softDescriptor=chargeTest-1"); 186 | 187 | $this->assertEquals($canonicalQueryTrue, $method->invoke($client, $this->requestParameters)); 188 | } 189 | } 190 | 191 | public function testGetCanonicalHeaders() 192 | { 193 | for ( $i=0; $i<3; $i++ ) { 194 | $client = new Client($this->configArray[$i]); 195 | $class = new \ReflectionClass($client); 196 | $method = $class->getMethod('getCanonicalHeaders'); 197 | $method->setAccessible(true); 198 | 199 | $canonicalHeadersTrue = array( 200 | 'accept' => 'application/json', 201 | 'content-type' => 'application/json', 202 | 'x-amz-pay-host' => 'pay-api.amazon.jp' 203 | ); 204 | 205 | $this->assertEquals($canonicalHeadersTrue, $method->invoke($client, $this->requestHeaders)); 206 | } 207 | } 208 | 209 | public function testGetCanonicalHeadersNames() 210 | { 211 | for ( $i=0; $i<3; $i++ ) { 212 | $client = new Client($this->configArray[$i]); 213 | $class = new \ReflectionClass($client); 214 | $method = $class->getMethod('getCanonicalHeadersNames'); 215 | $method->setAccessible(true); 216 | 217 | $canonicalHeadersNamesTrue = 'accept;content-type;x-amz-pay-host'; 218 | 219 | $this->assertEquals($canonicalHeadersNamesTrue, $method->invoke($client, $this->requestHeaders)); 220 | } 221 | } 222 | 223 | public function testGetHost() 224 | { 225 | for ( $i=0; $i<3; $i++ ) { 226 | $client = new Client($this->configArray[$i]); 227 | $class = new \ReflectionClass($client); 228 | $method = $class->getMethod('gethost'); 229 | $method->setAccessible(true); 230 | 231 | $hostTrue = 'pay-api.amazon.jp'; 232 | $this->assertEquals($hostTrue, $method->invoke($client, $this->uri)); 233 | 234 | $emptyHost = '/'; 235 | $this->assertEquals($emptyHost, $method->invoke($client, '')); 236 | } 237 | } 238 | 239 | public function testGetHeaderString() 240 | { 241 | for ( $i=0; $i<3; $i++ ) { 242 | $client = new Client($this->configArray[$i]); 243 | 244 | $headerStringTrue = ( 245 | "accept:application/json\n" . 246 | "content-type:application/json\n" . 247 | "x-amz-pay-host:pay-api.amazon.jp\n" 248 | ); 249 | 250 | $this->assertEquals($headerStringTrue, $client->getHeaderString($this->requestHeaders, $this->uri)); 251 | } 252 | } 253 | 254 | public function testGetPostSignedHeaders() { 255 | $method = 'POST'; 256 | $url = 'https://pay-api.amazon.com/sandbox/in-store/v999/merchantScan'; 257 | $requestParameters = array(); 258 | 259 | $request = array( 260 | 'chargeId' => 'S03-2622124-8818929-C062250', 261 | 'refundReferenceId' => 'refundRef-1', 262 | 'refundTotal' => array( 263 | 'currencyCode' => 'JPY', 264 | 'amount' => 2 265 | ), 266 | 'softDescriptor' => 'TESTSTORE refund', 267 | ); 268 | $payload = json_encode($request); 269 | 270 | for ( $i=0; $i<3; $i++ ) { 271 | $client = new Client($this->configArray[$i]); 272 | 273 | $postSignedHeaders = $client->getPostSignedHeaders($method, $url, $requestParameters, $payload); 274 | $signature = substr($postSignedHeaders[1], strpos($postSignedHeaders[1], "Signature=") + 10); 275 | $this-> assertNotNull($signature); 276 | //TODO: verify signature, see http://phpseclib.sourceforge.net/rsa/2.0/examples.html 277 | } 278 | } 279 | 280 | private function verifySignature($plaintext, $signature) { 281 | $rsa = RSA::loadPrivateKey(file_get_contents('tests/unit/unit_test_key_private.txt'))->withSaltLength(substr( $plaintext, 0, 22 ) === 'AMZN-PAY-RSASSA-PSS-V2' ? 32 : 20 ); 282 | return $rsa->getPublicKey()->verify($plaintext, base64_decode($signature)); 283 | } 284 | 285 | public function testGenerateButtonSignature() { 286 | $payload = '{"storeId":"amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","webCheckoutDetails":{"checkoutReviewReturnUrl":"https://localhost/test/CheckoutReview.php","checkoutResultReturnUrl":"https://localhost/test/CheckoutResult.php"}}'; 287 | 288 | 289 | for ( $i=0; $i<3; $i++ ) { 290 | $client = new Client($this->configArray[$i]); 291 | $signature = $client->generateButtonSignature($payload); 292 | 293 | //config with algorithm as parameter, AMZN-PAY-RSASSA-PSS-V2 is used 294 | if( $i == 1 ){ 295 | $plaintext = "AMZN-PAY-RSASSA-PSS-V2\n8dec52d799607be40f82d5c8e7ecb6c171e6591c41b1111a576b16076c89381c"; 296 | } else { 297 | $plaintext = "AMZN-PAY-RSASSA-PSS\n8dec52d799607be40f82d5c8e7ecb6c171e6591c41b1111a576b16076c89381c"; 298 | } 299 | $this->assertEquals($this->verifySignature($plaintext, $signature), true); 300 | 301 | // confirm "same" sigature is generated if an array is passed in instead of a string 302 | $payloadArray = array( 303 | "storeId" => "amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 304 | "webCheckoutDetails" => array( 305 | "checkoutReviewReturnUrl" => "https://localhost/test/CheckoutReview.php", 306 | "checkoutResultReturnUrl" => "https://localhost/test/CheckoutResult.php" 307 | ), 308 | ); 309 | 310 | $signature = $client->generateButtonSignature($payloadArray); 311 | $this->assertEquals($this->verifySignature($plaintext, $signature), true); 312 | 313 | // confirm "same" signature is generated when quotes and slashes are esacped 314 | $payloadEscaped = '{"storeId\":\"amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"webCheckoutDetails\":{\"checkoutReviewReturnUrl\":\"https:\/\/localhost\/test\/CheckoutReview.php\",\"checkoutResultReturnUrl\":\"https:\/\/localhost\/test\/CheckoutResult.php\"}}'; 315 | $signature = $client->generateButtonSignature($payloadEscaped); 316 | $this->assertEquals($this->verifySignature($plaintext, $signature), true); 317 | } 318 | } 319 | 320 | // Method used to test the Environment Specific Endpoint URL 321 | public function testCreateServiceURL() { 322 | // Constants 323 | $live = 'live/'; 324 | $sandbox = 'sandbox/'; 325 | 326 | // Testing Live specific endpoint for region United States 327 | $this->verifyEnvironmentSpecificEndpoint('us', false, $this->expectedUnitedStatesURL . $live); 328 | 329 | // Testing Sandbox specific endpoint for region United States 330 | $this->verifyEnvironmentSpecificEndpoint('us', true, $this->expectedUnitedStatesURL . $sandbox); 331 | 332 | // Testing Live specific endpoint for region Europe 333 | $this->verifyEnvironmentSpecificEndpoint('eu', false, $this->expectedEuropeURL . $live); 334 | 335 | // Testing Sandbox specific endpoint for region Europe 336 | $this->verifyEnvironmentSpecificEndpoint('eu', true, $this->expectedEuropeURL . $sandbox); 337 | 338 | // Testing Live specific endpoint for region Japan 339 | $this->verifyEnvironmentSpecificEndpoint('jp', false, $this->expectedJapanURL . $live); 340 | 341 | // Testing Sandbox specific endpoint for region Japan 342 | $this->verifyEnvironmentSpecificEndpoint('jp', true, $this->expectedJapanURL . $sandbox); 343 | } 344 | 345 | // Generic method used to verify Environment Specific Endpoint 346 | private function verifyEnvironmentSpecificEndpoint($region, $sandboxFlag, $expectedURL) { 347 | // Configuration 348 | $payConfig = array( 349 | 'public_key_id' => $this->configArray[0]['public_key_id'], 350 | 'private_key' => $this->configArray[0]['private_key'], 351 | 'sandbox' => $sandboxFlag, 352 | 'region' => $region, 353 | ); 354 | $reflectionMethod = self::getMethod('createServiceUrl'); 355 | $client = new Client($payConfig); 356 | 357 | // Building URL 358 | $actualURL = $reflectionMethod->invoke($client); 359 | 360 | // Assertion 361 | $this->assertEquals($actualURL, $expectedURL); 362 | 363 | $payConfigWithAlgorithm = array( 364 | 'public_key_id' => $this->configArray[1]['public_key_id'], 365 | 'private_key' => $this->configArray[1]['private_key'], 366 | 'sandbox' => $sandboxFlag, 367 | 'region' => $region, 368 | 'algorithm' => $this->configArray[1]['algorithm'] 369 | ); 370 | $client = new Client($payConfigWithAlgorithm); 371 | 372 | // Building URL 373 | $actualURL = $reflectionMethod->invoke($client); 374 | 375 | // Assertion 376 | $this->assertEquals($actualURL, $expectedURL); 377 | 378 | } 379 | 380 | // Method used to apply reflection on method which is having abstraction 381 | private static function getMethod($methodName) { 382 | $reflectionClass = new \ReflectionClass('Amazon\Pay\API\Client'); 383 | $reflectionMethod = $reflectionClass->getMethod($methodName); 384 | $reflectionMethod->setAccessible(true); 385 | return $reflectionMethod; 386 | } 387 | 388 | // Method used to test the Unified Endpoint URL 389 | public function testCreateServiceURLForUnifiedEndpoint() { 390 | // Constants 391 | $livePublicKeyId = 'LIVE-XXXXXXXXXXXXXXXXXXXXXXXX'; 392 | $sandboxPublicKeyId = 'SANDBOX-XXXXXXXXXXXXXXXXXXXXXXXX'; 393 | 394 | // Testing Unified endpoint URL by passing Live specific PublicKeyId for UnitedStates 395 | $this->verifyUnifiedEndpoint('us', $livePublicKeyId, $this->expectedUnitedStatesURL); 396 | 397 | // Testing Unified endpoint URL by passing Sandbox specific PublicKeyId for UnitedStates 398 | $this->verifyUnifiedEndpoint('us', $sandboxPublicKeyId, $this->expectedUnitedStatesURL); 399 | 400 | // Testing Unified endpoint URL by passing Live specific PublicKeyId for Europe 401 | $this->verifyUnifiedEndpoint('eu', $livePublicKeyId, $this->expectedEuropeURL); 402 | 403 | // Testing Unified endpoint URL by passing Sandbox specific PublicKeyId for Europe 404 | $this->verifyUnifiedEndpoint('eu', $sandboxPublicKeyId, $this->expectedEuropeURL); 405 | 406 | // Testing Unified endpoint URL by passing Live specific PublicKeyId for Japan 407 | $this->verifyUnifiedEndpoint('jp', $livePublicKeyId, $this->expectedJapanURL); 408 | 409 | // Testing Unified endpoint URL by passing Sandbox specific PublicKeyId for Japan 410 | $this->verifyUnifiedEndpoint('jp', $sandboxPublicKeyId, $this->expectedJapanURL); 411 | } 412 | 413 | // Generic method used to verify Unified Endpoint 414 | private function verifyUnifiedEndpoint($region, $publicKeyId, $expectedURL) { 415 | // Configuration 416 | $payConfig = array( 417 | 'public_key_id' => $publicKeyId, 418 | 'private_key' => $this->configArray[0]['private_key'], 419 | 'region' => $region, 420 | ); 421 | $reflectionMethod = self::getMethod('createServiceUrl'); 422 | $client = new Client($payConfig); 423 | 424 | // Building URL 425 | $actualURL = $reflectionMethod->invoke($client); 426 | 427 | // Assertion 428 | $this->assertEquals($actualURL, $expectedURL); 429 | 430 | 431 | } 432 | 433 | } 434 | -------------------------------------------------------------------------------- /tests/unit/unit_test_key_private.txt: -------------------------------------------------------------------------------- 1 | Enter your private key. -------------------------------------------------------------------------------- /tests/unit/unit_test_key_public.txt: -------------------------------------------------------------------------------- 1 | Enter your public key. --------------------------------------------------------------------------------