You are here

commerce_payflow.module in Commerce PayPal 7.2

Implements PayPal Payments Advanced (U.S. only) and Payflow Link Hosted Checkout pages and Transparent Redirect.

File

modules/payflow/commerce_payflow.module
View source
<?php

/**
 * @file
 * Implements PayPal Payments Advanced (U.S. only) and Payflow Link Hosted
 * Checkout pages and Transparent Redirect.
 */

/**
 * Implements hook_menu().
 */
function commerce_payflow_menu() {
  $items = array();

  // Add a menu item for capturing authorizations.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/payflow-link-capture'] = array(
    'title' => 'Capture',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_payflow_link_capture_form',
      3,
      5,
    ),
    'access callback' => 'commerce_payflow_link_capture_void_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
    'file' => 'includes/commerce_payflow.admin.inc',
  );

  // Add a menu item for processing reference transactions.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/payflow-link-reference'] = array(
    'title' => 'Reference',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_payflow_link_reference_form',
      3,
      5,
    ),
    'access callback' => 'commerce_payflow_link_reference_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
    'file' => 'includes/commerce_payflow.admin.inc',
  );

  // Add a menu item for voiding authorizations.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/payflow-link-void'] = array(
    'title' => 'Void',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_payflow_link_void_form',
      3,
      5,
    ),
    'access callback' => 'commerce_payflow_link_capture_void_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 4,
    'file' => 'includes/commerce_payflow.admin.inc',
  );

  // Add a menu item for refunding settled transactions.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/payflow-link-refund'] = array(
    'title' => 'Refund',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_payflow_link_refund_form',
      3,
      5,
    ),
    'access callback' => 'commerce_payflow_link_refund_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 4,
    'file' => 'includes/commerce_payflow.admin.inc',
  );
  return $items;
}

/**
 * Determines access to the prior authorization capture form or void form for
 * Payflow Link credit card transactions.
 *
 * @param $order
 *   The order the transaction is on.
 * @param $transaction
 *   The payment transaction object to be captured.
 *
 * @return
 *   TRUE or FALSE indicating access.
 */
