You are here

function commerce_paypal_ec_paypal_ipn_process in Commerce PayPal 7.2

Payment method callback: process an IPN once it's been validated.

File

modules/ec/commerce_paypal_ec.module, line 877
Implements PayPal Express Checkout in Drupal Commerce checkout.

Code

function commerce_paypal_ec_paypal_ipn_process($order, $payment_method, &$ipn) {

  // Do not perform any processing on EC transactions here that do not have
  // transaction IDs, indicating they are non-payment IPNs such as those used
  // for subscription signup requests.
  if (empty($ipn['txn_id'])) {
    return FALSE;
  }

  // Exit when we don't get a payment status we recognize.
  if (!in_array($ipn['payment_status'], array(
    'Failed',
    'Voided',
    'Pending',
    'Completed',
    'Refunded',
    'Denied',
  ))) {
    commerce_payment_redirect_pane_previous_page($order);
    return FALSE;
  }

  // If this is a prior authorization capture IPN...
  if (in_array($ipn['payment_status'], array(
    'Voided',
    'Completed',
  )) && !empty($ipn['auth_id'])) {

    // Ensure we can load the existing corresponding transaction.
    $transaction = commerce_paypal_payment_transaction_load($ipn['auth_id']);

    // If not, bail now because authorization transactions should be created by
    // the Express Checkout API request itself.
    if (!$transaction) {
      watchdog('commerce_paypal_ec', 'IPN for Order @order_number ignored: authorization transaction already created.', array(
        '@order_number' => $order->order_number,
      ), WATCHDOG_NOTICE);
      return FALSE;
    }
  }
  elseif (in_array($ipn['payment_status'], array(
    'Failed',
    'Refunded',
  ))) {

    // Ensure there isn't already an existing corresponding transaction.
    $transaction = commerce_paypal_payment_transaction_load($ipn['txn_id']);

    // If so, bail now because the refund transaction was created by the Express
    // Checkout API request itself.
    if ($transaction) {
      watchdog('commerce_paypal_ec', 'IPN for Order @order_number ignored: refund transaction already created.', array(
        '@order_number' => $order->order_number,
      ), WATCHDOG_NOTICE);
      return FALSE;
    }

    // Otherwise if this is a failed bank payment or refund, create a new
    // payment transaction to log it to the order.
    $transaction = commerce_payment_transaction_new('paypal_ec', $order->order_id);
    $transaction->instance_id = $payment_method['instance_id'];
  }
  elseif (in_array($ipn['payment_status'], array(
    'Completed',
    'Denied',
  )) && $ipn['payment_type'] === 'echeck') {

    // E-checks use the same ipn transaction id, with an updated status. Check
    // for existing transaction with a status that's not the same.
    // Ensure there is an existing transaction.
    $transaction = commerce_paypal_payment_transaction_load($ipn['txn_id']);
    if (!$transaction) {
      watchdog('commerce_paypal_ec', 'IPN for Order @order_number ignored: transaction not found.', array(
        '@order_number' => $order->order_number,
      ), WATCHDOG_NOTICE);
      return FALSE;
    }

    // The e-check status has updated, so change the transaction details.
    if ($ipn['payment_status'] == 'Completed') {
      $transaction->message = '<strong>' . t('eCheck payment successful') . '</strong>';
    }
    else {
      $transaction->message = '<strong>' . t('eCheck payment denied') . '</strong>';
    }
  }
  else {

    // In other circumstances, exit the processing, because we handle those
    // cases directly during API response processing.
    watchdog('commerce_paypal_ec', 'IPN for Order @order_number ignored: this operation was accommodated in the direct API response.', array(
      '@order_number' => $order->order_number,
    ), WATCHDOG_NOTICE);
    return FALSE;
  }
  $transaction->remote_id = $ipn['txn_id'];
  $transaction->amount = commerce_currency_decimal_to_amount($ipn['mc_gross'], $ipn['mc_currency']);
  $transaction->currency_code = $ipn['mc_currency'];
  $transaction->payload[REQUEST_TIME . '-ipn'] = $ipn;
  if (!empty($transaction->message)) {
    $transaction->message .= '<br />';
  }

  // Set the transaction's statuses based on the IPN's payment_status.
  $transaction->remote_status = $ipn['payment_status'];

  // If we didn't get an approval response code...
  switch ($ipn['payment_status']) {
    case 'Failed':
      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
      $transaction->message .= t("The payment has failed. This happens only if the payment was made from your customer’s bank account.");
      break;
    case 'Voided':
      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
      $transaction->message .= t('The authorization was voided.');
      break;
    case 'Pending':
      $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
      $transaction->message .= commerce_paypal_ipn_pending_reason($ipn['pending_reason']);
      break;
    case 'Completed':
      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
      $transaction->message .= t('The payment has completed.');
      break;
    case 'Refunded':
      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
      $transaction->message .= t('Refund for transaction @txn_id', array(
        '@txn_id' => $ipn['parent_txn_id'],
      ));
      break;
    case 'Denied':
      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
      $transaction->message .= t("The payment has been denied. This happens only if the payment was previously pending.");
      break;
  }

  // Save the transaction information.
  commerce_payment_transaction_save($transaction);
  $ipn['transaction_id'] = $transaction->transaction_id;
  watchdog('commerce_paypal_ec', 'IPN processed for Order @order_number with ID @txn_id.', array(
    '@txn_id' => $ipn['txn_id'],
    '@order_number' => $order->order_number,
  ), WATCHDOG_INFO);
}