/**
  * 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->retry_errors) === false) {
         // Fail on anything we don't recognize as retry-able.  For example:
         // 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',
         // These seem potentially fraudy - let's pay attention to them
         $this->logger->error('Heinous status returned from Amazon: ' . $errorCode);
         $this->finalizeInternalStatus(FinalStatus::FAILED);
     }
 }
 /**
  * Show the special page
  */
 protected function handleRequest()
 {
     $req = $this->getRequest();
     // TODO: Don't do that.
     $fake = $req->getBool('fake');
     $fail = $req->getBool('fail');
     if ($fake) {
         if ($fail) {
             $this->displayFailPage();
             return;
         } else {
             $go = ResultPages::getThankYouPage($this->adapter);
         }
         $this->getOutput()->addHTML("<br>Redirecting to page {$go}");
         $this->getOutput()->redirect($go);
         return;
     }
     $forbidden = false;
     $this->qs_oid = $req->getText('order_id', '');
     $this->qs_ref = $req->getText('REF', '');
     if ($this->qs_oid === '' && $this->qs_ref === '') {
         $forbidden = true;
         $f_message = 'No order ID in the Querystring.';
     } else {
         $result = $this->popout_if_iframe();
         if ($result) {
             return;
         }
     }
     $session_oid = $this->adapter->session_getData('Donor', 'order_id');
     if (is_null($session_oid) || $this->qs_oid !== $session_oid && strpos($this->qs_ref, (string) $session_oid) === false) {
         $forbidden = true;
         $f_message = "Requested order id not present in the session. (session_oid = '{$session_oid}')";
     }
     if ($forbidden) {
         $this->logger->error($this->qs_oid . " Resultswitcher: forbidden for reason: {$f_message}");
         wfHttpError(403, 'Forbidden', wfMessage('donate_interface-error-http-403')->text());
         return;
     }
     $this->setHeaders();
     $this->logger->info("Resultswitcher: OK to process Order ID: " . $this->qs_oid);
     // dispatch forms/handling
     if ($this->adapter->checkTokens()) {
         // Display form for the first time
         //this next block is for credit card coming back from GC. Only that. Nothing else, ever.
         if ($this->adapter->getData_Unstaged_Escaped('payment_method') === 'cc') {
             $sessionOrders = $req->getSessionData('order_status');
             if (!is_array($sessionOrders) || !isset($sessionOrders[$this->qs_oid]) || !is_array($sessionOrders[$this->qs_oid])) {
                 $result = $this->adapter->do_transaction('Confirm_CreditCard');
                 $session_info = array('data' => $result->getData(), 'message' => $result->getMessage(), 'errors' => $result->getErrors());
                 $sessionOrders[$this->qs_oid] = $session_info;
                 $sessionOrders[$this->qs_oid]['data']['count'] = 0;
             } else {
                 $sessionOrders = $req->getSessionData('order_status');
                 $sessionOrders[$this->qs_oid]['data']['count'] = $sessionOrders[$this->qs_oid]['data']['count'] + 1;
                 $this->logger->error("Resultswitcher: Multiple attempts to process. " . $sessionOrders[$this->qs_oid]['data']['count']);
                 $result = new PaymentTransactionResponse();
                 $result->setData($sessionOrders[$this->qs_oid]['data']);
                 $result->setMessage($sessionOrders[$this->qs_oid]['message']);
                 $result->setErrors($sessionOrders[$this->qs_oid]['errors']);
             }
             $req->setSessionData('order_status', $sessionOrders);
             $this->displayResultsForDebug($result);
             //do the switching between the... stuff.
             $status = $this->adapter->getFinalStatus();
             if ($status) {
                 switch ($status) {
                     case FinalStatus::COMPLETE:
                     case FinalStatus::PENDING:
                     case FinalStatus::PENDING_POKE:
                         $this->logger->info("Displaying thank you page for final status {$status}");
                         $go = ResultPages::getThankYouPage($this->adapter);
                         break;
                     case FinalStatus::FAILED:
                         $this->logger->info('Displaying fail page for final status failed.');
                         $this->displayFailPage();
                         return;
                 }
                 if ($go) {
                     $this->getOutput()->addHTML("<br>Redirecting to page {$go}");
                     $this->getOutput()->redirect($go);
                     return;
                 } else {
                     $this->logger->error("Resultswitcher: No redirect defined. Order ID: {$this->qs_oid}");
                 }
             } else {
                 $this->logger->error("Resultswitcher: No FinalStatus. Order ID: {$this->qs_oid}");
             }
         } else {
             $this->logger->error("Resultswitcher: Payment method is not cc. Order ID: {$this->qs_oid}");
         }
     } else {
         $this->logger->error("Resultswitcher: Token Check Failed. Order ID: {$this->qs_oid}");
     }
     $this->displayFailPage();
 }
 /**
  * Build a PaymentResult object from adapter results
  *
  * @param PaymentTransactionResponse $response processed response object
  * @param string $finalStatus final transaction status.
  *
  * @return PaymentResult
  */
 public static function fromResults(PaymentTransactionResponse $response, $finalStatus)
 {
     if ($finalStatus === FinalStatus::FAILED) {
         return PaymentResult::newFailure($response->getErrors());
     }
     if (!$response) {
         return PaymentResult::newEmpty();
     }
     if ($response->getErrors()) {
         // TODO: We will probably want the ability to refresh to a new form
         // as well and display errors at the same time.
         return PaymentResult::newRefresh($response->getErrors());
     }
     if ($response->getRedirect()) {
         return PaymentResult::newRedirect($response->getRedirect());
     }
     return PaymentResult::newSuccess();
 }
 /**
  * Returns an array of errors, in the format $error_code => $error_message.
  * This should be an empty array on transaction success.
  *
  * @deprecated
  *
  * @return array
  */
 public function getTransactionErrors()
 {
     if ($this->transaction_response && $this->transaction_response->getErrors()) {
         $simplify = function ($error) {
             return $error['message'];
         };
         return array_map($simplify, $this->transaction_response->getErrors());
     } else {
         return array();
     }
 }
 /**
  * Either confirm or reject the payment
  *
  * FIXME: This function is way too complex.  Unroll into new functions.
  *
  * @return PaymentTransactionResponse
  */
 private function transactionConfirm_CreditCard()
 {
     $is_orphan = $this->isBatchProcessor();
     if ($is_orphan) {
         // We're in orphan processing mode, so a "pending waiting for donor
         // input" status means that we'll never complete.  Set this range
         // to map to "failed".
         $this->addCodeRange('GET_ORDERSTATUS', 'STATUSID', FinalStatus::FAILED, 0, 70);
     }
     $cancelflag = false;
     //this will denote the thing we're trying to do with the donation attempt
     $problemflag = false;
     //this will get set to true, if we can't continue and need to give up and just log the hell out of it.
     $problemmessage = '';
     //to be used in conjunction with the flag.
     $problemseverity = LogLevel::ERROR;
     //to be used also in conjunction with the flag, to route the message to the appropriate log. Urf.
     $original_status_code = NULL;
     $loopcount = $this->getGlobal('RetryLoopCount');
     $loops = 0;
     $status_response = null;
     for ($loops = 0; $loops < $loopcount && !$cancelflag && !$problemflag; ++$loops) {
         $status_result = $this->do_transaction('GET_ORDERSTATUS');
         $validationAction = $this->getValidationAction();
         $cvv_result = $this->getData_Unstaged_Escaped('cvv_result');
         $gotCVV = strlen($cvv_result) > 0;
         // TODO: This logging is redundant with the response from GET_ORDERSTATUS.
         $logmsg = 'CVV Result: ' . $this->getData_Unstaged_Escaped('cvv_result');
         $logmsg .= ', AVS Result: ' . $this->getData_Unstaged_Escaped('avs_result');
         $this->logger->info($logmsg);
         // FIXME: "isForceCancel"?
         if ($status_result->getForceCancel()) {
             $cancelflag = true;
             //don't retry or MasterCard will fine us
         }
         //we filtered
         if ($validationAction !== 'process') {
             $cancelflag = true;
             //don't retry: We've fraud-failed them intentionally.
         } elseif ($status_result->getCommunicationStatus() === false) {
             //can't communicate or internal error
             $problemflag = true;
             $problemmessage = "Can't communicate or internal error: " . $status_result->getMessage();
         }
         $order_status_results = false;
         if (!$cancelflag && !$problemflag) {
             //			$order_status_results = $this->getFinalStatus();
             $txn_data = $this->getTransactionData();
             if (isset($txn_data['STATUSID'])) {
                 if (is_null($original_status_code)) {
                     $original_status_code = $txn_data['STATUSID'];
                 }
                 $order_status_results = $this->findCodeAction('GET_ORDERSTATUS', 'STATUSID', $txn_data['STATUSID']);
             }
             if ($loops === 0 && $is_orphan && !is_null($original_status_code)) {
                 //save stats.
                 if (!isset($this->orphanstats) || !isset($this->orphanstats[$original_status_code])) {
                     $this->orphanstats[$original_status_code] = 1;
                 } else {
                     $this->orphanstats[$original_status_code] += 1;
                 }
             }
             if (!$order_status_results) {
                 $problemflag = true;
                 $problemmessage = "We don't have an order status after doing a GET_ORDERSTATUS.";
             }
             switch ($order_status_results) {
                 case FinalStatus::FAILED:
                 case FinalStatus::REVISED:
                     $cancelflag = true;
                     //makes sure we don't try to confirm.
                     break 2;
                 case FinalStatus::COMPLETE:
                     $problemflag = true;
                     //nothing to be done.
                     $problemmessage = "GET_ORDERSTATUS reports that the payment is already complete.";
                     $problemseverity = LogLevel::INFO;
                     break 2;
                 case FinalStatus::PENDING_POKE:
                     if ($is_orphan && !$gotCVV) {
                         $problemflag = true;
                         $problemmessage = "Unable to retrieve orphan cvv/avs results (Communication problem?).";
                     }
                     //none of this should ever execute for a transaction that doesn't use 3d secure...
                     if ($txn_data['STATUSID'] === '200' && $loops < $loopcount - 1) {
                         $this->logger->info("Running DO_FINISHPAYMENT ({$loops})");
                         $dopayment_result = $this->do_transaction('DO_FINISHPAYMENT');
                         $dopayment_data = $dopayment_result->getData();
                         //Check the txn status and result code to see if we should bother continuing
                         if ($this->getTransactionStatus()) {
                             $this->logger->info("DO_FINISHPAYMENT ({$loops}) returned with status ID " . $dopayment_data['STATUSID']);
                             if ($this->findCodeAction('GET_ORDERSTATUS', 'STATUSID', $dopayment_data['STATUSID']) === FinalStatus::FAILED) {
                                 //ack and die.
                                 $problemflag = true;
                                 //nothing to be done.
                                 $problemmessage = "DO_FINISHPAYMENT says the payment failed. Giving up forever.";
                                 $this->finalizeInternalStatus(FinalStatus::FAILED);
                             }
                         } else {
                             $this->logger->error("DO_FINISHPAYMENT ({$loops}) returned NOK");
                         }
                         break;
                     }
                     if ($txn_data['STATUSID'] !== '200') {
                         break 2;
                         //no need to loop.
                     }
                     // FIXME: explicit that we want to fall through?
                 // FIXME: explicit that we want to fall through?
                 case FinalStatus::PENDING:
                     // If it's really pending at this point, we need to
                     // leave it alone.
                     // FIXME: If we're orphan slaying, this should stay in
                     // the queue, but we currently delete it.
                     break 2;
             }
         }
     }
     //if we got here with no problemflag,
     //confirm or cancel the payment based on $cancelflag
     if (!$problemflag) {
         if (!$cancelflag) {
             $final = $this->do_transaction('SET_PAYMENT');
             if ($final->getCommunicationStatus() === true) {
                 $this->finalizeInternalStatus(FinalStatus::COMPLETE);
                 //get the old status from the first txn, and add in the part where we set the payment.
                 $this->transaction_response->setTxnMessage("Original Response Status (pre-SET_PAYMENT): " . $original_status_code);
                 $this->postProcessDonation();
                 // Queueing is in here.
             } else {
                 $this->finalizeInternalStatus(FinalStatus::FAILED);
                 $problemflag = true;
                 $problemmessage = "SET_PAYMENT couldn't communicate properly!";
             }
         } else {
             if ($order_status_results === false) {
                 //we didn't do the check, because we're going to fail the thing.
                 /**
                  * No need to send an explicit CANCEL_PAYMENT here, because
                  * the payment has not been set.
                  * In fact, GC will error out if we try to do that, and tell
                  * us there is nothing to cancel.
                  */
                 $this->finalizeInternalStatus(FinalStatus::FAILED);
             } else {
                 //in case we got wiped out, set the final status to what it was before.
                 $this->finalizeInternalStatus($order_status_results);
             }
         }
     }
     if ($problemflag || $cancelflag) {
         if ($cancelflag) {
             //cancel wins
             $problemmessage = "Cancelling payment";
             $problemseverity = LogLevel::INFO;
             $errors = array('1000001' => $problemmessage);
         } else {
             $errors = array('1000000' => 'Transaction could not be processed due to an internal error.');
         }
         //we have probably had a communication problem that could mean stranded payments.
         $this->logger->log($problemseverity, $problemmessage);
         //hurm. It would be swell if we had a message that told the user we had some kind of internal error.
         $ret = new PaymentTransactionResponse();
         $ret->setCommunicationStatus(false);
         //DO NOT PREPEND $problemmessage WITH ANYTHING!
         //orphans.php is looking for specific things in position 0.
         $ret->setMessage($problemmessage);
         foreach ($errors as $code => $error) {
             $ret->addError($code, array('message' => $error, 'debugInfo' => 'Failure in transactionConfirm_CreditCard', 'logLevel' => $problemseverity));
         }
         // TODO: should we set $this->transaction_response ?
         return $ret;
     }
     //		return something better... if we need to!
     return $status_result;
 }