function commerce_payflow_link_capture_void_access($order, $transaction) {

  // Return FALSE if the transaction isn't for Payflow Link or isn't awaiting capture.
  $valid_types = array(
    'A',
    'Pending',
  );
  if (!in_array($transaction->payment_method, array(
    'payflow_link',
    'paypal_ppa',
  )) || !in_array($transaction->remote_status, $valid_types)) {
    return FALSE;
  }

  // Return FALSE if the transaction is not pending.
  if ($transaction->status != COMMERCE_PAYMENT_STATUS_PENDING) {
    return FALSE;
  }

  // Return FALSE if the transaction was created through Express Checkout.
  if (!empty($transaction->data['commerce_payflow']['tender']) && $transaction->data['commerce_payflow']['tender'] == 'P') {
    return FALSE;
  }

  // Return FALSE if it is more than 29 days past the original authorization.
  if (REQUEST_TIME - $transaction->created > 86400 * 29) {
    return FALSE;
  }

  // Allow access if the user can update payments on this transaction.
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Determines access to the reference transaction form for Payflow Link credit
 * card transactions.
 *
 * @param $order
 *   The order the transaction is on.
 * @param $transaction
 *   The payment transaction object to be captured.
 *
 * @return
 *   TRUE or FALSE indicating access.
 */
function commerce_payflow_link_reference_access($order, $transaction) {

  // Return FALSE if the transaction isn't valid for reference transactions:
  // Sale, Authorization, Delayed Capture, Void, or Credit. This list includes
  // both the Payflow Link codes and Express Checkout statuses.
  $valid_types = array(
    'S',
    'A',
    'D',
    'V',
    'C',
    'Pending',
    'Completed',
    'Voided',
    'Refunded',
  );
  if (!in_array($transaction->payment_method, array(
    'payflow_link',
    'paypal_ppa',
    'paypal_ec',
  )) || !in_array($transaction->remote_status, $valid_types)) {
    return FALSE;
  }

  // Return FALSE if the transaction was a failure.
  if ($transaction->status == COMMERCE_PAYMENT_STATUS_FAILURE) {
    return FALSE;
  }

  // Return FALSE if the transaction is an Express Checkout transaction that
  // does not have Payflow data.
  if ($transaction->payment_method == 'paypal_ec' && empty($transaction->data['commerce_payflow'])) {
    return FALSE;
  }

  // Return FALSE if the transaction came through PayPal and does not have a
  // billing agreement ID.
  if (!empty($transaction->data['commerce_payflow']['tender']) && $transaction->data['commerce_payflow']['tender'] == 'P' && empty($transaction->data['commerce_payflow']['baid'])) {
    return FALSE;
  }

  // Return FALSE if it is more than 365 days since the original transaction.
  if (REQUEST_TIME - $transaction->created > 86400 * 365) {
    return FALSE;
  }

  // Return FALSE if the payment method instance does not have reference
  // transaction support enabled.
  $payment_method = commerce_payment_method_instance_load($transaction->instance_id);
  if (empty($payment_method['settings']['reference_transactions'])) {
    return FALSE;
  }

  // Allow access if the user can update payments on this transaction.
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Determines access to the refund form for Payflow Link credit card transactions.
 *
 * @param $order
 *   The order the transaction is on.
 * @param $transaction
 *   The payment transaction object to be captured.
 *
 * @return
 *   TRUE or FALSE indicating access.
 */
function commerce_payflow_link_refund_access($order, $transaction) {

  // Return FALSE if the transaction isn't valid for credit transactions:
  // Sale or Delayed Capture.
  $valid_types = array(
    'S',
    'D',
  );
  if (!in_array($transaction->payment_method, array(
    'payflow_link',
    'paypal_ppa',
  )) || !in_array($transaction->remote_status, $valid_types)) {
    return FALSE;
  }

  // Return FALSE if the transaction was created through Express Checkout.
  if (!empty($transaction->data['commerce_payflow']['tender']) && $transaction->data['commerce_payflow']['tender'] == 'P') {
    return FALSE;
  }

  // Return FALSE if the transaction was not a success.
  if ($transaction->status != COMMERCE_PAYMENT_STATUS_SUCCESS) {
    return FALSE;
  }

  // Return FALSE if it is more than 60 days since the original transaction.
  if (REQUEST_TIME - $transaction->created > 86400 * 60) {
    return FALSE;
  }

  // Allow access if the user can update payments on this transaction.
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_payflow_commerce_payment_method_info() {
  $payment_methods = array();
  $payment_methods['payflow_link'] = array(
    'base' => 'commerce_payflow_link',
    'buttonsource' => 'CommerceGuys_Cart_PFL',
    'title' => t('Payflow Link'),
    'short_title' => t('Payflow Link'),
    'description' => t('Payflow Link Hosted Checkout Pages'),
    'terminal' => FALSE,
    'offsite' => TRUE,
    'offsite_autoredirect' => FALSE,
  );

  // Note that the PayPal Payments Advanced payment method uses the same 'base'
  // value as Payflow Link. These are the same service offered by PayPal, they
  // just offer US merchants the ability to use Payflow Link with PayPal as the
  // payment processor and call it PayPal Payments Advanced. We reuse the same
  // code for these two methods .with the exception of a thin wrapper around the
  // settings form. This means data from PPA transactions may be stored in
  // variables as payflow_link data, but it's all one and the same.
  $payment_methods['paypal_ppa'] = array(
    'base' => 'commerce_payflow_link',
    'buttonsource' => 'CommerceGuys_Cart_PPA',
    'title' => t('PayPal Payments Advanced'),
    'short_title' => t('PayPal Payments Advanced'),
    'description' => t('PayPal Payments Advanced Hosted Checkout Pages'),
    'terminal' => FALSE,
    'offsite' => TRUE,
    'offsite_autoredirect' => FALSE,
    'callbacks' => array(
      'settings_form' => 'commerce_paypal_ppa_settings_form',
    ),
  );
  return $payment_methods;
}

/**
 * Implements hook_page_alter().
 */
function commerce_payflow_page_alter(&$page) {

  // Add a registration link to the PayPal Payments Advanced and Payflow Link
  // payment method rules on the payment methods admin page.
  if (!empty($page['content']['system_main']['#page_callback']) && $page['content']['system_main']['#page_callback'] == 'commerce_payment_ui_admin_page') {

    // Ensure we loop over both enabled and disabled rules.
    foreach (array(
      'enabled',
      'disabled',
    ) as $key) {
      foreach ($page['content']['system_main'][$key]['rules']['#rows'] as $row_key => &$row) {
        $services = array(
          'commerce_payment_payflow_link' => 'payflow_link',
          'commerce_payment_paypal_ppa' => 'paypal_ppa',
        );
        foreach ($services as $rule_name => $method_id) {
          if (strpos($row[0]['data']['description']['settings']['machine_name']['#markup'], $rule_name) > 0) {
            $row[0]['data']['#suffix'] = '<div class="service-description">' . commerce_payflow_service_description($method_id) . '</div></div>';
          }
        }
      }
    }
  }
}

/**
 * Returns a service description and registration link for the specified method.
 */
function commerce_payflow_service_description($method_id) {
  switch ($method_id) {
    case 'payflow_link':
      return t('Accept payment securely on your site via credit card, debit card, or PayPal using a merchant account of your choice. This payment method requires a PayPal Payflow account. <a href="!url">Sign up here</a> and edit this rule to start accepting payments.', array(
        '!url' => 'https://www.paypal.com/webapps/mpp/referral/paypal-payflow-link?partner_id=VZ6B9QLQ8LZEE',
      ));
    case 'paypal_ppa':
      return t('Accept payment securely on your site via credit card, debit card, or PayPal using PayPal as your payment processor. This payment method requires a PayPal Payflow account. <a href="!url">Sign up here</a> and edit this rule to start accepting payments.', array(
        '!url' => 'https://www.paypal.com/webapps/mpp/referral/paypal-payments-advanced?partner_id=VZ6B9QLQ8LZEE',
      ));
  }
}

/**
 * Returns the default settings for the PayPal WPS payment method.
 */
function commerce_payflow_link_default_settings() {
  $default_currency = commerce_default_currency();
  return array(
    'partner' => 'PayPal',
    'vendor' => '',
    'user' => '',
    'password' => '',
    'mode' => 'test',
    'trxtype' => 'S',
    'currency_code' => in_array($default_currency, array_keys(commerce_paypal_currencies('payflow_link'))) ? $default_currency : 'USD',
    'allow_supported_currencies' => FALSE,
    'payment_icons' => drupal_map_assoc(array_keys(array_diff_key(commerce_paypal_payment_methods(), array(
      'echeck' => '',
    )))),
    'redirect_mode' => 'iframe',
    'cancel_link' => TRUE,
    'paypal_ec_instance' => '',
    'paypal_ec_disable' => TRUE,
    'reference_transactions' => FALSE,
    'ba_desc' => '',
    'show_payment_instructions' => FALSE,
    'emailcustomer' => FALSE,
    'log' => array(
      'request' => 0,
      'response' => 0,
    ),
    'silent_post_logging' => 'notification',
  );
}

/**
 * Payment method callback: settings form.
 */
function commerce_payflow_link_settings_form($settings = array(), $payment_method = NULL) {
  $form = array();

  // Default to the Payflow Link payment method if none is passed in.
  if (empty($payment_method)) {
    $payment_method = commerce_payment_method_load('payflow_link');
  }

  // Merge default settings into the stored settings array.
  $settings = (array) $settings + commerce_payflow_link_default_settings();
  $form['service_description'] = array(
    '#markup' => '<div>' . commerce_payflow_service_description($payment_method['method_id']) . ' ' . t('Your PayPal Manager login information should be used to configure these first four textfields.') . '<br /><br />' . t('<strong>Important:</strong> Refer to the <a href="!url" target="_blank">module documentation</a> to ensure your PayPal Manager service settings are configured properly.', array(
      '!url' => 'http://drupal.org/node/1902734',
    )) . '</div>',
  );

  // Add the Payflow account form fields.
  $form['partner'] = array(
    '#type' => 'textfield',
    '#title' => t('Partner'),
    '#description' => t('Either PayPal or the name of the reseller who registered your @title account.', array(
      '@title' => $payment_method['title'],
    )),
    '#default_value' => $settings['partner'],
    '#required' => TRUE,
  );
  $form['vendor'] = array(
    '#type' => 'textfield',
    '#title' => t('Merchant login'),
    '#description' => t('The merchant login ID you chose when you created your @title account.', array(
      '@title' => $payment_method['title'],
    )),
    '#default_value' => $settings['vendor'],
    '#required' => TRUE,
  );
  $form['user'] = array(
    '#type' => 'textfield',
    '#title' => t('User'),
    '#description' => t('The name of the user on the account you want to use to process transactions or the merchant login if you have not created users.'),
    '#default_value' => $settings['user'],
    '#required' => TRUE,
  );
  $form['password'] = array(
    '#type' => 'password',
    '#title' => t('Password'),
    '#description' => t('The password created for the user specified in the previous textfield.'),
    '#default_value' => $settings['password'],
    '#required' => TRUE,
  );
  $form['mode'] = array(
    '#type' => 'radios',
    '#title' => t('Processing mode'),
    '#description' => t('Either mode requires a @title account with Hosted Checkout Pages. "Enable Secure Token" must be set to "Yes" in the PayPal Manager service settings.', array(
      '@title' => $payment_method['title'],
    )),
    '#options' => array(
      'test' => 'Test - process test transactions to an account in test mode',
      'live' => 'Live - process real transactions to a live account',
    ),
    '#default_value' => $settings['mode'],
  );

  // Add the checkout and transaction option form elements.
  $form['trxtype'] = array(
    '#type' => 'radios',
    '#title' => t('Default transaction type'),
    '#options' => array(
      'S' => t('Sale - authorize and capture the funds at the time the payment is processed'),
      'A' => t('Authorization - reserve funds on the card to be captured later through your PayPal account'),
    ),
    '#default_value' => $settings['trxtype'],
  );
  $form['currency_code'] = array(
    '#type' => 'select',
    '#title' => t('Default currency'),
    '#description' => t('Transactions in other currencies will be converted to this currency, so multi-currency sites must be configured to use appropriate conversion rates.') . '<br />' . t('Note: valid currencies will differ depending on your country and processor. Check before selecting a different currency.'),
    '#options' => commerce_paypal_currencies($payment_method['method_id']),
    '#default_value' => $settings['currency_code'],
  );
  $form['allow_supported_currencies'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow Express Checkout transactions to use any currency in the options list above.'),
    '#description' => t('Transactions in unsupported currencies will still be converted into the default currency.') . '<br />' . t('In most cases, a single currency balance is the only option, so this should be tested thoroughly before being enabled on a live site.'),
    '#default_value' => $settings['allow_supported_currencies'],
  );
  $form['payment_icons'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Payment option icons to show on the checkout form'),
    '#description' => t('Your payment processor and @title account settings may limit which of these payment options are actually available on the payment form.', array(
      '@title' => $payment_method['title'],
    )),
    '#options' => array_diff_key(commerce_paypal_payment_methods(), array(
      'echeck' => '',
    )),
    '#default_value' => $settings['payment_icons'],
  );
  $form['redirect_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Checkout redirect mode'),
    '#options' => array(
      'iframe' => t('Stay on this site using an iframe to embed the hosted checkout page'),
      'post' => t('Redirect to the hosted checkout page via POST through an automatically submitted form'),
      'get' => t('Redirect to the hosted checkout page immediately with a GET request'),
    ),
    '#default_value' => $settings['redirect_mode'],
  );
  $form['cancel_link'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display a cancel link beneath the iframe of an embedded hosted checkout page.'),
    '#default_value' => $settings['cancel_link'],
  );

  // Prepare a list of PayPal Express Checkout payment method rule options.
  if (module_exists('commerce_paypal_ec')) {
    $options = commerce_paypal_ec_enabled_rules();
  }
  else {
    $options = array();
  }
  $form['paypal_ec_instance'] = array(
    '#type' => 'select',
    '#title' => t('PayPal Express Checkout payment method rule'),
    '#description' => t('Specify the payment method rule that contains your corresponding PayPal Express Checkout configuration for this @title instance.', array(
      '@title' => $payment_method['title'],
    )) . '<br />' . t('If none is selected, you must disable Express Checkout in your Hosted Checkout Pages service settings in the PayPal Manager.'),
    '#options' => $options,
    '#default_value' => $settings['paypal_ec_instance'],
    '#empty_value' => '',
  );
  $form['paypal_ec_disable'] = array(
    '#type' => 'checkbox',
    '#title' => t('Disable the selected PayPal Express Checkout payment method rule on the checkout form so customers just see the option on the hosted checkout page.'),
    '#default_value' => $settings['paypal_ec_disable'],
  );
  $form['reference_transactions'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable reference transactions for payments captured through this @title account.', array(
      '@title' => $payment_method['title'],
    )),
    '#description' => t('Contact PayPal if you are unsure if this option is available to you.'),
    '#default_value' => $settings['reference_transactions'],
  );
  $form['ba_desc'] = array(
    '#type' => 'textfield',
    '#title' => t('Express Checkout billing agreement description'),
    '#description' => t('If you have a PayPal account that supports reference transactions and need them for Express Checkout payments captured through @title, you must specify a billing agreement description.', array(
      '@title' => $payment_method['title'],
    )),
    '#default_value' => $settings['ba_desc'],
  );
  $form['show_payment_instructions'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show a message on the checkout form when @title is selected telling the customer to "Continue with checkout to complete payment via PayPal."', array(
      '@title' => $payment_method['title'],
    )),
    '#default_value' => $settings['show_payment_instructions'],
  );
  $form['emailcustomer'] = array(
    '#type' => 'checkbox',
    '#title' => t('Instruct PayPal to e-mail payment receipts to your customers upon payment.'),
    '#default_value' => $settings['emailcustomer'],
  );

  // Add the logging configuration form elements.
  $form['log'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Log the following messages for debugging'),
    '#options' => array(
      'request' => t('API request messages'),
      'response' => t('API response messages'),
    ),
    '#default_value' => $settings['log'],
  );

  /**
  * @todo Uncomment this setting when Silent POST is integrated.
  *
    $form['silent_post_logging'] = array(
   '#type' => 'radios',
   '#title' => t('Silent POST logging'),
   '#options' => array(
     'notification' => t('Log notifications during validation and processing.'),
     'full_post' => t('Log notifications with the full API request / response during validation and processing (used for debugging).'),
   ),
   '#default_value' => $settings['silent_post_logging'],
    );
  */
  return $form;
}

