Problem/Motivation

Paypal have finally implemented their HTTP/1.1 change first announced in 2011, meant to happen in 2014, and now seemingly actually enforced for sandbox IPN requests. One assumes that they might eventually require it on live IPN sometime too.

Because drupal_http_request is an HTTP/1.0 client, all IPN verification attempts now result in "400 Bad Request" - previously, this would only be caused by the missing "Host:" header.

There is a proposal to change this in Core very simply, #2242123: Make drupal_http_request() support HTTP 1.1, however chunking is required for an HTTP/1.1 client #106506: drupal_http_request() does not handle 'chunked' responses - Make it support HTTP 1.1. D8 has solved this by using the Guzzle.

Paypal say they have switched over already, however live requests do seem to continue to work for existing working sites. https://www.paypal-knowledge.com/infocenter/index?page=content&id=FAQ148...

Proposed resolution

  1. The IPN client/server interaction is not a fully feature functional HTTP/1.1 transaction. A simple local replacement for drupal_http_request could be written to "fake it" enough to make it. (See my horrible hack below)
  2. As proposed in #2683537: Paypal's New Warning, the cURL module can be used as both a work around, and a permanent solution by adding it as a dependancy of this module.

Work around: Use cURL module and configure via admin/config/services/chr to use chr_curl_http_request() as the default method.

NB: Due to the SSL changes that Paypal have also implemented, if your version of PHP is older, its curl version may not be able to talk the required SSL protocols. This will undoubtedly cause even more annoying support requests, at which point a "hack" of a fake HTTP/1.1 client request does seem like a much nicer alternative.

Remaining tasks

Patch the module to use one of the proposed solutions.

User interface changes

None.

API changes

Update will require installing a new module if going the chr module route.

Data model changes

None.

Original report by https://www.drupal.org/u/ibedoya

