jenkins-bot has submitted this change and it was merged.

Change subject: Handle results of Amazon API calls
......................................................................


Handle results of Amazon API calls

* Redirect to thank you page on success, failpage on hard fail.

* Add call to runPostProcessHooks

* Don't repeat calls to setOrderReferenceDetails for the same order
reference

* Show error messages when donor can try another card or repeat
attempt later

* Redisplay wallet widget on InvalidPaymentMethod

* Display login button on token expiration

TODO: make amount non-editable after first submit

Bug: T108123
Change-Id: I1eefabfde29aafdf0975ba5da1e17dae90853a76
---
M amazon_gateway/amazon.adapter.php
M amazon_gateway/amazon.api.php
M amazon_gateway/amazon.js
M gateway_common/i18n/interface/en.json
M gateway_common/i18n/interface/qqq.json
M tests/Adapter/Amazon/AmazonTest.php
A tests/includes/Responses/amazon/authorize_AmazonRejected.json
A tests/includes/Responses/amazon/authorize_TransactionTimedOut.json
8 files changed, 299 insertions(+), 33 deletions(-)

Approvals:
  Awight: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/amazon_gateway/amazon.adapter.php 
b/amazon_gateway/amazon.adapter.php
index bc4d269..49b2e88 100644
--- a/amazon_gateway/amazon.adapter.php
+++ b/amazon_gateway/amazon.adapter.php
@@ -26,6 +26,7 @@
  * and https://github.com/amzn/login-and-pay-with-amazon-sdk-php
  */
 class AmazonAdapter extends GatewayAdapter {
+
        const GATEWAY_NAME = 'Amazon';
        const IDENTIFIER = 'amazon';
        const GLOBAL_PREFIX = 'wgAmazonGateway';
@@ -37,10 +38,23 @@
                'Declined' => FinalStatus::FAILED,
        );
 