/**
 * Payment method callback: settings form.
 */
function commerce_paypal_ppa_settings_form($settings = array()) {

  // Grab the default Payflow Link settings.
  $settings += commerce_payflow_link_default_settings();

  // Ensure the default currency code is PPA compatible.
  if (!in_array($settings['currency_code'], array_keys(commerce_paypal_currencies('paypal_ppa')))) {
    $settings['currency_code'] = 'USD';
  }
  $form = commerce_payflow_link_settings_form($settings, commerce_payment_method_load('paypal_ppa'));
  return $form;
}

/**
 * Payment method callback: adds a message to the submission form if enabled in
 * the payment method settings.
 */
function commerce_payflow_link_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
  $form = array();
  if (!empty($payment_method['settings']['show_payment_instructions'])) {
    $class = $payment_method['method_id'] == 'payflow_link' ? 'commerce-payflow-link-info' : 'commerce-paypal-pa-info';
    $form[$payment_method['method_id'] . '_information'] = array(
      '#markup' => '<span class="' . $class . '">' . t('(Continue with checkout to complete payment via PayPal.)') . '</span>',
    );
  }
  return $form;
}

/**
 * Payment method callback: submit form validation.
 */
function commerce_payflow_link_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) {

  // Return an error if the enabling action's settings haven't been configured.
  foreach (array(
    'partner',
    'vendor',
    'user',
    'password',
  ) as $key) {
    if (empty($payment_method['settings'][$key])) {
      drupal_set_message(t('@title is not configured for use. Please contact an administrator to resolve this issue.', array(
        '@title' => $payment_method['title'],
      )), 'error');
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Payment method callback: submit form submission.
 */
function commerce_payflow_link_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {

  // Update the order to reference the Payflow Link payment method.
  $order->data['payment_method'] = $payment_method['instance_id'];

  // Generate a payment redirect key and Secure Token ID.
  $order->data['payment_redirect_key'] = drupal_hash_base64(time());
  $order->data['commerce_payflow'] = array(
    'tokenid' => str_replace('.', '', uniqid('', TRUE)),
  );

  // Request a token from Payflow Link.
  $payment_method['settings']['ba_desc'] = trim($payment_method['settings']['ba_desc']);
  if (!empty($payment_method['settings']['ba_desc'])) {
    $token = commerce_payflow_link_create_secure_token($payment_method, $order, $payment_method['settings']['ba_desc']);
  }
  else {
    $token = commerce_payflow_link_create_secure_token($payment_method, $order);
  }

  // If we got one back...
  if (!empty($token)) {

    // Set the Payflow Link data array and proceed to the redirect page.
    $order->data['commerce_payflow']['token'] = $token;
    return TRUE;
  }
  else {

    // Otherwise show an error message and remain on the current page.
    drupal_set_message(t('Communication with PayPal failed. Please try again or contact an administrator to resolve the issue.'), 'error');
    return FALSE;
  }
}

/**
 * Payment method callback: redirect form.
 */
function commerce_payflow_link_redirect_form($form, &$form_state, $order, $payment_method) {
  $mode = $payment_method['settings']['mode'];

  // If we didn't get a valid redirect token...
  if (empty($order->data['commerce_payflow']['token'])) {

    // Clear the payment related information from the data array.
    unset($order->data['payment_method']);
    unset($order->data['commerce_payflow']);

    // Show an error message and go back a page.
    drupal_set_message(t('Redirect to PayPal failed. Please try again or contact an administrator to resolve the issue.'), 'error');
    commerce_payment_redirect_pane_previous_page($order, t('Redirect to @description failed.', array(
      '@description' => $payment_method['description'],
    )));
  }
  elseif (!in_array(arg(3), array(
    'back',
    'return',
  ))) {

    // Otherwise determine how to process the redirect based on the payment
    // method settings.
    switch ($payment_method['settings']['redirect_mode']) {

      // For GET, redirect to Payflow Link with the parameters in the URL.
      case 'get':
        drupal_goto(commerce_payflow_link_hosted_checkout_url($payment_method, $order));

      // For POST, render a form that submits to the Payflow Link server.
      case 'post':

        // Set the form to submit to Payflow Link.
        $form['#action'] = commerce_payflow_link_server_url($mode);

        // Store the Payflow payment method instance in the form array.
        $form['commerce_payflow_payment_method'] = array(
          '#type' => 'value',
          '#value' => $payment_method,
        );

        // Add the Secure Token and Secure Token ID from the order's data array.
        $data = array(
          'SECURETOKEN' => $order->data['commerce_payflow']['token'],
          'SECURETOKENID' => $order->data['commerce_payflow']['tokenid'],
        );

        // If the payment method is in test mode, add the appropriate parameter.
        if ($mode == 'test') {
          $data['MODE'] = 'TEST';
        }

        // Add the parameters as hidden form elements to the form array.
        foreach ($data as $name => $value) {
          if (!empty($value)) {
            $form[$name] = array(
              '#type' => 'hidden',
              '#value' => $value,
            );
          }
        }
        $form['submit'] = array(
          '#type' => 'submit',
          '#value' => t('Proceed to payment server'),
          '#attached' => array(
            'js' => array(
              drupal_get_path('module', 'commerce_payment') . '/commerce_payment.js',
            ),
          ),
        );
        return $form;

      // In iframe mode, embed the Payflow checkout page into the Payment page.
      case 'iframe':

        // Store the Payflow payment method instance in the form array.
        $form['commerce_payflow_payment_method'] = array(
          '#type' => 'value',
          '#value' => $payment_method,
        );

        // Add the iframe in a markup element.
        $form['iframe'] = array(
          '#markup' => commerce_payflow_link_hosted_checkout_iframe($payment_method, $order),
        );

        // Add a cancel link if configured.
        if ($payment_method['settings']['cancel_link']) {
          $form['iframe']['#suffix'] = '<div class="commerce-payflow-cancel">' . l(t('Cancel payment and go back'), 'checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key']) . '</div>';
        }
        return $form;
    }
  }
}

/**
 * Payment method callback: redirect form back callback.
 */
function commerce_payflow_link_redirect_form_back($order, $payment_method) {

  // Display a message indicating the customer canceled payment.
  drupal_set_message(t('You have canceled payment at PayPal but may resume the checkout process here when you are ready.'));

  // Remove the payment information from the order data array.
  unset($order->data['commerce_payflow']);
  unset($order->data['payment_method']);
}

/**
 * Payment method callback: redirect form return validation.
 */
function commerce_payflow_link_redirect_form_validate($order, $payment_method) {
  if (!empty($payment_method['settings']['silent_post_logging']) && $payment_method['settings']['silent_post_logging'] == 'full_post') {
    watchdog('commerce_payflow', 'Customer returned from Payflow with the following POST data: !data', array(
      '!data' => '<pre>' . check_plain(print_r($_POST, TRUE)) . '</pre>',
    ), WATCHDOG_NOTICE);
  }

  // If for some reason the payment redirect key in this return post does not
  // match that in the order, prevent processing.
  if (!empty($_POST['USER1']) && $_POST['USER1'] != $order->data['payment_redirect_key']) {
    watchdog('commerce_payflow', 'Customer returned from Payflow with a non-matching redirect key.', array(), WATCHDOG_WARNING);
    return FALSE;
  }

  // This may be an unnecessary step, but if for some reason the user does end
  // up returning at the success URL with a Failed payment, go back.
  if (isset($_POST['RESULT']) && !commerce_payflow_link_validate_result($_POST['RESULT'])) {
    $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
    $order_total = $order_wrapper->commerce_order_total
      ->value();

    // Determine the currency code used to actually process the transaction,
    // which will either be the default currency code or the currency code of
    // the charge if it's supported by PayPal if that option is enabled.
    $currency_code = $payment_method['settings']['currency_code'];
    if (!empty($payment_method['settings']['allow_supported_currencies']) && in_array($order_total['currency_code'], array_keys(commerce_paypal_currencies($payment_method['method_id'])))) {
      $currency_code = $order_total['currency_code'];
    }

    // Provide a more descriptive error message in the failed transaction and
    // the watchdog.
    $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id);
    $transaction->instance_id = $payment_method['instance_id'];
    $transaction->amount = commerce_currency_decimal_to_amount(isset($_POST['AMT']) ? $_POST['AMT'] : 0, $currency_code);
    $transaction->currency_code = $currency_code;
    $transaction->payload[REQUEST_TIME] = $_POST;
    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
    $transaction->message = commerce_payflow_link_result_message($_POST['RESULT']);
    commerce_payment_transaction_save($transaction);
    return FALSE;
  }
}

/**
 * Payment method callback: redirect form return submission.
 */
function commerce_payflow_link_redirect_form_submit($order, $payment_method) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $order_total = $order_wrapper->commerce_order_total
    ->value();

  // Determine the currency code used to actually process the transaction,
  // which will either be the default currency code or the currency code of the
  // charge if it's supported by PayPal if that option is enabled.
  if (!empty($_POST['CURRENCY'])) {
    $currency_code = $_POST['CURRENCY'];
  }
  else {
    $currency_code = $payment_method['settings']['currency_code'];
    if (!empty($payment_method['settings']['allow_supported_currencies']) && in_array($order_total['currency_code'], array_keys(commerce_paypal_currencies($payment_method['method_id'])))) {
      $currency_code = $order_total['currency_code'];
    }
  }

  // Convert the order total to the currency specified by the settings.
  $amount = commerce_currency_convert($order_total['amount'], $order_total['currency_code'], $currency_code);

  // Prepare a transaction object to log the API response.
  $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id);
  $transaction->instance_id = $payment_method['instance_id'];
  $transaction->amount = commerce_currency_decimal_to_amount($_POST['AMT'], $currency_code);
  $transaction->currency_code = $currency_code;
  $transaction->payload[REQUEST_TIME] = $_POST;
  $transaction->remote_id = $_POST['PNREF'];
  $transaction->data['commerce_payflow']['pnref'] = $_POST['PNREF'];

  // Determine the type of transaction.
  if (!empty($_POST['TRXTYPE'])) {
    $trxtype = $_POST['TRXTYPE'];
  }
  elseif (!empty($_POST['TYPE'])) {
    $trxtype = $_POST['TYPE'];
  }
  else {
    $trxtype = $payment_method['settings']['trxtype'];
  }

  // Store the type of transaction in the remote status.
  $transaction->remote_status = $trxtype;
  $trxtype_name = commerce_payflow_trxtype_name($trxtype);

  // Build a meaningful response message.
  $message = array();

  // Set the transaction status based on the type of transaction this was.
  if (intval($_POST['RESULT']) == 0) {
    switch ($trxtype) {
      case 'S':
        $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
        break;
      case 'A':
      default:
        $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
        break;
    }
    $message[] = '<b>' . t('@action - Success', array(
      '@action' => $trxtype_name,
    )) . '</b>';
  }
  elseif (intval($_POST['RESULT']) == 126) {
    $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
    $message[] = '<b>' . t('@action - Pending fraud review', array(
      '@action' => $trxtype_name,
    )) . '</b>';
  }

  // Store the tender type in the transaction's data array.
  $transaction->data['commerce_payflow']['tender'] = $_POST['TENDER'];

  // Add the CC info if present.
  if (!empty($_POST['ACCT']) && !empty($_POST['EXPDATE'])) {

    // Convert the MMYY expiration date to a MM/YY format.
    $exp = str_pad($_POST['EXPDATE'], 4, '0', STR_PAD_LEFT);
    $exp = substr($exp, 0, 2) . '/' . substr($exp, -2);
    if (isset($_POST['CARDTYPE'])) {
      $message[] = t('@cardtype ending in @last4, exp. @exp', array(
        '@cardtype' => commerce_payflow_cardtype_name($_POST['CARDTYPE']),
        '@last4' => $_POST['ACCT'],
        '@exp' => $exp,
      ));
    }
    else {
      $message[] = t('Credit card ending in @last4, exp. @exp', array(
        '@last4' => $_POST['ACCT'],
        '@exp' => $exp,
      ));
    }
  }

  // Add the AVS response if present.
  if (!empty($_POST['AVSADDR']) || !empty($_POST['AVSZIP']) || !empty($_POST['AVSDATA'])) {
    if (!empty($_POST['AVSDATA'])) {
      $avsdata = $_POST['AVSDATA'];
    }
    else {
      $avsdata = '';
      if (!empty($_POST['AVSADDR'])) {
        $avsdata .= $_POST['AVSADDR'];
      }
      if (!empty($_POST['AVSZIP'])) {
        $avsdata .= $_POST['AVSZIP'];
      }
    }
    $message[] = t('AVS response: @avs', array(
      '@avs' => $avsdata,
    ));
  }

  // Add the CVV response if present.
  if (!empty($_POST['PROCCVV2'])) {
    $message[] = t('CVV2 match: @cvv', array(
      '@cvv' => commerce_paypal_cvv_match_message($_POST['PROCCVV2']),
    ));
  }

  // Add the pending reason if present.
  if (!empty($_POST['PENDINGREASON']) && $_POST['PENDINGREASON'] != 'completed') {
    $message[] = commerce_paypal_short_pending_reason($_POST['PENDINGREASON']);

    // And ensure the local and remote status are pending.
    $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
  }

  // If this was a PayPal payment, store whether or not it came with a billing
  // agreement for use in reference transactions.
  if ($_POST['TENDER'] == 'P') {
    $message[] = t('Payment initiated from a PayPal account.');

    // Add the PayPal fees if given.
    if (!empty($_POST['FEEAMT'])) {
      $message[] = t('PayPal fees: @feeamt', array(
        '@feeamt' => $_POST['FEEAMT'],
      ));
    }

    // Store the payment type so we know if the payment is via echeck.
    if (!empty($_POST['PAYMENTTYPE'])) {
      $transaction->data['commerce_paypal_ec']['paymenttype'] = $_POST['PAYMENTTYPE'];
    }
    else {
      $transaction->data['commerce_paypal_ec']['paymenttype'] = '';
    }

    // Store the billing agreement ID if applicable.
    if (!empty($_POST['BAID'])) {
      $transaction->data['commerce_payflow']['baid'] = $_POST['BAID'];
      $message[] = t('Billing agreement ID: @baid', array(
        '@baid' => $_POST['BAID'],
      ));
    }
    else {
      $transaction->data['commerce_payflow']['baid'] = '';
    }

    // Because follow-up API requests for this transaction must be submitted
    // directly to PayPal, check to see if there's a corresponding Express
    // Checkout payment method configured and update the remote ID to the PayPal
    // transaction ID.
    if (module_exists('commerce_paypal_ec') && !empty($payment_method['settings']['paypal_ec_instance'])) {
      $transaction->remote_id = $_POST['PPREF'];

      // Also update the remote status to match one expected by PayPal.
      if (!is_null(commerce_payflow_paypal_remote_status($trxtype))) {
        $transaction->remote_status = commerce_payflow_paypal_remote_status($trxtype);
      }

      // Finally set the method_id and instance_id to point to the Express
      // Checkout instance.
      $transaction->data['commerce_payflow']['original_instance'] = $payment_method['instance_id'];
      $transaction->payment_method = 'paypal_ec';
      $transaction->instance_id = 'paypal_ec|' . $payment_method['settings']['paypal_ec_instance'];
    }
  }

  // Set the final message.
  $transaction->message = implode('<br />', $message);

  // Save the transaction information.
  commerce_payment_transaction_save($transaction);
}