As I understood PayPal API will discontinue support for HTTP 1.0 protocol before 1 November 2014.
And an update should be applied quickly in Drupal core (https://drupal.org/node/2242123) or in this module

More info:

https://drupal.org/node/2215527
https://ppmts.custhelp.com/app/answers/detail/a_id/922

Postponed - PayPal API Changes:
https://ppmts.custhelp.com/app/answers/detail/a_id/1070

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

ELC’s picture

Title: HTTP 1.0 support will be discontinued on www.paypal.com » Paypal enforcing HTTP/1.1 on sandbox - update client method
Issue summary: View changes
Related issues: +#2683537: Paypal's New Warning

Installed a new site, and I am unable to test the workflow using the sandbox. All requests result in 400 Bad Request unless I add all of the changes shown in: https://developer.paypal.com/docs/classic/ipn/gs_IPN/

I am in the boat of being stuck with a server that cannot talk curl due to versions being out of step with Paypal, so this is my HACK.

Since this issue has been open in various fashions since 2011 and simply hasn't been addressed and probably never will, I going the insane route. Maybe when Paypal finally enforces it on live sites something might change. Heck, maybe I'll make it a real module! :P Of course, the Catch-22 is that unless enough sites switch over to using HTTP/1.1, Paypal may never actually switch live to it.

I cannot emphasise enough that this hack is a very bad idea and may break your site. Please know what you're doing using this.

insanehack_ipn.info

name = InsaneHack IPN
description = Provide hacky drupal_http_request replacement to enable Paypal IPN to work.
core = 7.x

dependencies[] = bad_judgement

insanehack_ipn.install

<?php
/**
 * @file
 * En/Disable and Uninstall function for this bad idea.
 */

/**
 * Implements hook_enable().
 */
function insanehack_ipn_enable() {
  // Store any current value and override our own.
  $current = variable_get('drupal_http_request_function', FALSE);
  if ($current != 'insanehack_ipn_request') {
    variable_set(
      'insanehack_ipn_original_http_request_function_value',
      $current
    );
  }

  // Set replacement.
  variable_set('drupal_http_request_function', 'insanehack_ipn_request');
}

/**
 * Implements hook_disable().
 */
function insanehack_ipn_disable() {
  // Return variable to previous value.
  variable_set('drupal_http_request_function', variable_get(
    'insanehack_ipn_original_http_request_function_value', FALSE
  ));
}

/**
 * Implements hook_uninstall().
 */
function insanehack_ipn_uninstall() {
  variable_del('insanehack_ipn_original_http_request_function_value');
}

insanehack_ipn.module

<?php
/**
 * @file
 * Modified drupal_http_request to allow Paypal IPN to work. This should not
 * be used.
 */

/**
 * Replacement function to Perform an HTTP request with special handling for
 * paypal IPN!
 *
 * This is a flexible and powerful HTTP client implementation. Correctly
 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
 *
 * @param $url
 *   A string containing a fully qualified URI.
 * @param array $options
 *   (optional) An array that can have one or more of the following elements:
 *   - headers: An array containing request headers to send as name/value pairs.
 *   - method: A string containing the request method. Defaults to 'GET'.
 *   - data: A string containing the request body, formatted as
 *     'param=value&param=value&...'. Defaults to NULL.
 *   - max_redirects: An integer representing how many times a redirect
 *     may be followed. Defaults to 3.
 *   - timeout: A float representing the maximum number of seconds the function
 *     call may take. The default is 30 seconds. If a timeout occurs, the error
 *     code is set to the HTTP_REQUEST_TIMEOUT constant.
 *   - context: A context resource created with stream_context_create().
 *
 * @return object
 *   An object that can have one or more of the following components:
 *   - request: A string containing the request body that was sent.
 *   - code: An integer containing the response status code, or the error code
 *     if an error occurred.
 *   - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
 *   - status_message: The status message from the response, if a response was
 *     received.
 *   - redirect_code: If redirected, an integer containing the initial response
 *     status code.
 *   - redirect_url: If redirected, a string containing the URL of the redirect
 *     target.
 *   - error: If an error occurred, the error message. Otherwise not set.
 *   - headers: An array containing the response headers as name/value pairs.
 *     HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
 *     easy access the array keys are returned in lower case.
 *   - data: A string containing the response body that was received.
 */
function insanehack_ipn_request($url, array $options = array()) {

  // Are we paypal IPN?
  $paypal = FALSE;
  $protocol = 'HTTP/1.0';
  if ($url == 'https://www.sandbox.paypal.com/cgi-bin/webscr') {
    $paypal = 'sandbox';
    $protocol = 'HTTP/1.1';
  }
  elseif ($url == 'https://www.paypal.com/cgi-bin/webscr') {
    $paypal = 'live';
    $protocol = 'HTTP/1.1';
  }

  $result = new stdClass();

  // Parse the URL and make sure we can handle the schema.
  $uri = @parse_url($url);

  if ($uri == FALSE) {
    $result->error = 'unable to parse URL';
    $result->code = -1001;
    return $result;
  }

  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
    $result->code = -1002;
    return $result;
  }

  timer_start(__FUNCTION__);

  // Merge the default options.
  $options += array(
    'headers' => array(),
    'method' => 'GET',
    'data' => NULL,
    'max_redirects' => 3,
    'timeout' => 30.0,
    'context' => NULL,
  );

  // Merge the default headers.
  $options['headers'] += array(
    'User-Agent' => 'Drupal (+http://drupal.org/)',
  );

  // Paypal requires a few extra ones.
  if ($paypal) {
    $options['headers'] += array(
      'Content-Type' => 'application/x-www-form-urlencoded',
      'Connection:' => 'close', // HTTP/1.1
    );
  }

  // stream_socket_client() requires timeout to be a float.
  $options['timeout'] = (float) $options['timeout'];

  // Use a proxy if one is defined and the host is not on the excluded list.
  $proxy_server = variable_get('proxy_server', '');
  if ($proxy_server && _drupal_http_use_proxy($uri['host'])) {
    // Set the scheme so we open a socket to the proxy server.
    $uri['scheme'] = 'proxy';
    // Set the path to be the full URL.
    $uri['path'] = $url;
    // Since the URL is passed as the path, we won't use the parsed query.
    unset($uri['query']);

    // Add in username and password to Proxy-Authorization header if needed.
    if ($proxy_username = variable_get('proxy_username', '')) {
      $proxy_password = variable_get('proxy_password', '');
      $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : ''));
    }
    // Some proxies reject requests with any User-Agent headers, while others
    // require a specific one.
    $proxy_user_agent = variable_get('proxy_user_agent', '');
    // The default value matches neither condition.
    if ($proxy_user_agent === NULL) {
      unset($options['headers']['User-Agent']);
    }
    elseif ($proxy_user_agent) {
      $options['headers']['User-Agent'] = $proxy_user_agent;
    }
  }

  switch ($uri['scheme']) {
    case 'proxy':
      // Make the socket connection to a proxy server.
      $socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
      // The Host header still needs to match the real request.
      $options['headers']['Host'] = $uri['host'];
      $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
      break;

    case 'http':
    case 'feed':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $socket = 'tcp://' . $uri['host'] . ':' . $port;
      // RFC 2616: "non-standard ports MUST, default ports MAY be included".
      // We don't add the standard port to prevent from breaking rewrite rules
      // checking the host that do not take into account the port number.
      $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
      break;

    case 'https':
      // Note: Only works when PHP is compiled with OpenSSL support.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $socket = 'ssl://' . $uri['host'] . ':' . $port;
      $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
      break;

    default:
      $result->error = 'invalid schema ' . $uri['scheme'];
      $result->code = -1003;
      return $result;
  }

  if (empty($options['context'])) {
    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
  }
  else {
    // Create a stream with context. Allows verification of a SSL certificate.
    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
  }

  // Make sure the socket opened properly.
  if (!$fp) {
    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
    $result->code = -$errno;
    $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));

    // Mark that this request failed. This will trigger a check of the web
    // server's ability to make outgoing HTTP requests the next time that
    // requirements checking is performed.
    // See system_requirements().
    variable_set('drupal_http_request_fails', TRUE);

    return $result;
  }

  // Construct the path to act on.
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
    $path .= '?' . $uri['query'];
  }

  // Only add Content-Length if we actually have any content or if it is a POST
  // or PUT request. Some non-standard servers get confused by Content-Length in
  // at least HEAD/GET requests, and Squid always requires Content-Length in
  // POST/PUT requests.
  $content_length = strlen($options['data']);
  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
    $options['headers']['Content-Length'] = $content_length;
  }

  // If the server URL has a user then attempt to use basic authentication.
  if (isset($uri['user'])) {
    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ':'));
  }

  // If the database prefix is being used by SimpleTest to run the tests in a copied
  // database then set the user-agent header to the database prefix so that any
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
  // user-agent is used to ensure that multiple testing sessions running at the
  // same time won't interfere with each other as they would if the database
  // prefix were stored statically in a file or database variable.
  $test_info = &$GLOBALS['drupal_test_info'];
  if (!empty($test_info['test_run_id'])) {
    $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
  }

  $request = $options['method'] . ' ' . $path . ' ' . $protocol . "\r\n";
  foreach ($options['headers'] as $name => $value) {
    $request .= $name . ': ' . trim($value) . "\r\n";
  }
  $request .= "\r\n" . $options['data'];
  $result->request = $request;
  // Calculate how much time is left of the original timeout value.
  $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
  if ($timeout > 0) {
    stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
    fwrite($fp, $request);
  }

  // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
  // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
  // instead must invoke stream_get_meta_data() each iteration.
  $info = stream_get_meta_data($fp);
  $alive = !$info['eof'] && !$info['timed_out'];
  $response = '';

  while ($alive) {
    // Calculate how much time is left of the original timeout value.
    $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
    if ($timeout <= 0) {
      $info['timed_out'] = TRUE;
      break;
    }
    stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
    $chunk = fread($fp, 1024);
    $response .= $chunk;
    $info = stream_get_meta_data($fp);
    $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
  }
  fclose($fp);

  if ($info['timed_out']) {
    $result->code = HTTP_REQUEST_TIMEOUT;
    $result->error = 'request timed out';
    return $result;
  }
  // Parse response headers from the response body.
  // Be tolerant of malformed HTTP responses that separate header and body with
  // \n\n or \r\r instead of \r\n\r\n.
  list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
  $response = preg_split("/\r\n|\n|\r/", $response);

  // Parse the response status line.
  $response_status_array = _drupal_parse_response_status(trim(array_shift($response)));
  $result->protocol = $response_status_array['http_version'];
  $result->status_message = $response_status_array['reason_phrase'];
  $code = $response_status_array['response_code'];

  $result->headers = array();

  // Parse the response headers.
  while ($line = trim(array_shift($response))) {
    list($name, $value) = explode(':', $line, 2);
    $name = strtolower($name);
    if (isset($result->headers[$name]) && $name == 'set-cookie') {
      // RFC 2109: the Set-Cookie response header comprises the token Set-
      // Cookie:, followed by a comma-separated list of one or more cookies.
      $result->headers[$name] .= ',' . trim($value);
    }
    else {
      $result->headers[$name] = trim($value);
    }
  }

  $responses = array(
    100 => 'Continue',
    101 => 'Switching Protocols',
    200 => 'OK',
    201 => 'Created',
    202 => 'Accepted',
    203 => 'Non-Authoritative Information',
    204 => 'No Content',
    205 => 'Reset Content',
    206 => 'Partial Content',
    300 => 'Multiple Choices',
    301 => 'Moved Permanently',
    302 => 'Found',
    303 => 'See Other',
    304 => 'Not Modified',
    305 => 'Use Proxy',
    307 => 'Temporary Redirect',
    400 => 'Bad Request',
    401 => 'Unauthorized',
    402 => 'Payment Required',
    403 => 'Forbidden',
    404 => 'Not Found',
    405 => 'Method Not Allowed',
    406 => 'Not Acceptable',
    407 => 'Proxy Authentication Required',
    408 => 'Request Time-out',
    409 => 'Conflict',
    410 => 'Gone',
    411 => 'Length Required',
    412 => 'Precondition Failed',
    413 => 'Request Entity Too Large',
    414 => 'Request-URI Too Large',
    415 => 'Unsupported Media Type',
    416 => 'Requested range not satisfiable',
    417 => 'Expectation Failed',
    500 => 'Internal Server Error',
    501 => 'Not Implemented',
    502 => 'Bad Gateway',
    503 => 'Service Unavailable',
    504 => 'Gateway Time-out',
    505 => 'HTTP Version not supported',
  );
  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  // base code in their class.
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }
  $result->code = $code;

  switch ($code) {
    case 200: // OK
    case 201: // Created
    case 202: // Accepted
    case 203: // Non-Authoritative Information
    case 204: // No Content
    case 205: // Reset Content
    case 206: // Partial Content
    case 304: // Not modified
      break;
    case 301: // Moved permanently
    case 302: // Moved temporarily
    case 307: // Moved temporarily
      $location = $result->headers['location'];
      $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
      if ($options['timeout'] <= 0) {
        $result->code = HTTP_REQUEST_TIMEOUT;
        $result->error = 'request timed out';
      }
      elseif ($options['max_redirects']) {
        // Redirect to the new location.
        $options['max_redirects']--;
        $result = drupal_http_request($location, $options);
        $result->redirect_code = $code;
      }
      if (!isset($result->redirect_url)) {
        $result->redirect_url = $location;
      }
      break;
    default:
      $result->error = $result->status_message;
  }

  return $result;
}
rreiss’s picture

