You are here

function commerce_paypal_process_ipn in Commerce PayPal 7.2

Same name and namespace in other branches
  1. 7 commerce_paypal.module \commerce_paypal_process_ipn()

Processes an incoming IPN.

Parameters

$payment_method: The payment method instance array that originally made the payment.

$debug_ipn: Optionally specify an IPN array for debug purposes; if left empty, the IPN be pulled from the $_POST. If an IPN is passed in, validation of the IPN at PayPal will be bypassed.

Return value

TRUE or FALSE indicating whether the IPN was successfully processed or not.

1 string reference to 'commerce_paypal_process_ipn'
commerce_paypal_menu in ./commerce_paypal.module
Implements hook_menu().

File

./commerce_paypal.module, line 66
Implements PayPal payment services for use with Drupal Commerce.

Code

function commerce_paypal_process_ipn($payment_method = NULL, $debug_ipn = array()) {

  // Retrieve the IPN from $_POST if the caller did not supply an IPN array.
  // Note that Drupal has already run stripslashes() on the contents of the
  // $_POST array at this point, so we don't need to worry about them.
  if (empty($debug_ipn)) {
    $ipn = $_POST;

    // Exit now if the $_POST was empty.
    if (empty($ipn)) {
      watchdog('commerce_paypal', 'IPN URL accessed with no POST data submitted.', array(), WATCHDOG_WARNING);
      return FALSE;
    }

    // Prepare an array to POST back to PayPal to validate the IPN.
    $variables = array(
      'cmd=_notify-validate',
    );
    foreach ($ipn as $key => $value) {
      $variables[] = $key . '=' . urlencode($value);
    }

    // Determine the proper PayPal server to POST to.
    if (!empty($ipn['test_ipn']) && $ipn['test_ipn'] == 1) {
      $host = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
    }
    else {
      $host = 'https://www.paypal.com/cgi-bin/webscr';
    }

    // Setup the cURL request.
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $host);
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $variables));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);

    // Commerce PayPal requires SSL peer verification, which may prevent out of
    // date servers from successfully processing API requests. If you get an error
    // related to peer verification, you may need to download the CA certificate
    // bundle file from http://curl.haxx.se/docs/caextract.html, place it in a
    // safe location on your web server, and update your settings.php to set the
    // commerce_paypal_cacert variable to contain the absolute path of the file.
    // Alternately, you may be able to update your php.ini to point to the file
    // with the curl.cainfo setting.
    if (variable_get('commerce_paypal_cacert', FALSE)) {
      curl_setopt($ch, CURLOPT_CAINFO, variable_get('commerce_paypal_cacert', ''));
    }
    $response = curl_exec($ch);

    // If an error occurred during processing, log the message and exit.
    if ($error = curl_error($ch)) {
      watchdog('commerce_paypal', 'Attempt to validate IPN failed with cURL error: @error', array(
        '@error' => $error,
      ), WATCHDOG_ERROR);
      return FALSE;
    }
    curl_close($ch);

    // inspect IPN validation result and act accordingly
    if (strcmp($response, "INVALID") == 0) {

      // If the IPN was invalid, log a message and exit.
      watchdog('commerce_paypal', 'Invalid IPN received and ignored. Response: @response', array(
        '@response' => $response,
      ), WATCHDOG_ALERT);
      return FALSE;
    }
  }
  else {
    $ipn = $debug_ipn;
  }

  // If the payment method specifies full IPN logging, do it now.
  if (!empty($payment_method['settings']['ipn_logging']) && $payment_method['settings']['ipn_logging'] == 'full_ipn') {
    if (!empty($ipn['txn_id'])) {
      watchdog('commerce_paypal', 'Attempting to process IPN @txn_id. !ipn_log', array(
        '@txn_id' => $ipn['txn_id'],
        '!ipn_log' => '<pre>' . check_plain(print_r($ipn, TRUE)) . '</pre>',
      ), WATCHDOG_NOTICE);
    }
    else {
      watchdog('commerce_paypal', 'Attempting to process an IPN. !ipn_log', array(
        '!ipn_log' => '<pre>' . check_plain(print_r($ipn, TRUE)) . '</pre>',
      ), WATCHDOG_NOTICE);
    }
  }

  // Exit if the IPN has already been processed.
  if (!empty($ipn['txn_id']) && ($prior_ipn = commerce_paypal_ipn_load($ipn['txn_id']))) {
    if ($prior_ipn['payment_status'] == $ipn['payment_status']) {
      watchdog('commerce_paypal', 'Attempted to process an IPN that has already been processed with transaction ID @txn_id.', array(
        '@txn_id' => $ipn['txn_id'],
      ), WATCHDOG_NOTICE);
      return FALSE;
    }
  }

  // Load the order based on the IPN's invoice number.
  if (!empty($ipn['invoice']) && strpos($ipn['invoice'], '-') !== FALSE) {
    list($ipn['order_id'], $timestamp) = explode('-', $ipn['invoice']);
  }
  elseif (!empty($ipn['invoice'])) {
    $ipn['order_id'] = $ipn['invoice'];
  }
  else {
    $ipn['order_id'] = 0;
    $timestamp = 0;
  }
  if (!empty($ipn['order_id'])) {
    $order = commerce_order_load($ipn['order_id']);
  }
  else {
    $order = FALSE;
  }

  // Give the payment method module an opportunity to validate the receiver
  // e-mail address and amount of the payment if possible. If a validate
  // function exists, it is responsible for setting its own watchdog message.
  if (!empty($payment_method)) {
    $callback = $payment_method['base'] . '_paypal_ipn_validate';

    // If a validator function existed...
    if (function_exists($callback)) {

      // Only exit if the function explicitly returns FALSE.
      if ($callback($order, $payment_method, $ipn) === FALSE) {
        return FALSE;
      }
    }
  }

  // Give the payment method module an opportunity to process the IPN.
  if (!empty($payment_method)) {
    $callback = $payment_method['base'] . '_paypal_ipn_process';

    // If a processing function existed...
    if (function_exists($callback)) {

      // Skip saving if the function explicitly returns FALSE, meaning the IPN
      // wasn't actually processed.
      if ($callback($order, $payment_method, $ipn) !== FALSE) {

        // Save the processed IPN details.
        commerce_paypal_ipn_save($ipn);
      }
    }
  }

  // Invoke the hook here so implementations have access to the order and
  // payment method if available and a saved IPN array that includes the payment
  // transaction ID if created in the payment method's default process callback.
  module_invoke_all('commerce_paypal_ipn_process', $order, $payment_method, $ipn);
}