/**
 * Implements hook_form_alter().
 */
function commerce_payflow_form_alter(&$form, &$form_state, $form_id) {

  // If we're altering a checkout form that has the Payflow Link radio button...
  if (strpos($form_id, 'commerce_checkout_form_') === 0 && !empty($form['commerce_payment']['payment_method'])) {
    $payflow_link = FALSE;
    foreach ($form['commerce_payment']['payment_method']['#options'] as $key => &$value) {
      list($method_id, $rule_name) = explode('|', $key);

      // If we find Payflow Link, include its CSS on the form and exit the loop.
      if (in_array($method_id, array(
        'payflow_link',
        'paypal_ppa',
      ))) {

        // Merge in the default settings in case the payment method has not been
        // configured yet.
        $payment_method = commerce_payment_method_instance_load($key);
        $payment_method['settings'] += commerce_payflow_link_default_settings();
        $payment_icons = array_values($payment_method['settings']['payment_icons']);
        $payflow_link = TRUE;

        // Prepare an icons array that only includes enabled icons.
        $icons = commerce_paypal_icons($payment_icons);
        if (in_array('paypal', $payment_icons, TRUE)) {
          $value = t('Credit card, debit card, or PayPal:') . ' ' . implode(' ', $icons);
        }
        else {
          $value = t('Credit card or debit card:') . ' ' . implode(' ', $icons);
        }

        // Check to see if we should disable a corresponding Express Checkout
        // payment method option.
        if (!empty($payment_method['settings']['paypal_ec_disable']) && !empty($payment_method['settings']['paypal_ec_instance'])) {
          $ec_instance_id = 'paypal_ec|' . $payment_method['settings']['paypal_ec_instance'];

          // Remove the option from the list.
          unset($form['commerce_payment']['payment_method']['#options'][$ec_instance_id]);

          // Reset the default selection if that happened to be it.
          if ($form['commerce_payment']['payment_method']['#default_value'] == $ec_instance_id) {
            reset($form['commerce_payment']['payment_method']['#options']);
            $default_value = key($form['commerce_payment']['payment_method']['#options']);

            // Set the new default value of the payment method radio buttons.
            $form['commerce_payment']['payment_method']['#default_value'] = $default_value;

            // Update the payment details area of the form if necessary.
            $default_payment_method = commerce_payment_method_instance_load($default_value);
            if ($callback = commerce_payment_method_callback($default_payment_method, 'submit_form')) {
              $form['commerce_payment']['payment_details'] = $callback($default_payment_method, array(), commerce_checkout_pane_load('commerce_payment'), $form_state['order']);
            }
            else {
              $form['commerce_payment']['payment_details'] = array();
            }
          }
        }
      }
    }

    // If we did find Payflow Link, include its CSS now.
    if ($payflow_link) {

      // Include its CSS on the form.
      $form['commerce_payment']['payment_method']['#attached']['css'][] = drupal_get_path('module', 'commerce_payflow') . '/theme/commerce_payflow.theme.css';
    }
  }

  // Alter the Payment checkout page form for Payflow Link.
  if ($form_id == 'commerce_checkout_form_payment' && !empty($form['commerce_payflow_payment_method'])) {
    switch ($form['commerce_payflow_payment_method']['#value']['settings']['redirect_mode']) {

      // Display the automatic redirection for POST redirects.
      case 'post':
        $form['help']['#markup'] = '<div class="checkout-help">' . t('Please wait while you are redirected to the payment server. If nothing happens within 10 seconds, please click on the button below.') . '</div>';
        break;

      // Clear the help text for embedded iframe payment.
      case 'iframe':
        unset($form['help']);
        break;
    }
  }

  // Alter the Review checkout page form for Payflow Link.
  if ($form_id == 'commerce_checkout_form_review' && !empty($form_state['order']->data['commerce_payflow'])) {
    drupal_add_js(array(
      'commercePayflow' => array(
        'page' => 'review',
      ),
    ), 'setting');
    $form['#attached']['js'][] = drupal_get_path('module', 'commerce_payflow') . '/commerce_payflow.js';

    // If the Payflow query variable is present, reshow the error message and
    // reload the page.
    if (!empty($_GET['payflow-page']) && $_GET['payflow-page'] == 'review') {
      drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
      drupal_goto('checkout/' . $form_state['order']->order_id . '/review');
    }
  }

  // Alter the Completion checkout page form for Payflow Link.
  if ($form_id == 'commerce_checkout_form_complete' && !empty($form_state['order']->data['commerce_payflow'])) {
    $form['#attached']['js'][] = drupal_get_path('module', 'commerce_payflow') . '/commerce_payflow.js';
  }
}