Version: 7.x-2.3 » 7.x-2.x-dev
Status: Active » Needs review
FileSize
3.26 KB

Hi,

I've create a new patch for 7.x-2.x branch.
I've replaced `drupal_http_request` with cURL based implementation (as on the `commerce_paypal_api_request` function).

I've also added the setopt to TLSv1 (required..) - `curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);`

If you get "Attempt to validate IPN failed with cURL error: SSL connect error", then please make sure that your server has the latest libcurl and openssh packages.
For RedHat (e.g. Centos) use `yum install curl openssh` .

Thanks.
Rotem

torgosPizza’s picture

Issue tags: +commerce-sprint

Tagging with sprint tag so we can get some more eyeballs on it.

squall3d’s picture

Fixed my issue

ELC’s picture

This places a mandatory requirement on using curl, and having a system that is able to be fixed if it has an issue. I'm not sure that a reasonable assumption given shared hosting solutions.

This would prevent normal function of the module that would otherwise work on all installations.

How about instead of forcing this module to use curl, it instead uses a system similar to drupal_http_request. The curl code in your patch could go into a sub-module which uses this variable to configure itself, or to use the curl module:

$httpfunc = variable_get('commerce_paypal_http_request_func', 'drupal_http_request');
$httpfunc($url, $options);