+       // When an authorization or capture is declined, we examine the reason 
code
+       // to see if we should let the donor try again with a different card.  
For
+       // these codes, we should tell the donor to try a different method 
entirely.
+       protected $fatal_errors = array(
+               // These two may show up if we start doing asynchronous 
authorization
+               'AmazonClosed',
+               'AmazonRejected',
+               // For synchronous authorization, timeouts usually indicate 
that the
+               // donor's account is under scrutiny, so letting them choose a 
different
+               // card would likely just time out again
+               'TransactionTimedOut',
+       );
+
        function __construct( $options = array() ) {
                parent::__construct( $options );
 
-               if ($this->getData_Unstaged_Escaped( 'payment_method' ) == null 
) {
+               if ( $this->getData_Unstaged_Escaped( 'payment_method' ) == 
null ) {
                        $this->addRequestData(
                                array( 'payment_method' => 'amazon' )
                        );
@@ -49,7 +63,7 @@
        }
 
        public function getFormClass() {
-               if ( strpos( $this->dataObj->getVal_Escaped( 'ffname' ), 
'error') === 0 ) {
+               if ( strpos( $this->dataObj->getVal_Escaped( 'ffname' ), 
'error' ) === 0 ) {
                        // TODO: make a mustache error form
                        return parent::getFormClass();
                }
@@ -75,10 +89,13 @@
                // We use account_config instead
                $this->accountInfo = array();
        }
+
        function defineReturnValueMap() {}
+
        function defineDataConstraints() {}
+
        function defineOrderIDMeta() {
-               $this->order_id_meta = array (
+               $this->order_id_meta = array(
                        'generate' => TRUE,
                        'ct_id' => TRUE,
                );
@@ -87,7 +104,24 @@
        function setGatewayDefaults() {}
 
        public function defineErrorMap() {
-               $this->error_map = array();
+               $self = $this;
+               $differentCard = function() use ( $self ) {
+                       $language = $self->getData_Unstaged_Escaped( 'language' 
);
+                       $country = $self->getData_Unstaged_Escaped( 'country' );
+                       return WmfFramework::formatMessage(
+                               
'donate_interface-donate-error-try-a-different-card-html', 
'https://wikimediafoundation.org/wiki/Special:LandingCheck?basic=true&landing_page=Ways_to_Give'
+                               . 
"&language=$language&uselang=$language&country=$country", 
'problemsdonat...@wikimedia.org'
+                       );
+               };
+               $this->error_map = array(
+                       // These might be transient - tell donor to try again 
soon
+                       'InternalServerError' => 'donate_interface-try-again',
+                       'RequestThrottled' => 'donate_interface-try-again',
+                       'ServiceUnavailable' => 'donate_interface-try-again',
+                       'ProcessingFailure' => 'donate_interface-try-again',
+                       // Donor needs to select a different card
+                       'InvalidPaymentMethod' => $differentCard,
+               );
        }
 
        function defineTransactions() {
@@ -123,10 +157,7 @@
                        $this->confirmOrderReference();
                        $this->authorizeAndCapturePayment();
                } catch ( ResponseProcessingException $ex ) {
-                       $resultData->addError(
-                               $ex->getErrorCode(),
-                               $ex->getMessage()
-                       );
+                       $this->handleErrors( $ex, $resultData );
                }
 
                $this->incrementSequenceNumber();
@@ -165,20 +196,15 @@
 
                $orderReferenceId = $this->getData_Staged( 'order_reference_id' 
);
 
-               $setDetailsResult = $client->setOrderReferenceDetails( array(
-                       'amazon_order_reference_id' => $orderReferenceId,
-                       'amount' => $this->getData_Staged( 'amount' ),
-                       'currency_code' => $this->getData_Staged( 
'currency_code' ),
-                       'seller_note' => WmfFramework::formatMessage( 
'donate_interface-donation-description' ),
-                       'seller_order_reference_id' => $this->getData_Staged( 
'order_id' ),
-               ) )->toArray();
-               self::checkErrors( $setDetailsResult );
+               $this->setOrderReferenceDetailsIfUnset( $client, 
$orderReferenceId );
 
                $confirmResult = $client->confirmOrderReference( array(
                        'amazon_order_reference_id' => $orderReferenceId,
                ) )->toArray();
                self::checkErrors( $confirmResult );
 
+               // TODO: either check the status, or skip this call when we 
already have
+               // donor details
                $getDetailsResult = $client->getOrderReferenceDetails( array(
                        'amazon_order_reference_id' => $orderReferenceId,
                ) )->toArray();
@@ -195,6 +221,33 @@
                        'fname' => $fname,
                        'lname' => $lname,
                ) );
+               // Stash their info in pending queue and logs to fill in data 
for
+               // audit and IPN messages
+               $details = $this->getStompTransaction();
+               $this->logger->info( 'Got info for Amazon donation: ' . 
json_encode( $details ) );
+               $this->setLimboMessage( 'pending' );
+       }
+
+       /**
+        * Set the order reference details if they haven't been set yet.  Track
+        * which ones have been set in session.
+        * @param PwaClientInterface $client
+        * @param string $orderReferenceId
+        */
+       protected function setOrderReferenceDetailsIfUnset( $client, 
$orderReferenceId ) {
+               if ( $this->session_getData( 'order_refs', $orderReferenceId ) 
) {
+                       return;
+               }
+               $setDetailsResult = $client->setOrderReferenceDetails( array(
+                       'amazon_order_reference_id' => $orderReferenceId,
+                       'amount' => $this->getData_Staged( 'amount' ),
+                       'currency_code' => $this->getData_Staged( 
'currency_code' ),
+                       'seller_note' => WmfFramework::formatMessage( 
'donate_interface-donation-description' ),
+                       'seller_order_reference_id' => $this->getData_Staged( 
'order_id' ),
+               ) )->toArray();
+               self::checkErrors( $setDetailsResult );
+               // TODO: session_setData wrapper?
+               $_SESSION['order_refs'][$orderReferenceId] = true;
        }
 
        /**
@@ -217,6 +270,10 @@
                        'authorization_reference_id' => $this->getData_Staged( 
'order_id' ),
                        'transaction_timeout' => 0, // authorize synchronously
                        // Could set 'SoftDescriptor' to control what appears 
on CC statement (16 char max, prepended with AMZ*)
+                       // Use the seller_authorization_note to simulate an 
error in the sandbox
+                       // See 
https://payments.amazon.com/documentation/lpwa/201749840#201750790
+                       // 'seller_authorization_note' => 
'{"SandboxSimulation": {"State":"Declined", 
"ReasonCode":"TransactionTimedOut"}}',
+                       // 'seller_authorization_note' => 
'{"SandboxSimulation": {"State":"Declined", 
"ReasonCode":"InvalidPaymentMethod"}}',
                ) )->toArray();
                $this->checkErrors( $authResponse );
 
@@ -245,11 +302,21 @@
                $captureDetails = 
$captureResponse['GetCaptureDetailsResult']['CaptureDetails'];
                $captureState = $captureDetails['CaptureStatus']['State'];
 
+               // TODO: verify that this does not prevent us from refunding.
+               $this->closeOrderReference();
+
+               $this->finalizeInternalStatus( 
$this->capture_status_map[$captureState] );
+               $this->runPostProcessHooks();
+               $this->deleteLimboMessage( 'pending' );
+       }
+
+       protected function closeOrderReference() {
+               $client = $this->getPwaClient(); // maybe just make this a 
member variable
+               $orderReferenceId = $this->getData_Staged( 'order_reference_id' 
);
+
                $client->closeOrderReference( array(
                        'amazon_order_reference_id' => $orderReferenceId,
                ) );
-
-               $this->finalizeInternalStatus( 
$this->capture_status_map[$captureState] );
        }
 
        /**
@@ -305,4 +372,25 @@
                $vars['wgAmazonGatewayWidgetScript'] = 
$this->account_config['WidgetScriptURL'];
                $vars['wgAmazonGatewayLoginScript'] = $this->getGlobal( 
'LoginScript' );
        }
+
+       /**
+        * FIXME: this synthesized 'TransactionResponse' is increasingly silly
+        * Maybe make this adapter more normal by adding an 'SDK' communication 
type
+        * that just creates an array of $data, then overriding curl_transaction
+        * to use the PwaClient.
+        * @param ResponseProcessingException $exception
+        * @param PaymentTransactionResponse $resultData
+        */
+       public function handleErrors( $exception, $resultData ) {
+               $errorCode = $exception->getErrorCode();
+               $resultData->addError(
+                       $errorCode, $this->getErrorMapByCodeAndTranslate( 
$errorCode )
+               );
+               if ( array_search( $errorCode, $this->fatal_errors ) !== false 
) {
+                       // These seem potentially fraudy - let's pay attention 
to them
+                       $this->logger->error( 'Heinous status returned from 
Amazon: ' . $errorCode );
+                       $this->finalizeInternalStatus( FinalStatus::FAILED );
+               }
+       }
+
 }
diff --git a/amazon_gateway/amazon.api.php b/amazon_gateway/amazon.api.php
index 65db0be..bacdb92 100644
--- a/amazon_gateway/amazon.api.php
+++ b/amazon_gateway/amazon.api.php
@@ -9,6 +9,7 @@
        );
 
        public function execute() {
+               $output = $this->getResult();
                $orderReferenceId = $this->getParameter( 'orderReferenceId' );
                $adapterParams = array(
                        'api_request' => true,
@@ -25,22 +26,28 @@
                                'order_reference_id' => $orderReferenceId,
                        ) );
                        $result = $adapter->doPayment();
-                       if ( $result->getRefresh() ) {
-                               $this->getResult()->addValue(
+                       if ( $result->isFailed() ) {
+                               $output->addvalue(
+                                       null,
+                                       'redirect',
+                                       $adapter->getFailPage()
+                               );
+                       } else if ( $result->getRefresh() ) {
+                               $output->addValue(
                                        null,
                                        'errors',
                                        $result->getErrors()
                                );
                        } else {
-                               $this->getResult()->addValue(
+                               $output->addValue(
                                        null,
-                                       'success',
-                                       !$result->isFailed()
+                                       'redirect',
+                                       $adapter->getThankYouPage()
                                );
                        }
                } else {
                        // Don't let people continue if they failed a token 
check!
-                       $this->getResult()->addValue(
+                       $output->addValue(
                                null,
                                'errors',
                                array( 'token-mismatch' => $this->msg( 
'donate_interface-token-mismatch' )->text() )
diff --git a/amazon_gateway/amazon.js b/amazon_gateway/amazon.js
index 437d902..94a0617 100644
--- a/amazon_gateway/amazon.js
+++ b/amazon_gateway/amazon.js
@@ -40,10 +40,16 @@
                amazon.Login.authorize( loginOptions, returnUrl );
        }
 
-       function showErrorAndLoginButton( message ) {
+       function addErrorMessage( message ) {
                $( '#topError' ).append(
-                       $( '<div class="error">' + message + '</div>' )
+                       $( '<p class="error">' + message + '</p>' )
                );
+       }
+
+       function showErrorAndLoginButton( message ) {
+               if ( message ) {
+                       addErrorMessage( message );
+               }
                OffAmazonPayments.Button(
                        'amazonLogin',
                        sellerId,
@@ -56,21 +62,29 @@
                );
        }
 
+       function tokenExpired() {
+               // Re-create widget so it displays timeout error message
+               createWalletWidget();
+               showErrorAndLoginButton();
+       }
+
        accessToken = getURLParameter( 'access_token', location.hash );
        loginError = getURLParameter( 'error', location.search );
 
        // This will be called as soon as the login script is loaded
        window.onAmazonLoginReady = function() {
+               var tokenLifetime;
                amazon.Login.setClientId( clientId );
                amazon.Login.setUseCookie( true );
                amazon.Login.setSandboxMode( sandbox );
                if ( loggedIn ) {
+                       tokenLifetime = parseInt( getURLParameter( 
'expires_in', location.hash ), 10 );
                        createWalletWidget();
+                       setTimeout( tokenLifetime * 1000, tokenExpired );
                } else {
                        if ( loginError ) {
                                showErrorAndLoginButton(
                                        getURLParameter( 'error_description', 
location.search )
-                                       // TODO: better error message with 
links to alternative donation methods
                                );
                        } else {
                                redirectToLogin();
@@ -94,7 +108,7 @@
        }
 
        function createWalletWidget() {
-               new OffAmazonPayments.Widgets.Wallet( {
+               var params = {
                        sellerId: sellerId,
                        onReady: function( billingAgreement ) {
                                // Will come in handy for recurring payments
@@ -102,22 +116,63 @@
                        },
                        agreementType: 'OrderReference',
                        onOrderReferenceCreate: function( orderReference ) {
+                               if ( orderReferenceId ) {
+                                       // Redisplaying for an existing order, 
no need to continue
+                                       return;
+                               }
                                orderReferenceId = 
orderReference.getAmazonOrderReferenceId();
                                $( '#paymentContinue' ).show();
+                               // FIXME: Unbind click handler from forms.js
                                $( '#paymentContinueBtn' ).off( 'click' );
                                $( '#paymentContinueBtn' ).click( submitPayment 
);
+                       },
+                       onPaymentSelect: function() {
+                               // In case we hid the button because of an 
invalid payment error
+                               $( '#paymentContinue' ).show();
                        },
                        design: {
                                designMode: 'responsive'
                        },
                        onError: function( error ) {
                                // Error message appears directly in widget
-                               showErrorAndLoginButton( '' );
+                               showErrorAndLoginButton();
                        }
-               } ).bind( 'walletWidget' );
+               };
+               // If we are refreshing the widget to display a correctable 
error,
+               // we need to set the Amazon order reference ID for continuity
+               if ( orderReferenceId ) {
+                       params.amazonOrderReferenceId = orderReferenceId;
+               }
+               new OffAmazonPayments.Widgets.Wallet( params ).bind( 
'walletWidget' );
        }
 
+       function handleErrors( errors ) {
+               var code,
+                       refreshWallet = false;
+
+               for ( code in errors ) {
+                       if ( !errors.hasOwnProperty( code ) ) {
+                               continue;
+                       }
+                       addErrorMessage( errors[code] );
+                       if ( code === 'InvalidPaymentMethod' ) {
+                               // Card declined, but they can try another
+                               refreshWallet = true;
+                       }
+               }
+
+               if ( refreshWallet ) {
+                       // Redisplay the widget to show an error and let the 
donor pick a different card
+                       $( '#paymentContinue' ).hide();
+                       createWalletWidget();
+               }
+       }
+
+       // FIXME: if donation amount is edited after we call 
setOrderReferenceDetails
+       // once, we need to close the old order reference and get a new one on 
retry.
+       // Maybe just make it non-editable here?
        function submitPayment() {
+               $( '#topError' ).html('');
                $( '#overlay' ).show();
                var postdata = {
                        action: 'di_amazon_bill',
@@ -135,9 +190,9 @@
                        success: function ( data ) {
                                $( '#overlay' ).hide();
                                if ( data.errors ) {
-                                       // TODO: correctable error, let 'em 
correct it
-                               } else if ( data.success ) {
-                                       // TODO: send donor to TY page, 
auth/capture money
+                                       handleErrors( data.errors );
+                               } else if ( data.redirect ) {
+                                       location.href = data.redirect;
                                } else {
                                        // TODO: send donor to fail page
                                }
diff --git a/gateway_common/i18n/interface/en.json 
b/gateway_common/i18n/interface/en.json
index fe5371a..b34b3e0 100644
--- a/gateway_common/i18n/interface/en.json
+++ b/gateway_common/i18n/interface/en.json
@@ -218,6 +218,7 @@
        "donate_interface-error-msg-fiscal_number-pe": "RUC",
        "donate_interface-error-msg-fiscal_number-uy": "RUT",
        "donate_interface-donate-error-try-a-different-card": "Please [$1 try a 
different card] or one of our [$2 other ways to give] or contact us at $3",
+       "donate_interface-donate-error-try-a-different-card-html": "Please try 
a different card or one of our <a href=\"$1\">other ways to give</a>, or 
contact us at <a href=\"mailto:$2\";>$2</a>",
        "donate_interface-donate-error-try-again-html": "Please <a 
href=\"$1\">try again</a>, try one of our <a href=\"$2\">other ways to 
give</a>, or contact us at <a href=\"mailto:$3\";>$3</a>",
        "donate_interface-donate-error-thank-you-for-your-support": "Thank you 
for your support!",
        "donate_interface-error-no-form": "We were unable to find a donation 
form matching your parameters. Please contact [mailto:don...@wikimedia.org our 
help team] for more information.",
diff --git a/gateway_common/i18n/interface/qqq.json 
b/gateway_common/i18n/interface/qqq.json
index 0e720f1..31464cd 100644
--- a/gateway_common/i18n/interface/qqq.json
+++ b/gateway_common/i18n/interface/qqq.json
@@ -242,6 +242,7 @@
        "donate_interface-error-msg-fiscal_number-pe": "Peru-specific term for 
the fiscal number or tax id.  This is the RUC (Registro Único de 
Contribuyentes) assigned by the Peruvian government.",
        "donate_interface-error-msg-fiscal_number-uy": "Uruguay-specific term 
for the fiscal number or tax id.  This is the RUT (Registro Único Tributario) 
assigned by the Uruguayan government.",
        "donate_interface-donate-error-try-a-different-card": "This message 
will be displayed in the the article /index.php/Donate-error. Parameters:\n* $1 
- link back to the form to try another credit card\n* $2 - link to other 
payment methods\n* $3 - an e-mail address link such as: 
mailto:some...@example.com";,
+       "donate_interface-donate-error-try-a-different-card-html": 
"{{Related|donate_interface-donate-error-try-a-different-card}}This message 
will be displayed in the payments form where the donor can select a different 
card type. Parameters:\n* $1 - link to other payment methods\n* $2 - an e-mail 
address link such as: mailto:some...@example.com";,
        "donate_interface-donate-error-try-again-html": "This html-formatted 
message will be used on dynamic error pages for all payment 
types.\n\nParameters:\n* $1 - link back to the payments form most recently used 
by the donor, to try again\n* $2 - link to other payment methods\n* $3 - an 
email address, where the donor can report problems. Example: 
problemsdonat...@wikimedia.org",
        "donate_interface-donate-error-thank-you-for-your-support": "Thank you 
for your support!",
        "donate_interface-error-no-form": "Error message given if no form or 
payment method is available in this language/country/currency.",
diff --git a/tests/Adapter/Amazon/AmazonTest.php 
b/tests/Adapter/Amazon/AmazonTest.php
index e7ff302..bb09574 100644
--- a/tests/Adapter/Amazon/AmazonTest.php
+++ b/tests/Adapter/Amazon/AmazonTest.php
@@ -184,4 +184,50 @@
                $errors = $result->getErrors();
                $this->assertTrue( isset( $errors['InvalidPaymentMethod'] ), 
'InvalidPaymentMethod error should be set' );
        }
+
+       /**
+        * This apparently indicates a shady enough txn that we should turn 
them away
+        */
+       function testFailOnAmazonRejected() {
+               $init = $this->getDonorTestData( 'US' );
+               $init['amount'] = '10.00';
+               $init['order_reference_id'] = mt_rand( 0, 10000000 ); // 
provided by client-side widget IRL
+               // We don't get any profile data up front
+               unset( $init['email'] );
+               unset( $init['fname'] );
+               unset( $init['lname'] );
+
+               $mockClient = TestingAmazonAdapter::$client;
+               $mockClient->returns['authorize'][] = 'AmazonRejected';
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $result = $gateway->doPayment();
+
+               $this->assertTrue( $result->isFailed(), 'Result should be 
failed' );
+               // Could assert something about errors after rebasing onto 
master
+               // $errors = $result->getErrors();
+               // $this->assertTrue( isset( $errors['AmazonRejected'] ), 
'AmazonRejected error should be set' );
+       }
+
+       /**
+        * When the transaction times out, just gotta fail it till we work out 
an
+        * asynchronous authorization flow
+        */
+       function testTransactionTimedOut() {
+               $init = $this->getDonorTestData( 'US' );
+               $init['amount'] = '10.00';
+               $init['order_reference_id'] = mt_rand( 0, 10000000 ); // 
provided by client-side widget IRL
+               // We don't get any profile data up front
+               unset( $init['email'] );
+               unset( $init['fname'] );
+               unset( $init['lname'] );
+
+               $mockClient = TestingAmazonAdapter::$client;
+               $mockClient->returns['authorize'][] = 'TransactionTimedOut';
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $result = $gateway->doPayment();
+
+               $this->assertTrue( $result->isFailed(), 'Result should be 
failed' );
+       }
 }
diff --git a/tests/includes/Responses/amazon/authorize_AmazonRejected.json 
b/tests/includes/Responses/amazon/authorize_AmazonRejected.json
new file mode 100644
index 0000000..687ea6d
--- /dev/null
+++ b/tests/includes/Responses/amazon/authorize_AmazonRejected.json
@@ -0,0 +1,34 @@
+{
+    "AuthorizeResult": {
+        "AuthorizationDetails": {
+            "AuthorizationAmount": {
+                "CurrencyCode": "USD",
+                "Amount": "10.00"
+            },
+            "CapturedAmount": {
+                "CurrencyCode": "USD",
+                "Amount": "0"
+            },
+            "SoftDescriptor": "AMZ*Wikimedia Founda",
+            "ExpirationTimestamp": "2015-10-01T22:31:02.551Z",
+            "AuthorizationStatus": {
+                "LastUpdateTimestamp": "2015-09-01T22:31:02.551Z",
+                "State": "Declined",
+                "ReasonCode": "AmazonRejected"
+            },
+            "AuthorizationFee": {
+                "CurrencyCode": "USD",
+                "Amount": "0.00"
+            },
+            "CaptureNow": "true",
+            "SellerAuthorizationNote": "{\"SandboxSimulation\": 
{\"State\":\"Declined\", \"ReasonCode\":\"AmazonRejected\"}}",
+            "CreationTimestamp": "2015-09-01T22:31:02.551Z",
+            "AmazonAuthorizationId": "S01-7821958-9177140-A084347",
+            "AuthorizationReferenceId": "36435-0"
+        }
+    },
+    "ResponseMetadata": {
+        "RequestId": "4a509982-d8cb-4dec-ad56-81a9dc5f070c"
+    },
+    "ResponseStatus": "200"
+}
\ No newline at end of file
diff --git a/tests/includes/Responses/amazon/authorize_TransactionTimedOut.json 
b/tests/includes/Responses/amazon/authorize_TransactionTimedOut.json
new file mode 100644
index 0000000..c2a1cf2
--- /dev/null
+++ b/tests/includes/Responses/amazon/authorize_TransactionTimedOut.json
@@ -0,0 +1,34 @@
+{
+    "AuthorizeResult": {
+        "AuthorizationDetails": {
+            "AuthorizationAmount": {
+                "CurrencyCode": "USD",
+                "Amount": "10.00"
+            },
+            "CapturedAmount": {
+                "CurrencyCode": "USD",
+                "Amount": "0"
+            },
+            "SoftDescriptor": "AMZ*Wikimedia Founda",
+            "ExpirationTimestamp": "2015-10-02T17:14:02.214Z",
+            "AuthorizationStatus": {
+                "LastUpdateTimestamp": "2015-09-02T17:14:02.214Z",
+                "State": "Declined",
+                "ReasonCode": "TransactionTimedOut"
+            },
+            "AuthorizationFee": {
+                "CurrencyCode": "USD",
+                "Amount": "0.00"
+            },
+            "CaptureNow": "true",
+            "SellerAuthorizationNote": "{\"SandboxSimulation\": 
{\"State\":\"Declined\", \"ReasonCode\":\"TransactionTimedOut\"}}",
+            "CreationTimestamp": "2015-09-02T17:14:02.214Z",
+            "AmazonAuthorizationId": "S01-6689996-9664966-A069817",
+            "AuthorizationReferenceId": "36450-0"
+        }
+    },
+    "ResponseMetadata": {
+        "RequestId": "06488d3c-bcee-4448-8f0a-bd6f816e6fec"
+    },
+    "ResponseStatus": "200"
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/233993
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I1eefabfde29aafdf0975ba5da1e17dae90853a76
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/extensions/DonationInterface
Gerrit-Branch: amazon
Gerrit-Owner: Ejegg <eeggles...@wikimedia.org>
Gerrit-Reviewer: AndyRussG <andrew.green...@gmail.com>
Gerrit-Reviewer: Awight <awi...@wikimedia.org>
Gerrit-Reviewer: Cdentinger <cdentin...@wikimedia.org>
Gerrit-Reviewer: Ejegg <eeggles...@wikimedia.org>
Gerrit-Reviewer: Katie Horn <kh...@wikimedia.org>
Gerrit-Reviewer: Ssmith <ssm...@wikimedia.org>
Gerrit-Reviewer: XenoRyet <dkozlow...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to