/**
 * Requests a Secure Token from Payflow for use in follow-up API requests.
 *
 * @param $payment_method
 *   The payment method instance to use to generate the Secure Token.
 * @param $order
 *   The order whose details should be submitted in the Secure Token request.
 * @param $billing_agreement
 *   The name to use for PayPal billing agreements if the customer pays via
 *   Express Checkout and the site needs to perform reference transactions.
 *
 * @return
 *   The Secure Token if successfully retrieved or FALSE on failure.
 */
function commerce_payflow_link_create_secure_token($payment_method, $order, $billing_agreement = NULL) {

  // Extract the order total value array.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $order_total = $order_wrapper->commerce_order_total
    ->value();

  // Determine the currency code to use to actually process the transaction,
  // which will either be the default currency code or the currency code of the
  // order if it's supported by PayPal if that option is enabled.
  $currency_code = $payment_method['settings']['currency_code'];
  $order_currency_code = $order_total['currency_code'];
  if (!empty($settings['allow_supported_currencies']) && in_array($order_currency_code, array_keys(commerce_paypal_currencies($payment_method['method_id'])))) {
    $currency_code = $order_currency_code;
  }

  // Prepare a transaction amount value in the proper currency.
  $amt = commerce_currency_convert($order_total['amount'], $order_total['currency_code'], $currency_code);

  // Prepare the billing address for use in the request.
  $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address
    ->value();
  if (empty($billing_address['first_name'])) {
    $name_parts = explode(' ', $billing_address['name_line']);
    $billing_address['first_name'] = array_shift($name_parts);
    $billing_address['last_name'] = implode(' ', $name_parts);
  }

  // Build a name-value pair array for this transaction.
  $nvp = array(
    // Request a secure token using our order's token ID.
    'CREATESECURETOKEN' => 'Y',
    'SECURETOKENID' => $order->data['commerce_payflow']['tokenid'],
    // Indicate the type and amount of the transaction.
    'TRXTYPE' => $payment_method['settings']['trxtype'],
    'AMT' => commerce_paypal_price_amount($amt, $currency_code),
    'CURRENCY' => $currency_code,
    'INVNUM' => commerce_paypal_ipn_invoice($order),
    // Add the billing address.
    'BILLTOEMAIL' => substr($order->mail, 0, 60),
    'BILLTOFIRSTNAME' => substr($billing_address['first_name'], 0, 45),
    'BILLTOLASTNAME' => substr($billing_address['last_name'], 0, 45),
    'BILLTOSTREET' => substr($billing_address['thoroughfare'], 0, 150),
    'BILLTOCITY' => substr($billing_address['locality'], 0, 45),
    'BILLTOSTATE' => substr($billing_address['administrative_area'], 0, 2),
    'BILLTOCOUNTRY' => substr($billing_address['country'], 0, 2),
    'BILLTOZIP' => substr($billing_address['postal_code'], 0, 10),
    // Add application specific parameters.
    'BUTTONSOURCE' => $payment_method['buttonsource'],
    'ERRORURL' => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array(
      'absolute' => TRUE,
    )),
    'RETURNURL' => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array(
      'absolute' => TRUE,
    )),
    'CANCELURL' => url('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key'], array(
      'absolute' => TRUE,
    )),
    'DISABLERECEIPT' => 'TRUE',
    // @todo Support the 'MOBILE' template if we have a way to determine the
    // site is currently using a responsive theme.
    'TEMPLATE' => $payment_method['settings']['redirect_mode'] == 'iframe' ? 'MINLAYOUT' : 'TEMPLATEA',
    'CSCREQUIRED' => 'TRUE',
    'CSCEDIT' => 'TRUE',
    'URLMETHOD' => 'POST',
    // Store the payment redirect key in a custom field.
    'USER1' => $order->data['payment_redirect_key'],
  );

  // If Express Checkout is enabled, include the IPN URL.
  if (module_exists('commerce_paypal_ec') && !empty($payment_method['settings']['paypal_ec_instance'])) {

    // Ensure the Rule is still valid to receive Express Checkout IPNs.
    if (in_array($payment_method['settings']['paypal_ec_instance'], array_keys(commerce_paypal_ec_enabled_rules()))) {
      $nvp['NOTIFYURL'] = commerce_paypal_ipn_url('paypal_ec|' . $payment_method['settings']['paypal_ec_instance']);
    }
    else {
      watchdog('commerce_payflow', 'Payflow Link is configured to use a disabled or non-existent Express Checkout payment method rule.', array(), WATCHDOG_WARNING);
    }
  }

  // If reference transactions are supported by the payment method isntance and
  // a billing agreement description is passed in, set its parameters.
  if (!empty($payment_method['settings']['reference_transactions']) && !is_null($billing_agreement)) {
    $nvp['BILLINGTYPE'] = 'MerchantInitiatedBilling';
    if (!empty($billing_agreement)) {
      $nvp['BA_DESC'] = $billing_agreement;
    }
  }

  // If enabled, email the customer a receipt from PayPal.
  if (!empty($payment_method['settings']['emailcustomer'])) {
    $nvp['EMAILCUSTOMER'] = 'TRUE';
  }

  // If the Shipping module is on and we have a shipping address...
  if (module_exists('commerce_shipping') && !empty($order_wrapper->commerce_customer_shipping->commerce_customer_address)) {
    $shipping_address = $order_wrapper->commerce_customer_shipping->commerce_customer_address
      ->value();

    // Add the shipping address parameters to the request.
    $nvp += array(
      'SHIPTOFIRSTNAME' => substr($shipping_address['first_name'], 0, 45),
      'SHIPTOLASTNAME' => substr($shipping_address['last_name'], 0, 45),
      'SHIPTOSTREET' => substr($shipping_address['thoroughfare'], 0, 150),
      'SHIPTOCITY' => substr($shipping_address['locality'], 0, 45),
      'SHIPTOSTATE' => substr($shipping_address['administrative_area'], 0, 2),
      'SHIPTOCOUNTRY' => substr($shipping_address['country'], 0, 2),
      'SHIPTOZIP' => substr($shipping_address['postal_code'], 0, 10),
    );
  }

  // Add the line item details to the array.
  $nvp += commerce_payflow_link_itemize_order($order, $currency_code);

  // Submit the API request to Payflow.
  $response = commerce_payflow_api_request($payment_method, 'pro', $nvp, $order);

  // If the request is successful, return the token.
  if (isset($response['RESULT']) && $response['RESULT'] == '0') {
    return $response['SECURETOKEN'];
  }

  // Otherwise indicate failure by returning FALSE.
  return FALSE;
}