This would allow anyone to use curl module, a locally included curl wrapper, or some other module. Using curl for a broken HTTP/1.1 implementation does seem like overkill since the hack in #1 does work (I sent it live). Paypal don't appear to be using a full stack 1.1 server, just some home grown solution which works with their load balancers.

rreiss’s picture

@ELC I agree.
I think that maybe we should use another method to make the request (e.g. stream socket as on drupal http rquest), not even sure that we want to use the curl snippet in my patch, but... The module is already somehow dependent on cURL (look at the existing `commerce_paypal_api_request` method).

My suggestions are:
1. Use cURL as default, and allow other modules to override the default implementation
2. Add dependency with chr module
3. Use our own drupal_http_request wrapper

I think that the 1st option is the best one (with the proper documentation)

gdanger’s picture

#2 works for me. Thanks

albany’s picture

Hi...,

I'm inexperienced with patching; so please forgive my ignorance with my questions as follows:-

I patched 7.x-2.3 with commerce_paypal-provide-extra-options-commerce-ec-2028329-13_0 patch

can I patch the 7.x-2.x-dev with the #2 patch and then patch it again with the above?

Hope this make sense.

Regards

Steve

nicksanta’s picture

Solved by using the cURL HTTP Request module - https://www.drupal.org/project/chr. Below is a step-bys-step guide.

