/** * Sends a curl request to the gateway server, and gets a response. * Saves that response to the transaction_response's rawResponse; * @param string $data the raw data we want to curl up to a server somewhere. * Should have been constructed with either buildRequestNameValueString, or * buildRequestXML. * @return boolean true if the communication was successful and there is a * parseable response, false if there was a fundamental communication * problem. (timeout, bad URL, etc.) */ protected function curl_transaction($data) { $this->profiler->getStopwatch(__FUNCTION__, true); // Basic variable init $retval = false; // By default return that we failed $gatewayName = self::getGatewayName(); $email = $this->getData_Unstaged_Escaped('email'); /** * This log line is pretty important. Usually when a donor contacts us * saying that they have experienced problems donating, the first thing * we have to do is associate a gateway transaction ID and ctid with an * email address. If the cURL function fails, we lose the ability to do * that association outside of this log line. */ $this->logger->info("Initiating cURL for donor {$email}"); // Initialize cURL and construct operation (also run filter) $ch = curl_init(); $filterResult = $this->runSessionVelocityFilter(); if ($filterResult == false) { return false; } // assign header data necessary for the curl_setopt() function $headers = $this->getCurlBaseHeaders(); $headers[] = 'Content-Length: ' . strlen($data); $curl_opts = $this->getCurlBaseOpts(); $curl_opts[CURLOPT_HTTPHEADER] = $headers; $curl_opts[CURLOPT_POSTFIELDS] = $data; // Always capture the cURL output $curlDebugLog = fopen('php://temp', 'r+'); $curl_opts[CURLOPT_STDERR] = $curlDebugLog; $enableCurlVerboseLogging = $this->getGlobal('CurlVerboseLog'); curl_setopt_array($ch, $curl_opts); // As suggested in the PayPal developer forum sample code, try more than once to get a // response in case there is a general network issue $continue = true; $tries = 0; $curl_response = false; $loopCount = $this->getGlobal('RetryLoopCount'); do { $this->logger->info("Preparing to send {$this->getCurrentTransaction()} transaction to {$gatewayName}"); // Execute the cURL operation $curl_response = $this->curl_exec($ch); // Always read the verbose output rewind($curlDebugLog); $logged = fread($curlDebugLog, 4096); if ($curl_response !== false) { // The cURL operation was at least successful, what happened in it? // Only log verbose output on success if configured to do so if ($enableCurlVerboseLogging) { $this->logger->info("cURL verbose logging: {$logged}"); } $headers = $this->curl_getinfo($ch); $httpCode = $headers['http_code']; switch ($httpCode) { case 200: // Everything is AWESOME $continue = false; $this->logger->debug("Successful transaction to {$gatewayName}"); $this->transaction_response->setRawResponse($curl_response); $retval = true; break; case 400: // Oh noes! Bad request.. BAD CODE, BAD BAD CODE! $continue = false; $this->logger->error("{$gatewayName} returned (400) BAD REQUEST: {$curl_response}"); // Even though there was an error, set the results. Amazon at least gives // us useful XML return $this->transaction_response->setRawResponse($curl_response); $retval = true; break; case 403: // Hmm, forbidden? Maybe if we ask it nicely again... $continue = true; $this->logger->alert("{$gatewayName} returned (403) FORBIDDEN: {$curl_response}"); break; default: // No clue what happened... break out and log it $continue = false; $this->logger->error("{$gatewayName} failed remotely and returned ({$httpCode}): {$curl_response}"); break; } } else { // Well the cURL transaction failed for some reason or another. Try again! $continue = true; $errno = $this->curl_errno($ch); $err = curl_error($ch); $this->logger->alert("cURL transaction to {$gatewayName} failed: ({$errno}) {$err}. " . "cURL verbose logging: {$logged}"); } $tries++; if ($tries >= $loopCount) { $continue = false; } if ($continue) { // If we're going to try again, log timing for this particular curl attempt and reset $this->profiler->saveCommunicationStats(__FUNCTION__, $this->getCurrentTransaction(), "cURL problems"); $this->profiler->getStopwatch(__FUNCTION__, true); rewind($curlDebugLog); } } while ($continue); // End while cURL transaction hasn't returned something useful // Clean up and return curl_close($ch); fclose($curlDebugLog); $log_results = array('result' => $curl_response, 'headers' => $headers); $this->profiler->saveCommunicationStats(__FUNCTION__, $this->getCurrentTransaction(), "Response: " . print_r($log_results, true)); return $retval; }