/**
 * Returns an itemized order data array for use in a name-value pair array.
 *
 * @param $order
 *   The order whose line items should be converted into name-value pairs.
 * @param $currency_code
 *   The currency code to use to calculate all amounts.
 *
 * @return
 *   The name-value pair array representing the line items that should be added
 *   to the name-value pair array for an API request.
 */
function commerce_payflow_link_itemize_order($order, $currency_code) {
  $nvp = array();
  $currency = commerce_currency_load($currency_code);

  // Extract the order total value array.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $order_total = $order_wrapper->commerce_order_total
    ->value();

  // Initialize the order level amount parameter.
  $amt = commerce_currency_convert($order_total['amount'], $order_total['currency_code'], $currency_code);

  // Loop over all the line items on the order.
  $i = 0;
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {

    // Extract the unit price total value array.
    $unit_price = $line_item_wrapper->commerce_unit_price
      ->value();

    // Calculate the cost as the unit price minus the tax amount and add it to
    // the running total for the order.
    $l_cost = commerce_currency_convert($unit_price['amount'], $unit_price['currency_code'], $currency_code);

    // Add the line item to the return array.
    $nvp += array(
      'L_NAME' . $i => commerce_line_item_title($line_item_wrapper
        ->value()),
      'L_COST' . $i => commerce_paypal_price_amount($l_cost, $currency_code),
      'L_QTY' . $i => round($line_item_wrapper->quantity
        ->value()),
    );

    // If it was a product line item, add the SKU.
    if (in_array($line_item_wrapper->type
      ->value(), commerce_product_line_item_types())) {
      $nvp += array(
        'L_SKU' . $i => $line_item_wrapper->line_item_label
          ->value(),
      );
    }
    $i++;
  }

  // Determine the order level item amount and tax amount line items. To prevent
  // rounding problems getting in the way, we calculate them based on the order
  // total instead of tallying it from each line item.
  if (module_exists('commerce_tax')) {
    $taxamt = commerce_round(COMMERCE_ROUND_HALF_UP, commerce_tax_total_amount($order_total['data']['components'], FALSE, $currency_code));
  }
  else {
    $taxamt = 0;
  }

  // Add payment details line items.
  $nvp += array(
    'ITEMAMT' => commerce_paypal_price_amount($amt - $taxamt, $currency_code),
    'TAXAMT' => commerce_paypal_price_amount($taxamt, $currency_code),
  );
  return $nvp;
}