# 1. Download cURL HTTP Request module - https://www.drupal.org/project/chr
drush -y dl chr

# 2. Enable the new module
drush -y en chr

# 3. Set the default HTTP request callback
drush -y vset drupal_http_request_function chr_curl_http_request
ndiSIGN’s picture

Hi,

It's a bit strange because I was getting the log message:

Attempt to validate IPN failed with error 400: Bad Request

So I activated the IPN settings (http://www.mysite.com) on my PayPal sandbox account but that didn't help. Then I tried the solution from #9 which solved the 400 error. And the payment info was being printed in a table on (admin/commerce/orders/11/payment).

From this similar thread: https://www.drupal.org/node/1055390 I understood you should not need to do anything with IPN settings on the PayPal side, so I turned these off again.

The funny thing is, it was still working without error messages.

But before to post my findings on the other thread I wanted to make sure I was exact, so I turned everything off and only activated the cURL HTTP Request module, but then the 400 error returned.

It turns out, in my case I need to activate both the module from #9 and set my website address in the IPN settings (dubbel checked this) in the PayPal sandbox to make it work. After that I can clear the IPN settings from PayPal and it still continues to work.

gaydamaka’s picture

Hi,

#2 not works form me. But if change CURLOPT_SSLVERSION from CURL_SSLVERSION_TLSv1 to CURL_SSLVERSION_TLSv1_0 it works fine.

http://stackoverflow.com/questions/26379773/paypal-ipn-acknowledgements-...

rreiss’s picture

Can we agree that we can add runtime check on hook_install to make some "connectivity" checks?
My idea is to make dummy request with drupal http request to the sandbox / debugging IPN and see if it's successful.
True: Use the default drupal http requst module (we assume that the sie is using chr or another drupal http request alternative).
False: Use my cURL patch from above as the default method, in order to allow the module to work without the need in another contrib. module (btw the module is already using cURL in its other methods..), and show information message to let the developers know that they may use the chr module or another alternative.

What do you guys think? Can we agree on the way to patch this annoying issue?

rreiss’s picture

Status: Needs review » Needs work
AnisAlgeria’s picture

Hi,

My website is hosting at OVH ( french company of web hosting), I am using commerce paypal. everything is ok but IPN notifiction from paypal was failed and error is ( Attempt to validate IPN failed with error 0: Error opening socket ssl://www.sandbox.paypal.com:443).
My Server is a mutuel server in OVH. although Paypal had alerted to upgrade SSL Version to( OpenSSL/1.0.0 to up )
OVh still at (OpenSSL/0.9.8o ).
I have SSL certificate and I redirect all my links to Https://.

version of PHP: 5.6.21
cURL support :enabled
SSL Version :OpenSSL/0.9.8o

I am using commerce Paypal 7.x-2.3
I trayed the patch but didn't work for me,
I ask if there is a solution to get notification from paypal sanbox even if my server sitll in OpenSSL/0.9.8o ?
thank you for your help

rreiss’s picture

The patch won't work without the latest openSSL. At least it didn't work for me.

AnisAlgeria’s picture

Thank you rreiss,
So there is no solution to get IPN notifiction from paypal with this configuration.

rreiss’s picture

No, unless your host will support SHA256 see http://stackoverflow.com/questions/32548246/paypal-changes-for-sha-256-c... for more info.

rsbecker’s picture

The patch at #11 did not work out of the box for me. But replacing lines 104 and 382 with the following fixed my site.

curl_setopt($ch, CURLOPT_SSLVERSION,6); // 6 is TLS 1.2
Chalk’s picture

The solution #9 (cURL HTTP Request module) works for me. Thanks!

AntiNSA’s picture

Curl module not working for me.

rreiss’s picture

@AntiNSA are you getting an error? Curl is enabled on your server? If so, curl & OpenSSL are up to date?

armyofda12mnkeys’s picture

I opened another ticket related to this and saw this thread later (sorry about that): https://www.drupal.org/node/2815541

#9 worked for me (on Dreamhost php 5.6 if makes a difference since seems to depend on curl being on server).
if Paypal IPN went through, should the status be 'Pending' or change to 'Completed' (or the user needs to ship and then should come back and set the Order manually to Completed in the dropdown?)?

torgosPizza’s picture

@armyofda12:The status of the order is set via Rules; the default that ships with Commerce sets them to Pending. You can easily override that configuration to set them to Completed if you wish - that is what we've done. (I have another Rule that reacts to "An order is updated" and if it contains physical goods sets the status to a custom "Pending Shipping" status.)

armyofda12mnkeys’s picture

thanks @torgosPizza for that info.

ericjenkins’s picture

#9 worked for me, as well. Thank you!

LIQUID VISUAL’s picture

re: #9 - NickSantos - could anyone tell me how to do step three without drush? Many thanks.

rreiss’s picture

I think that you can just use the chr module's configuration page, can't you?
If not, use the variables editor via the GUI.

scottatdrake’s picture

FileSize
3.85 KB

This patch is a re-roll of #11. It updates the TLS version to 1.2, which will be required by June 30, 2017.

It uses curl_setopt($ch, CURLOPT_SSLVERSION, 6); over curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); because the constant in the latter example isn't introduced until PHP7.

This also updates commerce_payflow.module because it has the same requirements for TLS 1.2 but was left out of the previous patch.

andyg5000’s picture

Status: Needs work » Needs review
ac’s picture

Patch #28 applies and works. Need to get this committed and released pretty soon if June 30 2017 is the deadline!

andyg5000’s picture

The patch in #28 works as expected. The constant "CURL_SSLVERSION_TLSv1_2" was added in curl v 7.34.0 (not PHP 7), so we should probably still use it.

We should also check for it as part of the requirements when installing and provide notes in the release about the requirement.

As a side note, I was able to connect to https://tlstest.paypal.com/ on a server that didn't have openssl compiled with tls 1.2 by commenting out the "CURLOPT_SSLVERSION" line. Not sure which version it was defaulting to. ¯\_(ツ)_/¯

mglaman’s picture

Assigned: Unassigned » mglaman

Reviewing this and linked issues.

mglaman’s picture

The patch in #28 works as expected. The constant "CURL_SSLVERSION_TLSv1_2" was added in curl v 7.34.0 (not PHP 7), so we should probably still use it.

It was added in PHP 5.5.19 and 5.6.3. So PHP 5.4 is a no go.

mglaman’s picture

+++ b/commerce_paypal.module
@@ -91,18 +91,43 @@ function commerce_paypal_process_ipn($payment_method = NULL, $debug_ipn = array(
+    curl_setopt($ch, CURLOPT_SSLVERSION, 6);

@@ -353,6 +378,7 @@ function commerce_paypal_api_request($payment_method, $nvp = array(), $order = N
+  curl_setopt($ch, CURLOPT_SSLVERSION, 6);

+++ b/modules/payflow/commerce_payflow.module
@@ -1294,6 +1294,7 @@ function commerce_payflow_api_request($payment_method, $api, $nvp = array(), $or
+  curl_setopt($ch, CURLOPT_SSLVERSION, 6);

There's no need to set the SSL version, either. The underlying library will handle those goodies. Basically you'd only set this if it was outdated and needed to use some legacy setup.

See similar discussion at #2877107: Use TLS 1.2 with cURL

mglaman’s picture

Here is rerolled #28 with items from #34 addressed.

  • mglaman committed 818af27 on 7.x-2.x authored by scottatdrake
    Issue #2263585 by rreiss, mglaman, gaydamaka, scottatdrake, andyg5000:...
mglaman’s picture

Status: Needs review » Fixed

This is now fixed. Most payment gateways for Drupal Commerce use curl. And it resolves issues about drupal_http_request.

newaytech’s picture

After updating the paypal_commerce module - I now get a 500 error and wihite screen when POSTING to https://www.sandbox.paypal.com/cgi-bin/webscr

I did have the cURL module installed - and have tried the tick box on and off to override the httpRequest method.

Anyone else seeing this?

I'll rollback the code to see if issue persists.

mglaman’s picture

Assigned: mglaman » Unassigned

Please provide a proper error message from your logs. A 500 error message can be from many things.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

istavros’s picture

#35 patch didn't work for me so I had to add one more line.
I made a patch for it.

rbogdan’s picture

Assigned: Unassigned » rbogdan
FileSize
803 bytes

I analyzed code from PayPal examples and prepare patch. My patch add 3 curl options for commerce_paypal_process_ipn().