/**
 * Submits an API request to Payflow.
 *
 * This function is currently used by PayPal Payments Advanced and Payflow Link.
 *
 * This function may be used for any PayPal payment method that uses the same
 * settings array structure as these other payment methods and whose API
 * requests should be submitted to the same URLs as determined by the function
 * commerce_payflow_api_server_url().
 *
 * @param $payment_method
 *   The payment method instance array associated with this API request.
 * @param $api
 *   Either 'pro' or 'link' indicating which API server the request should be
 *   sent to.
 * @param $nvp
 *   The set of name-value pairs describing the transaction to submit.
 * @param $order
 *   The order the payment request is being made for.
 *
 * @return
 *   The response array from PayPal if successful or FALSE on error.
 */
function commerce_payflow_api_request($payment_method, $api, $nvp = array(), $order = NULL) {
  $mode = $payment_method['settings']['mode'];

  // Get the API endpoint URL for the payment method's transaction mode.
  if ($api == 'pro') {
    $url = commerce_payflow_pro_server_url($mode);
  }
  else {
    $url = commerce_payflow_link_server_url($mode);
  }

  // Add the default name-value pairs to the array.
  $nvp += array(
    // API credentials
    'PARTNER' => $payment_method['settings']['partner'],
    'VENDOR' => $payment_method['settings']['vendor'],
    'USER' => $payment_method['settings']['user'],
    'PWD' => $payment_method['settings']['password'],
    // Set the mode based on which server we're submitting to.
    'MODE' => $mode == 'test' ? 'TEST' : 'LIVE',
  );

  // Allow modules to alter parameters of the API request.
  drupal_alter('commerce_payflow_api_request', $nvp, $order, $payment_method);

  // Log the request if specified.
  if ($payment_method['settings']['log']['request'] == 'request') {

    // Mask sensitive request data.
    $log_nvp = $nvp;
    $log_nvp['PWD'] = str_repeat('X', strlen($log_nvp['PWD']));
    if (!empty($log_nvp['ACCT'])) {
      $log_nvp['ACCT'] = str_repeat('X', strlen($log_nvp['ACCT']) - 4) . substr($log_nvp['ACCT'], -4);
    }
    if (!empty($log_nvp['CVV2'])) {
      $log_nvp['CVV2'] = str_repeat('X', strlen($log_nvp['CVV2']));
    }
    watchdog('commerce_payflow', 'Payflow API request to @url: !param', array(
      '@url' => $url,
      '!param' => '<pre>' . check_plain(print_r($log_nvp, TRUE)) . '</pre>',
    ), WATCHDOG_DEBUG);
  }

  // Prepare the name-value pair array to be sent as a string.
  $pairs = array();
  foreach ($nvp as $key => $value) {

    // Since we aren't supposed to urlencode parameter values for PFL / PPA API
    // requests, we strip out ampersands and equals signs to
    $pairs[] = $key . '=' . str_replace(array(
      '&',
      '=',
      '#',
    ), array(
      '',
    ), $value);
  }

  // Setup the cURL request.
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_VERBOSE, 0);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $pairs));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  curl_setopt($ch, CURLOPT_TIMEOUT, 45);
  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', ''));
  }
  $result = curl_exec($ch);

  // Log any errors to the watchdog.
  if ($error = curl_error($ch)) {
    watchdog('commerce_payflow', 'cURL error: @error', array(
      '@error' => $error,
    ), WATCHDOG_ERROR);
    return FALSE;
  }
  curl_close($ch);

  // Make the response an array.
  $response = array();
  foreach (explode('&', $result) as $nvp) {
    list($key, $value) = explode('=', $nvp);
    $response[urldecode($key)] = urldecode($value);
  }

  // Log the response if specified.
  if ($payment_method['settings']['log']['response'] == 'response') {
    watchdog('commerce_payflow', 'Payflow server response: !param', array(
      '!param' => '<pre>' . check_plain(print_r($response, TRUE)) . '</pre>',
    ), WATCHDOG_DEBUG);
  }
  return $response;
}

/**
 * Returns the URL to a Payflow Pro API server.
 *
 * @param $mode
 *   Either 'test' or 'live' indicating which server's URL to return.
 *
 * @return
 *   The request URL with a trailing slash.
 */
function commerce_payflow_pro_server_url($mode) {
  switch ($mode) {
    case 'test':
      return 'https://pilot-payflowpro.paypal.com/';
    case 'live':
      return 'https://payflowpro.paypal.com/';
  }
}

/**
 * Returns the URL to a Payflow Link API server.
 *
 * @param $mode
 *   Either 'test' or 'live' indicating which server's URL to return.
 *
 * @return
 *   The request URL with a trailing slash.
 */
function commerce_payflow_link_server_url($mode) {
  switch ($mode) {
    case 'test':
      return 'https://pilot-payflowlink.paypal.com/';
    case 'live':
      return 'https://payflowlink.paypal.com/';
  }
}

/**
 * Returns the URL to the specified Payflow Link Hosted Checkout page.
 *
 * @param $payment_method
 *   The payment method instance used to generate a Secure Token for the order.
 * @param $order
 *   The order object the hosted checkout is for.
 *
 * @return
 *   The URL to use to redirect to Payflow's Hosted Checkout page.
 */
function commerce_payflow_link_hosted_checkout_url($payment_method, $order) {
  $mode = $payment_method['settings']['mode'];

  // Build a query array using information from the order object.
  $query = array(
    'SECURETOKEN' => $order->data['commerce_payflow']['token'],
    'SECURETOKENID' => $order->data['commerce_payflow']['tokenid'],
  );

  // Set the MODE parameter if the URL is for the test server.
  if ($mode == 'test') {
    $query['MODE'] = 'TEST';
  }

  // Grab the base URL of the appropriate API server.
  return url(commerce_payflow_link_server_url($mode), array(
    'query' => $query,
  ));
}

/**
 * Returns an iframe embedding the specified Payflow Link Hosted Checkout page.
 *
 * @param $payment_method
 *   The payment method instance used to generate a Secure Token for the order.
 * @param $order
 *   The order object the hosted checkout is for.
 *
 * @return
 *   The iframe HTML to use to embed Payflow's Hosted Checkout page on-site.
 */
function commerce_payflow_link_hosted_checkout_iframe($payment_method, $order) {
  $name = $payment_method['method_id'] == 'payflow_link' ? 'embedded-payflow-link' : 'embedded-paypal-ppa';
  return '<iframe src="' . commerce_payflow_link_hosted_checkout_url($payment_method, $order) . '" name="' . $name . '" scrolling="no" frameborder="0" width="490px" height="565px"></iframe>';
}

/**
 * Determines whether or not a transaction was accepted based on the RESULT.
 *
 * @param $result
 *   The RESULT value from a Payflow transaction.
 *
 * @return
 *   Boolean indicating whether or not a transaction was accepted.
 */
function commerce_payflow_link_validate_result($result) {

  // Return TRUE for a transaction that was either accepted or has been flagged
  // for manual review by the merchant.
  if (in_array(intval($result), array(
    0,
    126,
  ))) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Returns the message to display to a customer explaining the RESULT of a
 * Payflow transaction.
 *
 * @param $result
 *   The RESULT value from a Payflow transaction.
 *
 * @return
 *   An error or explanation message fit for display to a customer.
 */
function commerce_payflow_link_result_message($result) {
  switch (intval($result)) {
    case 0:
      return t('Transaction approved.');
    case 1:
      return t('Account authentication error. Please contact an administrator to resolve this issue.');
    case 5:
    case 26:
      return t('The Payflow hosted checkout page is not configured for use. Please contact an administrator to resolve this issue.');
    case 2:
    case 25:
      return t('You have attempted to use an invalid payment method. Please check your payment information and try again.');
    case 3:
      return t('The specified transaction type is not appropriate for this transaction.');
    case 4:
    case 6:
      return t('The payment request specified an invalid amount format or currency code. Please contact an administrator to resolve this issue.');
    case 7:
    case 8:
    case 9:
    case 10:
    case 19:
    case 20:
      return t('The payment request included invalid parameters. Please contact an administrator to resolve this issue.');
    case 11:
    case 115:
    case 160:
    case 161:
    case 162:
      return t('The payment request timed out. Please try again or contact an administrator to resolve the issue.');
    case 12:
    case 13:
    case 22:
    case 23:
    case 24:
      return t('Payment declined. Please check your payment information and try again.');
    case 27:
    case 28:
    case 29:
    case 30:
    case 31:
    case 32:
    case 33:
    case 34:
    case 35:
    case 36:
    case 37:
    case 52:
    case 99:
    case 100:
    case 101:
    case 102:
    case 103:
    case 104:
    case 105:
    case 106:
    case 107:
    case 108:
    case 109:
    case 110:
    case 111:
    case 113:
    case 116:
    case 118:
    case 120:
    case 121:
    case 122:
    case 132:
    case 133:
    case 150:
    case 151:
      return t('The transaction failed at PayPal. Please contact an administrator to resolve this issue.');
    case 50:
    case 51:
      return t('Payment was declined due to insufficient funds or transaction limits. Please check your payment information and try again.');
    case 112:
      return t('Address and Zip code do not match. Please check your payment information and try again.');
    case 114:
      return t('Card Security Code (CSC) does not match. Please check your payment information and try again.');
    case 117:
    case 125:
    case 127:
    case 128:
      return t('Payment was declined due to merchant fraud settings. Please contact an administrator to resolve this issue.');
    case 126:
      return t('Payment was flagged for review by the merchant. We will validate the payment and update your order as soon as possible.');
  }
}

/**
 * Returns the description of a transaction type for a Payflow payment action.
 */
function commerce_payflow_trxtype_name($trxtype) {
  switch ($trxtype) {
    case 'A':
      return t('Authorization only');
    case 'S':
      return t('Authorization and capture');
    case 'D':
      return t('Prior authorization capture');
    case 'V':
      return t('Void');
    case 'C':
      return t('Refund');
    default:
      return t('Unknown transaction type');
  }
}

/**
 * Returns the PayPal remote status that corresponds to the given Payflow
 * transaction type.
 */
function commerce_payflow_paypal_remote_status($trxtype) {
  switch ($trxtype) {
    case 'A':
      return 'Pending';
    case 'S':
    case 'D':
      return 'Completed';
    case 'V':
      return 'Voided';
    case 'C':
      return 'Refunded';
    default:
      return NULL;
  }
}

/**
 * Returns the name of the credit card type matching the CARDTYPE value.
 */
function commerce_payflow_cardtype_name($cardtype) {
  switch (intval($cardtype)) {
    case 0:
      return t('Visa');
    case 1:
      return t('MasterCard');
    case 2:
      return t('Discover');
    case 3:
      return t('American Express');
    case 4:
      return t("Diner's Club");
    case 5:
      return t('JCB');
    default:
      return t('Credit card');
  }
}

Functions

Namesort descending Description
commerce_payflow_api_request Submits an API request to Payflow.
commerce_payflow_cardtype_name Returns the name of the credit card type matching the CARDTYPE value.
commerce_payflow_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_payflow_form_alter Implements hook_form_alter().
commerce_payflow_link_capture_void_access Determines access to the prior authorization capture form or void form for Payflow Link credit card transactions.
commerce_payflow_link_create_secure_token Requests a Secure Token from Payflow for use in follow-up API requests.
commerce_payflow_link_default_settings Returns the default settings for the PayPal WPS payment method.
commerce_payflow_link_hosted_checkout_iframe Returns an iframe embedding the specified Payflow Link Hosted Checkout page.
commerce_payflow_link_hosted_checkout_url Returns the URL to the specified Payflow Link Hosted Checkout page.
commerce_payflow_link_itemize_order Returns an itemized order data array for use in a name-value pair array.
commerce_payflow_link_redirect_form Payment method callback: redirect form.
commerce_payflow_link_redirect_form_back Payment method callback: redirect form back callback.
commerce_payflow_link_redirect_form_submit Payment method callback: redirect form return submission.
commerce_payflow_link_redirect_form_validate Payment method callback: redirect form return validation.
commerce_payflow_link_reference_access Determines access to the reference transaction form for Payflow Link credit card transactions.
commerce_payflow_link_refund_access Determines access to the refund form for Payflow Link credit card transactions.
commerce_payflow_link_result_message Returns the message to display to a customer explaining the RESULT of a Payflow transaction.
commerce_payflow_link_server_url Returns the URL to a Payflow Link API server.
commerce_payflow_link_settings_form Payment method callback: settings form.
commerce_payflow_link_submit_form Payment method callback: adds a message to the submission form if enabled in the payment method settings.
commerce_payflow_link_submit_form_submit Payment method callback: submit form submission.
commerce_payflow_link_submit_form_validate Payment method callback: submit form validation.
commerce_payflow_link_validate_result Determines whether or not a transaction was accepted based on the RESULT.
commerce_payflow_menu Implements hook_menu().
commerce_payflow_page_alter Implements hook_page_alter().
commerce_payflow_paypal_remote_status Returns the PayPal remote status that corresponds to the given Payflow transaction type.
commerce_payflow_pro_server_url Returns the URL to a Payflow Pro API server.
commerce_payflow_service_description Returns a service description and registration link for the specified method.
commerce_payflow_trxtype_name Returns the description of a transaction type for a Payflow payment action.
commerce_paypal_ppa_settings_form Payment method callback: settings form.