You are here

commerce_paypal_ec.module in Commerce PayPal 7.2

Implements PayPal Express Checkout in Drupal Commerce checkout.

File

modules/ec/commerce_paypal_ec.module
View source
<?php

/**
 * @file
 * Implements PayPal Express Checkout in Drupal Commerce checkout.
 */

// Define PayPal Credit related keys and secrets. These are intentionally public.
define('PAYPAL_BANNER_API_KEY', 'b934ca6ea3d9318f86edd1abe51738246e96e700');
define('PAYPAL_BANNER_API_KEY_STAGING', '5b143c7130a51e9fe93468ee83ddadd0459b7fef');
define('PAYPAL_BANNER_API_SECRET', '505d0a6aa32b1bc7d5b7362bb526d50b10b53b9f');
define('PAYPAL_BANNER_API_SECRET_STAGING', '4c20fe076f0d0edf871e272568f4a91970f1650c');
define('PAYPAL_BANNER_BN_CODE', 'YNUEPVVKCHL7E');

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

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

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

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

/**
 * Determines access to the prior authorization capture form or void form for
 * Paypal EC 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_paypal_ec_capture_void_access($order, $transaction) {

  // Return FALSE if the transaction isn't for Paypal EC or isn't awaiting capture.
  if ($transaction->payment_method != 'paypal_ec' || $transaction->remote_status != 'Pending') {
    return FALSE;
  }

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

  // Return FALSE if the transaction is actually a pending echeck.
  if (!empty($transaction->data['commerce_paypal_ec']['paymenttype']) && $transaction->data['commerce_paypal_ec']['paymenttype'] == 'echeck') {
    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 refund form for Paypal EC 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_paypal_ec_refund_access($order, $transaction) {

  // Return FALSE if the transaction isn't Completed.
  if ($transaction->payment_method != 'paypal_ec' || $transaction->remote_status != 'Completed') {
    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_checkout_page_info().
 */
function commerce_paypal_ec_commerce_checkout_page_info() {
  $checkout_pages = array();
  $checkout_pages['paypal_ec'] = array(
    'title' => t('Confirm order'),
    'help' => t('Confirm your order information and use the button at the bottom of the page to finalize your payment.'),
    'status_cart' => FALSE,
    'locked' => TRUE,
    'buttons' => FALSE,
    'weight' => 30,
  );
  return $checkout_pages;
}

/**
 * Implements hook_commerce_checkout_pane_info().
 */
function commerce_paypal_ec_commerce_checkout_pane_info() {
  $checkout_panes = array();
  $checkout_panes['paypal_ec_review'] = array(
    'title' => t('Review and confirm your order'),
    'name' => t('Express Checkout review and confirm (only to be used on the confirm order page)'),
    'file' => 'includes/commerce_paypal_ec.checkout_pane.inc',
    'base' => 'commerce_paypal_ec_review_pane',
    'page' => 'paypal_ec',
    'fieldset' => FALSE,
  );
  return $checkout_panes;
}

/**
 * Implements hook_commerce_checkout_router().
 */
function commerce_paypal_ec_commerce_checkout_router($order, $checkout_page) {

  // If the current page is the Express Checkout page but the current order did
  // not use the Express Checkout flow...
  if ($checkout_page['page_id'] == 'paypal_ec' && (empty($order->data['commerce_paypal_ec']['flow']) || $order->data['commerce_paypal_ec']['flow'] != 'ec')) {

    // Update the order status to the next checkout page.
    $next_page = $checkout_page['next_page'];
    $order = commerce_order_status_update($order, 'checkout_' . $next_page, FALSE, FALSE);

    // Inform modules of checkout completion if the next page is Completed.
    if ($next_page == 'complete') {
      commerce_checkout_complete($order);
    }

    // Redirect to the URL for the new checkout page.
    $target_uri = commerce_checkout_order_uri($order);
    return drupal_goto($target_uri);
  }
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_paypal_ec_commerce_payment_method_info() {
  $payment_methods = array();
  $payment_methods['paypal_ec'] = array(
    'base' => 'commerce_paypal_ec',
    'buttonsource' => 'CommerceGuys_Cart_EC',
    'title' => t('PayPal Express Checkout'),
    'short_title' => t('PayPal EC'),
    'description' => t('PayPal Express Checkout'),
    'terminal' => FALSE,
    'offsite' => TRUE,
    'offsite_autoredirect' => TRUE,
  );
  return $payment_methods;
}

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

  // Add a registration link to the PayPal Express Checkout 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) {
        if (strpos($row[0]['data']['description']['settings']['machine_name']['#markup'], 'commerce_payment_paypal_ec') > 0) {
          $row[0]['data']['#suffix'] = '<div class="service-description">' . commerce_paypal_ec_service_description() . '</div></div>';
        }
      }
    }
  }
}

/**
 * Returns a service description and registration link for the specified method.
 */
function commerce_paypal_ec_service_description() {
  return t('Allow customers to pay via PayPal and optionally credit card or debit card on a securely hosted checkout form. This payment method requires a PayPal Business 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-express-checkout?partner_id=VZ6B9QLQ8LZEE',
  ));
}

/**
 * Returns the default settings for the PayPal EC payment method.
 */
function commerce_paypal_ec_default_settings() {
  $default_currency = commerce_default_currency();
  $default_settings = array(
    'api_username' => '',
    'api_password' => '',
    'api_signature' => '',
    'server' => 'sandbox',
    'currency_code' => in_array($default_currency, array_keys(commerce_paypal_currencies('paypal_ec'))) ? $default_currency : 'USD',
    'allow_supported_currencies' => FALSE,
    'txn_type' => COMMERCE_CREDIT_AUTH_CAPTURE,
    'ec_mode' => 'Mark',
    'shipping_prompt' => 1,
    'log' => array(
      'request' => 0,
      'response' => 0,
    ),
    'ipn_logging' => 'notification',
    'receiver_emails' => '',
    'reference_transactions' => FALSE,
    'ba_desc' => '',
    'show_payment_instructions' => FALSE,
    'update_billing_profiles' => TRUE,
    'enable_bml' => FALSE,
    'enable_on_cart' => TRUE,
  );
  if (module_exists('commerce_shipping')) {
    $default_settings['update_shipping_profiles'] = TRUE;
  }
  return $default_settings;
}

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

  // Merge default settings into the stored settings array.
  $settings = (array) $settings + commerce_paypal_ec_default_settings();
  $form['service_description'] = array(
    '#markup' => '<div>' . commerce_paypal_ec_service_description() . ' ' . t('Refer to the <a href="!url" target="_blank">module documentation</a> to find your API credentials and ensure your payment method and account settings are configured properly.', array(
      '!url' => 'http://drupal.org/node/1901466',
    )) . '</div>',
  );
  $form['api_username'] = array(
    '#type' => 'textfield',
    '#title' => t('API username'),
    '#default_value' => $settings['api_username'],
  );
  $form['api_password'] = array(
    '#type' => 'textfield',
    '#title' => t('API password'),
    '#default_value' => $settings['api_password'],
  );
  $form['api_signature'] = array(
    '#type' => 'textfield',
    '#title' => t('Signature'),
    '#default_value' => $settings['api_signature'],
  );
  $form['server'] = array(
    '#type' => 'radios',
    '#title' => t('PayPal server'),
    '#options' => array(
      'sandbox' => 'Sandbox - use for testing, requires a PayPal Sandbox account',
      'live' => 'Live - use for processing real transactions',
    ),
    '#default_value' => $settings['server'],
  );
  $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.'),
    '#options' => commerce_paypal_currencies('paypal_ec'),
    '#default_value' => $settings['currency_code'],
  );
  $form['allow_supported_currencies'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow transactions to use any currency in the options list above.'),
    '#description' => t('Transactions in unsupported currencies will still be converted into the default currency.'),
    '#default_value' => $settings['allow_supported_currencies'],
  );
  $form['txn_type'] = array(
    '#type' => 'radios',
    '#title' => t('Default transaction type'),
    '#description' => t('The default will be used to process transactions during checkout.'),
    '#options' => array(
      COMMERCE_CREDIT_AUTH_CAPTURE => t("Sale (direct debit from the customer's PayPal account)"),
      COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual or automated capture after checkout)'),
    ),
    '#default_value' => $settings['txn_type'],
  );
  $form['ec_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Express Checkout mode'),
    '#description' => t('Express Checkout Account Optional (ECAO) where PayPal accounts are not required for payment may not be available in all markets.'),
    '#options' => array(
      'Mark' => t('Require a PayPal account (this is the standard configuration).'),
      'SoleLogin' => t('Allow PayPal AND credit card payments, defaulting to the PayPal form.'),
      'SoleBilling' => t('Allow PayPal AND credit card payments, defaulting to the credit card form.'),
    ),
    '#default_value' => $settings['ec_mode'],
  );
  $form['shipping_prompt'] = array(
    '#type' => 'radios',
    '#title' => t('Shipping address collection'),
    '#description' => t('Express Checkout will only request a shipping address if the Shipping module is enabled to store the address in the order.'),
    '#options' => array(
      '0' => t('Do not ask for a shipping address at PayPal.'),
    ),
    '#default_value' => '0',
  );
  if (module_exists('commerce_shipping')) {
    $form['shipping_prompt']['#options'] += array(
      '1' => t('Ask for a shipping address at PayPal if the order does not have one yet.'),
      '2' => t('Ask for a shipping address at PayPal even if the order already has one.'),
    );
    $form['shipping_prompt']['#default_value'] = $settings['shipping_prompt'];
  }
  $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'],
  );
  $form['ipn_logging'] = array(
    '#type' => 'radios',
    '#title' => t('IPN logging'),
    '#options' => array(
      'notification' => t('Log notifications during IPN validation and processing.'),
      'full_ipn' => t('Log notifications with the full IPN during validation and processing (used for debugging).'),
    ),
    '#default_value' => $settings['ipn_logging'],
  );
  $form['receiver_emails'] = array(
    '#type' => 'textfield',
    '#title' => t('PayPal receiver e-mail addresses'),
    '#description' => t('Enter the primary e-mail address for your PayPal account where you receive Express Checkout payments or a comma separated list of valid e-mail addresses.') . '<br />' . t('IPNs that originate from payments made to a PayPal account whose e-mail address is not in this list will not be processed.'),
    '#default_value' => $settings['receiver_emails'],
  );
  $form['reference_transactions'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable reference transactions for payments captured through Express Checkout.'),
    '#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, you must specify a billing agreement description.'),
    '#default_value' => $settings['ba_desc'],
  );
  $form['show_payment_instructions'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show a message on the checkout form when PayPal EC is selected telling the customer to "Continue with checkout to complete payment via PayPal."'),
    '#default_value' => $settings['show_payment_instructions'],
  );
  $form['update_billing_profiles'] = array(
    '#type' => 'checkbox',
    '#title' => t('Update billing customer profiles with address information the customer enters at PayPal.'),
    '#default_value' => $settings['update_billing_profiles'],
  );
  if (module_exists('commerce_shipping')) {
    $form['update_shipping_profiles'] = array(
      '#type' => 'checkbox',
      '#title' => t('Update shipping customer profiles with address information the customer enters at PayPal.'),
      '#default_value' => $settings['update_shipping_profiles'],
    );
  }

  // Add that scripts that manage the PayPal Credit popin.
  drupal_add_library('system', 'ui.dialog');
  $form['enable_bml'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable the PayPal Credit button on the shopping cart page.'),
    '#description' => t('By selecting PayPal, you also receive PayPal Credit absolutely FREE. PayPal Credit enables customers to buy now and pay over time. Still not sure? <a href="#" class="paypal-bml-popin">Learn more</a>.'),
    '#default_value' => $settings['enable_bml'],
    '#attached' => array(
      'js' => array(
        drupal_get_path('module', 'commerce_paypal_ec') . '/theme/commerce_paypal_ec.popin.js',
      ),
    ),
  );
  $form['paypal_bml_help_text'] = array(
    '#markup' => commerce_paypal_ec_bml_help_text(),
  );
  $form['enable_on_cart'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show the PayPal Express Checkout button on the cart form.'),
    '#default_value' => $settings['enable_on_cart'],
  );
  return $form;
}

/**
 * Returns the PayPal Credit help text HTML.
 */
function commerce_paypal_ec_bml_help_text() {
  return '<div class="paypal-bml-popin-text"><p>' . t('Add PayPal Credit as a payment method and give your customers access to special financing for 6 months on purchases of $99 or more.¹ Even though customers can pay over time, you get paid up front. Plus, offering financing can help you increase sales and get larger orders.²') . '</p><p><strong>' . t('Features:') . '</strong><ul><li>' . t('You get paid up front.') . '</li><li>' . t("There's no additional integration work required.") . '</li><li>' . t('PayPal offers free web banners to advertise that special financing is available.') . '</li></ul></p><p>' . t('Learn more at DrupalCommerce.org.') . '</p><p><small>' . t('¹Subject to consumer credit card approval.') . '</small><br /><small>' . t("²As reported in Nielsen's PayPal Credit Average Order Value study for activity occurring from April 2015 to March 2016 (small merchants) and October 2015 to March 2016 (midsize merchants), which compared PayPal Credit transactions to credit and debit card transactions on websites that offer PayPal Credit as a payment option or within the PayPal Wallet. Nielsen measured 284,890 transactions across 27 mid and small merchants. Copyright Nielsen 2016.") . '</small></p></div>';
}

/**
 * Implements hook_form_alter().
 */
function commerce_paypal_ec_form_alter(&$form, &$form_state, $form_id) {
  if (!is_string($form_id)) {
    return;
  }

  // If we're altering a shopping cart form and PayPal EC is enabled...
  if (strpos($form_id, 'views_form_commerce_cart_form_') === 0 && commerce_paypal_ec_enabled()) {

    // If the cart form View shows line items...
    if (!empty($form_state['build_info']['args'][0]->result)) {
      $order = $form_state['order'];

      // And Express Checkout is also available as a payment option in checkout.
      if (commerce_paypal_ec_enabled($order)) {

        // Add the Express Checkout form as a suffix to the cart form.
        $payment_method = commerce_payment_method_instance_load('paypal_ec|commerce_payment_paypal_ec');
        if (!isset($payment_method['settings']['enable_on_cart']) || !empty($payment_method['settings']['enable_on_cart'])) {
          $ec_form = drupal_get_form('commerce_paypal_ec_order_form', $payment_method, $order);
          $form['#suffix'] .= drupal_render($ec_form);

          // Add the PayPal Credit form with a separator if enabled.
          if (!empty($payment_method['settings']['enable_bml'])) {
            $bml_form = drupal_get_form('commerce_paypal_bml_order_form', $payment_method, $order, TRUE);
            $form['#suffix'] .= drupal_render($bml_form);
          }
        }
      }
    }
  }

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

      // If we find PayPal EC, include its CSS on the form and exit the loop.
      if ($method_id == 'paypal_ec') {
        $paypal_ec = TRUE;
        $value = theme('paypal_ec_mark_image');
      }
    }

    // If we did find PayPal EC, include its CSS now.
    if ($paypal_ec) {
      $form['commerce_payment']['payment_method']['#attached']['css'][] = drupal_get_path('module', 'commerce_paypal_ec') . '/theme/commerce_paypal_ec.theme.css';
    }
  }
}

/**
 * Displays an Express Checkout button as a form that redirects to PayPal.
 */
function commerce_paypal_ec_order_form($form, &$form_state, $payment_method, $order) {
  $form_state['payment_method'] = $payment_method;
  $form_state['order'] = $order;
  $form['#attributes'] = array(
    'class' => array(
      'paypal-ec-order-form',
    ),
  );

  // @todo See if we can embed this using HTTP to avoid potential browser
  // warnings if HTTPS is not enabled on the site.
  $form['paypal_ec'] = array(
    '#type' => 'image_button',
    '#value' => t('Check out with PayPal'),
    '#src' => commerce_paypal_ec_button_url(),
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'commerce_paypal_ec') . '/theme/commerce_paypal_ec.theme.css',
      ),
    ),
  );
  return $form;
}

/**
 * Displays a PayPal Credit button as a form that redirects to PayPal.
 */
function commerce_paypal_bml_order_form($form, &$form_state, $payment_method, $order, $separator = FALSE) {
  $form_state['payment_method'] = $payment_method;
  $form_state['order'] = $order;
  $form['#attributes'] = array(
    'class' => array(
      'paypal-bml-order-form',
    ),
  );

  // Display a separator at the top of this form if necessary; used when this
  // form is rendered beneath an Express Checkout button form.
  if ($separator) {
    $form['separator'] = array(
      '#markup' => '<div class="paypal-ec-bml-separator">' . t('--- OR ---') . '</div>',
    );
  }
  $form['paypal_bml'] = array(
    '#type' => 'image_button',
    '#value' => t('Check out with PayPal Credit'),
    '#src' => commerce_paypal_ec_bml_button_url(),
    '#validate' => array(
      'commerce_paypal_ec_order_form_validate',
    ),
    '#submit' => array(
      'commerce_paypal_ec_order_form_submit',
    ),
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'commerce_paypal_ec') . '/theme/commerce_paypal_ec.theme.css',
      ),
    ),
  );

  // @todo Allow for localization by changing en_US to a supported locale code.
  $form['paypal_bml_undertext'] = array(
    '#markup' => '<div><a target="_blank" class="paypal-bml-text" href="https://www.securecheckout.billmelater.com/paycapture-content/fetch?hash=AU826TU8&content=/bmlweb/ppwpsiw.html"><img src="https://www.paypalobjects.com/webstatic/en_US/btn/btn_bml_text.png" /></a></div>',
  );
  return $form;
}

/**
 * Validate handler: ensures PayPal Express Checkout has been configured.
 */
function commerce_paypal_ec_order_form_validate($form, &$form_state) {

  // Return an error if the enabling action's settings haven't been configured.
  foreach (array(
    'api_username',
    'api_password',
    'api_signature',
  ) as $key) {
    if (empty($form_state['payment_method']['settings'][$key])) {
      form_set_error('', t('PayPal Express Checkout is not configured for use. Please contact an administrator to resolve this issue.'));
    }
  }
}

/**
 * Submit handler: redirect to PayPal Express Checkout.
 */
function commerce_paypal_ec_order_form_submit($form, &$form_state) {

  // Determine the payment flow to use: Credit if that button was used or default
  // to the EC flow.
  if (isset($form_state['values']['paypal_bml'])) {
    $flow = 'bml';
  }
  else {
    $flow = 'ec';
  }

  // Update the order to reference the PayPal Express Checkout payment method.
  $payment_method = $form_state['payment_method'];
  $order = $form_state['order'];
  $order->data['payment_method'] = $payment_method['instance_id'];

  // Generate a payment redirect key.
  $order->data['payment_redirect_key'] = drupal_hash_base64(time());

  // Request a token from Express Checkout.
  $token = commerce_paypal_ec_set_express_checkout($payment_method, $order, $flow);

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

    // Set the Express Checkout data array.
    $order->data['commerce_paypal_ec'] = array(
      'flow' => 'ec',
      'token' => $token,
      'payerid' => FALSE,
    );

    // Set the redirect to PayPal.
    $form_state['redirect'] = commerce_paypal_ec_checkout_url($payment_method['settings']['server'], $order->data['commerce_paypal_ec']['token']);

    // Update the order status to the payment redirect page.
    commerce_order_status_update($order, 'checkout_payment', FALSE, NULL, t('Customer clicked the Express Checkout button on the cart page.'));

    // Save the changes to the order data array.
    commerce_order_save($order);
  }
  else {

    // Otherwise show an error message and remain on the cart page.
    drupal_set_message(t('Redirect to PayPal Express Checkout failed. Please try again or contact an administrator to resolve the issue.'), 'error');
    $form_state['redirect'] = 'cart';
  }
}

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

/**
 * Payment method callback: submit form validation.
 */
function commerce_paypal_ec_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(
    'api_username',
    'api_password',
    'api_signature',
  ) as $key) {
    if (empty($payment_method['settings'][$key])) {
      drupal_set_message(t('PayPal Express Checkout is not configured for use. Please contact an administrator to resolve this issue.'), 'error');
      return FALSE;
    }
  }
  return TRUE;
}

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

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

  // Generate a payment redirect key.
  $order->data['payment_redirect_key'] = drupal_hash_base64(time());

  // Request a token from Express Checkout.
  $token = commerce_paypal_ec_set_express_checkout($payment_method, $order, 'mark');

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

    // Set the Express Checkout data array and proceed to the redirect page.
    $order->data['commerce_paypal_ec'] = array(
      'flow' => 'mark',
      'token' => $token,
      'payerid' => FALSE,
    );
    return TRUE;
  }
  else {

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

/**
 * Payment method callback: redirect form.
 */
function commerce_paypal_ec_redirect_form($form, &$form_state, $order, $payment_method) {

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

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

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

    // Otherwise go ahead and redirect to PayPal.
    drupal_goto(commerce_paypal_ec_checkout_url($payment_method['settings']['server'], $order->data['commerce_paypal_ec']['token']));
  }
}

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

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

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

  // If the customer initially redirected to PayPal from the cart form...
  if ($flow == 'ec') {

    // Send them back to the shopping cart page instead of the previous page in
    // the checkout process.
    commerce_order_status_update($order, 'cart', FALSE, NULL, t('Customer canceled Express Checkout at PayPal.'));
    drupal_goto('cart');
  }
}

/**
 * Payment method callback: redirect form return validation.
 */
function commerce_paypal_ec_redirect_form_validate($order, $payment_method) {
  $payment_method['settings'] += commerce_paypal_ec_default_settings();
  if (!empty($payment_method['settings']['ipn_logging']) && $payment_method['settings']['ipn_logging'] == 'full_ipn') {
    watchdog('commerce_paypal_ec', 'Customer returned from PayPal with the following POST data:!ipn_data', array(
      '!ipn_data' => '<pre>' . check_plain(print_r($_POST, TRUE)) . '</pre>',
    ), WATCHDOG_NOTICE);
  }

  // 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 (!empty($_POST['payment_status']) && $_POST['payment_status'] == 'Failed') {
    return FALSE;
  }

  // Don't attempt to verify the Express Checkout details without a valid token.
  if (empty($order->data['commerce_paypal_ec']['token'])) {
    return FALSE;
  }

  // Build a name-value pair array to obtain buyer information from PayPal.
  $nvp = array(
    'METHOD' => 'GetExpressCheckoutDetails',
    'TOKEN' => $order->data['commerce_paypal_ec']['token'],
  );

  // Submit the API request to PayPal.
  $response = commerce_paypal_api_request($payment_method, $nvp, $order);

  // If the request failed, exit now with a failure message.
  if ($response['ACK'] == 'Failure') {
    return FALSE;
  }

  // Set the Payer ID used to finalize payment.
  $order->data['commerce_paypal_ec']['payerid'] = $response['PAYERID'];

  // If the user is anonymous, add their PayPal e-mail to the order.
  if (empty($order->mail)) {
    $order->mail = $response['EMAIL'];
  }

  // Create a billing information profile for the order with the available info.
  if (!empty($payment_method['settings']['update_billing_profiles'])) {
    commerce_paypal_ec_customer_profile($order, 'billing', $response, 'PAYMENTREQUEST_0_');
  }

  // If the shipping module exists on the site, create a shipping information
  // profile for the order with the available info.
  if (module_exists('commerce_shipping') && !empty($payment_method['settings']['update_shipping_profiles'])) {
    commerce_paypal_ec_customer_profile($order, 'shipping', $response, 'PAYMENTREQUEST_0_');
  }

  // Recalculate the price of products on the order in case taxes have
  // changed or prices have otherwise been affected.
  if ($order->data['commerce_paypal_ec']['flow'] == 'ec') {
    commerce_cart_order_refresh($order);
  }

  // Save the changes to the order.
  commerce_order_save($order);

  // If the customer completed payment using the Mark flow, then we should
  // attempt to process payment now and go back if it fails.
  if ($order->data['commerce_paypal_ec']['flow'] == 'mark') {
    $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
    $charge = $order_wrapper->commerce_order_total
      ->value();

    // Attempt to process the payment.
    if (!commerce_paypal_ec_do_payment($payment_method, $order, $charge)) {
      return FALSE;
    }
  }
}

/**
 * Payment method callback: redirect form return submission.
 */
function commerce_paypal_ec_redirect_form_submit($order, $payment_method) {

  // Because we need to be able to halt checkout completion if payment fails for
  // some reason, instead of getting and processing Express Checkout payment
  // details in the submission step, we do this in the validate step above.
}

/**
 * Payment method callback: validate an IPN based on receiver e-mail address,
 * price, and other parameters as possible.
 */
function commerce_paypal_ec_paypal_ipn_validate($order, $payment_method, $ipn) {

  // Prepare a trimmed list of receiver e-mail addresses.
  if (!empty($payment_method['settings']['receiver_emails'])) {
    $receiver_emails = explode(',', $payment_method['settings']['receiver_emails']);
  }
  else {
    $receiver_emails = array();
  }
  foreach ($receiver_emails as $key => &$email) {
    $email = trim(strtolower($email));
  }

  // Return FALSE if the receiver e-mail does not match the one specified by
  // the payment method instance.
  if (!empty($ipn['receiver_email']) && !in_array(trim(strtolower($ipn['receiver_email'])), $receiver_emails)) {
    commerce_payment_redirect_pane_previous_page($order);
    watchdog('commerce_paypal_ec', 'IPN rejected: invalid receiver e-mail specified (@receiver_email); must match a receiver e-mail address configured in the payment method settings.', array(
      '@receiver_email' => $ipn['receiver_email'],
    ), WATCHDOG_NOTICE);
    return FALSE;
  }

  // Prepare the IPN data for inclusion in the watchdog message if enabled.
  $ipn_data = '';
  if (!empty($payment_method['settings']['ipn_logging']) && $payment_method['settings']['ipn_logging'] == 'full_ipn') {
    $ipn_data = '<pre>' . check_plain(print_r($ipn, TRUE)) . '</pre>';
  }

  // Log a message including the PayPal transaction ID if available.
  if (!empty($ipn['txn_id'])) {
    watchdog('commerce_paypal_ec', 'IPN validated for Order @order_number with ID @txn_id.!ipn_data', array(
      '@order_number' => $order->order_number,
      '@txn_id' => $ipn['txn_id'],
      '!ipn_data' => $ipn_data,
    ), WATCHDOG_NOTICE);
  }
  else {
    watchdog('commerce_paypal_ec', 'IPN validated for Order @order_number.!ipn_data', array(
      '@order_number' => $order->order_number,
      '!ipn_data' => $ipn_data,
    ), WATCHDOG_NOTICE);
  }
}

/**
 * Payment method callback: process an IPN once it's been validated.
 */
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);
}

/**
 * Submits a SetExpressCheckout request to PayPal for the given order.
 *
 * This function does not make any changes to the given order, so storage of the
 * token in the order's data array must happen separately. Additionally, the
 * $order must already have a payment_redirect_key in its data array for the
 * proper creation of return and cancel URLs in the API request.
 *
 * @param $payment_method
 *   The payment method instance array containing the Express Checkout settings.
 * @param $order
 *   The order to set Express Checkout for.
 * @param $flow
 *   Either 'ec' or 'mark' indicating which Express Checkout flow the token is
 *   being requested for.
 *
 * @return
 *   The Express Checkout token if successful or FALSE if not.
 */
function commerce_paypal_ec_set_express_checkout($payment_method, $order, $flow) {

  // 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'];
  if (!empty($payment_method['settings']['allow_supported_currencies']) && in_array($order_total['currency_code'], array_keys(commerce_paypal_currencies('paypal_ec')))) {
    $currency_code = $order_total['currency_code'];
  }

  // Build a name-value pair array for this transaction.
  $nvp = array(
    'METHOD' => 'SetExpressCheckout',
    // Default the Express Checkout landing page to the Mark solution.
    'SOLUTIONTYPE' => 'Mark',
    'LANDINGPAGE' => 'Login',
    // Disable entering notes in PayPal, as we don't have any way to accommodate
    // them right now.
    'ALLOWNOTE' => '0',
    'PAYMENTREQUEST_0_PAYMENTACTION' => commerce_paypal_payment_action($payment_method['settings']['txn_type']),
    'PAYMENTREQUEST_0_AMT' => commerce_paypal_price_amount($order_total['amount'], $order_total['currency_code']),
    'PAYMENTREQUEST_0_CURRENCYCODE' => $currency_code,
    'PAYMENTREQUEST_0_INVNUM' => commerce_paypal_ipn_invoice($order),
    // Set the return and cancel URLs.
    '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,
    )),
  );

  // If reference transactions are enabled and a billing agreement is supplied...
  if (!empty($payment_method['settings']['reference_transactions']) && !empty($payment_method['settings']['ba_desc'])) {
    $nvp['BILLINGTYPE'] = 'MerchantInitiatedBillingSingleAgreement';
    $nvp['L_BILLINGTYPE0'] = 'MerchantInitiatedBillingSingleAgreement';
    $nvp['L_BILLINGAGREEMENTDESCRIPTION0'] = $payment_method['settings']['ba_desc'];
  }

  // Add itemized information to the API request.
  $nvp += commerce_paypal_ec_itemize_order($order, $currency_code);

  // If Express Checkout Account Optional is enabled...
  if ($payment_method['settings']['ec_mode'] != 'Mark') {

    // Update the solution type and landing page parameters accordingly.
    $nvp['SOLUTIONTYPE'] = 'Sole';
    if ($payment_method['settings']['ec_mode'] == 'SoleBilling') {
      $nvp['LANDINGPAGE'] = 'Billing';
    }
  }

  // Overrides specific values for the Credit payment method.
  if ($flow == 'bml') {
    $nvp['USERSELECTEDFUNDINGSOURCE'] = 'BML';
    $nvp['SOLUTIONTYPE'] = 'SOLE';
    $nvp['LANDINGPAGE'] = 'BILLING';
  }

  // If the shipping module is enabled...
  if (module_exists('commerce_shipping')) {

    // If we have a shipping address, pass it to PayPal and do not allow the
    // customer to set a new one at PayPal.
    if (isset($order_wrapper->commerce_customer_shipping) && !empty($order_wrapper->commerce_customer_shipping->commerce_customer_address)) {
      $shipping_address = $order_wrapper->commerce_customer_shipping->commerce_customer_address
        ->value();

      // Ensure there's a name_line.
      if (empty($shipping_address['name_line'])) {
        $shipping_address['name_line'] = $shipping_address['first_name'] . ' ' . $shipping_address['last_name'];
      }

      // Add the shipping address fields to the request.
      $nvp += array(
        'PAYMENTREQUEST_0_SHIPTONAME' => substr($shipping_address['name_line'], 0, 32),
        'PAYMENTREQUEST_0_SHIPTOSTREET' => substr($shipping_address['thoroughfare'], 0, 100),
        'PAYMENTREQUEST_0_SHIPTOSTREET2' => substr($shipping_address['premise'], 0, 100),
        'PAYMENTREQUEST_0_SHIPTOCITY' => substr($shipping_address['locality'], 0, 40),
        'PAYMENTREQUEST_0_SHIPTOSTATE' => substr($shipping_address['administrative_area'], 0, 40),
        'PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE' => substr($shipping_address['country'], 0, 2),
        'PAYMENTREQUEST_0_SHIPTOZIP' => substr($shipping_address['postal_code'], 0, 20),
      );

      // For the Mark flow, do not prompt for an address at PayPal unless the
      // payment method explicitly asks to.
      if ($payment_method['settings']['shipping_prompt'] != '2' && $flow == 'mark') {
        $nvp += array(
          'NOSHIPPING' => '1',
          'ADDROVERRIDE' => '1',
        );
      }
      else {
        $nvp += array(
          'NOSHIPPING' => '0',
          'ADDROVERRIDE' => '0',
        );
      }
    }
    else {

      // Allow the customer to specify a shipping address at PayPal if enabled.
      if ($payment_method['settings']['shipping_prompt'] != '0') {
        $nvp['NOSHIPPING'] = '0';
      }
      else {
        $nvp['NOSHIPPING'] = '1';
      }
    }
  }
  else {

    // Otherwise disable shipping options entirely.
    $nvp['NOSHIPPING'] = '1';
  }

  // Submit the SetExpressCheckout API request to PayPal.
  $response = commerce_paypal_api_request($payment_method, $nvp, $order);

  // If the request is successful, return the token.
  if (in_array($response['ACK'], array(
    'SuccessWithWarning',
    'Success',
  ))) {
    return $response['TOKEN'];
  }

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

/**
 * Confirm an Express Checkout payment for an order for the specified charge
 * amount with a DoExpressCheckoutPayment API request.
 *
 * @param $payment_method
 *   The PayPal Express Checkout payment method instance whose settings should
 *   be used to submit the request.
 * @param $order
 *   The order the payment is for.
 * @param $charge
 *   A price field value array representing the charge amount and currency.
 *
 * @return
 *   Boolean indicating the success or failure of the payment request.
 */
function commerce_paypal_ec_do_payment($payment_method, $order, $charge) {

  // 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
  // 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($charge['currency_code'], array_keys(commerce_paypal_currencies('paypal_ec')))) {
    $currency_code = $charge['currency_code'];
  }

  // Convert the charge amount to the specified currency.
  $amount = commerce_currency_convert($charge['amount'], $charge['currency_code'], $currency_code);
  $nvp = array(
    'METHOD' => 'DoExpressCheckoutPayment',
    'TOKEN' => $order->data['commerce_paypal_ec']['token'],
    'PAYERID' => $order->data['commerce_paypal_ec']['payerid'],
    'BUTTONSOURCE' => $payment_method['buttonsource'],
    'PAYMENTREQUEST_0_AMT' => commerce_paypal_price_amount($amount, $currency_code),
    'PAYMENTREQUEST_0_CURRENCYCODE' => $currency_code,
    'PAYMENTREQUEST_0_INVNUM' => commerce_paypal_ipn_invoice($order),
    'PAYMENTREQUEST_0_PAYMENTACTION' => commerce_paypal_payment_action($payment_method['settings']['txn_type']),
    'PAYMENTREQUEST_0_NOTIFYURL' => commerce_paypal_ipn_url($payment_method['instance_id']),
  );

  // Add itemized information to the API request.
  $nvp += commerce_paypal_ec_itemize_order($order, $currency_code);

  // Submit the request to PayPal.
  $response = commerce_paypal_api_request($payment_method, $nvp, $order);

  // Prepare a transaction object to log the API response.
  $transaction = commerce_payment_transaction_new('paypal_ec', $order->order_id);
  $transaction->instance_id = $payment_method['instance_id'];
  $transaction->amount = $amount;
  $transaction->currency_code = $currency_code;
  $transaction->payload[REQUEST_TIME] = $response;

  // If available, set the remote status and transaction ID.
  $key_map = array(
    'remote_status' => 'PAYMENTINFO_0_PAYMENTSTATUS',
    'remote_id' => 'PAYMENTINFO_0_TRANSACTIONID',
  );
  foreach ($key_map as $key => $response_key) {
    if (!empty($response[$response_key])) {
      $transaction->{$key} = $response[$response_key];
    }
  }

  // Store the transaction ID as the parent transaction ID in case subsequent
  // API operations alter this transaction's remote ID.
  if (!empty($transaction->remote_id)) {
    $transaction->data['commerce_paypal_ec']['parenttransactionid'] = $transaction->remote_id;
  }

  // Check if there is an error code in the response.
  if (!empty($response['L_ERRORCODE0'])) {

    // Log the error in a payment transaction.
    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
    $transaction->remote_status = '';
    $message = array();
    $message[] = '<strong>' . t('Payment failed') . '</strong>';
    $message[] = t('Error @code: @message', array(
      '@code' => $response['L_ERRORCODE0'],
      '@message' => $response['L_SHORTMESSAGE0'],
    ));
    $transaction->message = implode('<br />', $message);
    commerce_payment_transaction_save($transaction);

    // If the response error indicates a funding failure, redirect the customer
    // back to PayPal for another attempt.
    // @see: https://developer.paypal.com/docs/classic/express-checkout/ht_ec_fundingfailure10486/
    if ($response['L_ERRORCODE0'] === '10486') {

      // Log the error in watchdog.
      watchdog('commerce_paypal_ec', 'PayPal Express Checkout transaction funding failed for order @order_number. Redirecting the user back to PayPal.', array(
        '@order_number' => $order->order_number,
      ), WATCHDOG_NOTICE);

      // Update order.
      commerce_order_status_update($order, 'checkout_payment', FALSE, NULL, t('Customer payment transaction funding failed. Redirecting customer back to the PayPal Express Checkout page.'));

      // Redirect the user back to PayPal.
      drupal_goto(commerce_paypal_ec_checkout_url($payment_method['settings']['server'], $order->data['commerce_paypal_ec']['token']));
    }

    // Log the error in watchdog.
    watchdog('commerce_paypal_ec', 'PayPal Express Checkout transaction failed for order @order_number.', array(
      '@order_number' => $order->order_number,
    ), WATCHDOG_ERROR);
    return FALSE;
  }

  // If we received an unknown response status...
  if (!isset($response['PAYMENTINFO_0_PAYMENTSTATUS']) || !in_array($response['PAYMENTINFO_0_PAYMENTSTATUS'], array(
    'Failed',
    'Voided',
    'Pending',
    'Completed',
    'Refunded',
  ))) {

    // Display an error message and remain on the same page.
    drupal_set_message(t('We could not complete your payment with PayPal. Please try again or contact us if the problem persists.'), 'error');

    // Log the error in a payment transaction and watchdog.
    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
    $transaction->remote_status = '';
    $transaction->message = t('Payment failed with unknown status.');
    commerce_payment_transaction_save($transaction);
    watchdog('commerce_paypal_ec', 'PayPal Express Checkout transaction failed for order @order_number.', array(
      '@order_number' => $order->order_number,
    ), WATCHDOG_ERROR);
    return FALSE;
  }

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

  // If we didn't get an approval response code...
  switch ($response['PAYMENTINFO_0_PAYMENTSTATUS']) {
    case 'Failed':
      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
      $message[] = '<strong>' . t('Payment failed') . '</strong>';
      $message[] = t('Error @code: @message', array(
        '@code' => $response['PAYMENTINFO_0_ERRORCODE'],
        '@message' => $response['PAYMENTINFO_0_SHORTMESSAGE'],
      ));
      break;
    case 'Voided':
      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
      $message[] = '<strong>' . t('Authorization voided') . '</strong>';
      break;
    case 'Pending':
      $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
      $transaction->data['commerce_paypal_ec']['paymenttype'] = $response['PAYMENTINFO_0_PAYMENTTYPE'];
      $message[] = '<strong>' . t('Payment pending at PayPal') . '</strong>';
      $message[] = commerce_paypal_short_pending_reason($response['PAYMENTINFO_0_PENDINGREASON']);
      break;
    case 'Completed':
      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
      $message[] = t('Payment completed successfully');
      break;
    case 'Refunded':
      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
      $message[] = t('Refund for transaction @txn_id', array(
        '@txn_id' => $response['PAYMENTINFO_0_TRANSACTIONID'],
      ));
      break;
  }

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

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

  // If the payment failed, display an error and rebuild the form.
  if (!in_array($response['PAYMENTINFO_0_PAYMENTSTATUS'], array(
    'Refunded',
    'Completed',
    'Pending',
  ))) {
    drupal_set_message(t('We encountered an error processing your payment with PayPal. Please try again or contact us for assistance.'), 'error');
    return FALSE;
  }
  return TRUE;
}

/**
 * Returns a name-value pair array of information to the API request.
 *
 * @param object $order
 *   The order to itemized
 * @param string $currency_code
 *   The currency to return the different amounts.
 *
 * @return array
 *   A name-value pair array.
 */
function commerce_paypal_ec_itemize_order($order, $currency_code) {
  $nvp = array();

  // 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 to determine the total item
  // amount and shipping amount.
  $i = 0;
  $shippingamt = 0;
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {

    // If the current line item is a shipping line item, track its amount value
    // without taxes separately from products.
    if (module_exists('commerce_shipping') && $line_item_wrapper->type
      ->value() == 'shipping') {

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

      // Track the total costs of all shipping line items on the order to add to
      // the final payment request as a single amount, though typically this
      // should be limited to one shipping line item per order.
      $shippingamt += $shipping_price['amount'];
    }
    else {

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

      // Convert the unit price to the propery currency.
      $l_amt = commerce_currency_convert($unit_price['amount'], $unit_price['currency_code'], $currency_code);

      // Add payment details line items.
      $nvp += array(
        'L_PAYMENTREQUEST_0_NAME' . $i => commerce_line_item_title($line_item_wrapper
          ->value()),
        'L_PAYMENTREQUEST_0_AMT' . $i => commerce_paypal_price_amount($l_amt, $currency_code),
        'L_PAYMENTREQUEST_0_QTY' . $i => round($line_item_wrapper->quantity
          ->value()),
        'L_PAYMENTREQUEST_0_NUMBER' . $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 the total item and tax amounts to the payment request
  $nvp += array(
    'PAYMENTREQUEST_0_ITEMAMT' => commerce_paypal_price_amount($amt - $taxamt - $shippingamt, $currency_code),
    'PAYMENTREQUEST_0_TAXAMT' => commerce_paypal_price_amount($taxamt, $currency_code),
  );
  if ($shippingamt > 0) {
    $nvp['PAYMENTREQUEST_0_SHIPPINGAMT'] = commerce_paypal_price_amount($shippingamt, $currency_code);
  }
  return $nvp;
}

/**
 * Creates or updates a customer profile for an order based on information
 * obtained from PayPal after an Express Checkout.
 *
 * @param $order
 *   The order that was paid via Express Checkout.
 * @param $profile_type
 *   The type of the customer profile that should be created or updated.
 * @param $response
 *   The response array from a GetExpressCheckoutDetails API request.
 * @param $prefix
 *   The prefix for keys in the response array that will be used to populate the
 *   customer profile.
 * @param $skip_save
 *   Boolean indicating whether or not this function should skip saving the
 *   order after setting it to reference the newly created customer profile;
 *   defaults to TRUE, requiring the caller to save the order.
 */
function commerce_paypal_ec_customer_profile($order, $profile_type, $response, $prefix, $skip_save = TRUE) {

  // First check if the order already references a customer profile of the
  // specified type.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $field_name = variable_get('commerce_customer_profile_' . $profile_type . '_field', '');

  // If the associated order field has been set and the order currently
  // references a customer profile through it...
  if (!empty($field_name) && !empty($order_wrapper->{$field_name})) {

    // Update the existing customer profile.
    $profile = $order_wrapper->{$field_name}
      ->value();
  }
  elseif (!empty($order->data['profiles']['customer_profile_' . $profile_type])) {

    // Otherwise look for an association stored in the order's data array.
    $profile = commerce_customer_profile_load($order->data['profiles']['customer_profile_' . $profile_type]);
  }

  // Create a new profile if we could not find an existing one.
  if (empty($profile)) {
    $profile = commerce_customer_profile_new($profile_type, $order->uid);
  }

  // Add the order context to the profile to ensure it can be updated without
  // resulting in customer profile duplication.
  $profile->entity_context = array(
    'entity_type' => 'commerce_order',
    'entity_id' => $order->order_id,
  );

  // Prepare an addressfield array to set to the customer profile.
  $field = field_info_field('commerce_customer_address');
  $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', $profile_type);
  $address = addressfield_default_values($field, $instance);

  // Use the first name and last name if the profile is a billing profile.
  if ($profile_type == 'billing') {
    $address['first_name'] = $response['FIRSTNAME'];
    $address['last_name'] = $response['LASTNAME'];
    $address['name_line'] = $address['first_name'] . ' ' . $address['last_name'];
    $address['country'] = $response['COUNTRYCODE'];
  }
  elseif ($profile_type == 'shipping') {

    // Map addressfield value keys to keys available in the response array.
    $key_map = array(
      'country' => 'SHIPTOCOUNTRYCODE',
      'name_line' => 'SHIPTONAME',
      'first_name' => NULL,
      'last_name' => NULL,
      'organisation_name' => NULL,
      'administrative_area' => 'SHIPTOSTATE',
      'sub_administrative_area' => NULL,
      'locality' => 'SHIPTOCITY',
      'dependent_locality' => NULL,
      'postal_code' => 'SHIPTOZIP',
      'thoroughfare' => 'SHIPTOSTREET',
      'premise' => 'SHIPTOSTREET2',
      'sub_premise' => NULL,
      'data' => NULL,
    );

    // Loop over the addressfield value array looking for values in the response
    // array that match parts of the address to update.
    foreach ($address as $key => &$value) {

      // If there is no correlation for the current field key, skip it.
      if (empty($key_map[$key])) {
        continue;
      }

      // Update the addressfield value array with the value from the response
      // array. Note that we will erase existing data if it isn't present in the
      // response array.
      $response_key = $prefix . $key_map[$key];
      if (empty($response[$response_key])) {
        $value = '';
      }
      else {
        $value = $response[$response_key];
      }
    }
  }

  // Add the addressfield value to the customer profile.
  $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile);
  $profile_wrapper->commerce_customer_address = $address;

  // Save the customer profile and update the order to reference it.
  $profile_wrapper
    ->save();
  $order_wrapper->{'commerce_customer_' . $profile_type} = $profile_wrapper;

  // Save the order if specified.
  if (!$skip_save) {
    $order_wrapper
      ->save();
  }
}

/**
 * Returns the URL to the specified PayPal EC checkout page.
 *
 * @param $server
 *   Either sandbox or live indicating which server to get the URL for.
 * @param $token
 *   The token retrieved from the SetExpressCheckout API call.
 *
 * @return
 *   The URL to use to submit requests to the PayPal WPP server.
 */
function commerce_paypal_ec_checkout_url($server, $token) {
  switch ($server) {
    case 'sandbox':
      return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=' . $token;
    case 'live':
      return 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=' . $token;
  }
}

/**
 * Returns whether or not Express Checkout is enabled, in general or for
 * a specific order.
 *
 * @param $order
 *    The order that needs to be checked.
 *
 * @todo Rework this function to support any number of Express Checkout rules.
 */
function commerce_paypal_ec_enabled($order = NULL) {

  // If an order is passed in, then pass it through Rules to only return TRUE if
  // the Express Checkout rule is available for the order.
  if (!empty($order)) {
    $order->payment_methods = array();
    rules_invoke_all('commerce_payment_methods', $order);

    // Sort the payment methods array by the enabling Rules' weight values.
    uasort($order->payment_methods, 'drupal_sort_weight');
    return in_array('paypal_ec|commerce_payment_paypal_ec', array_keys($order->payment_methods));
  }

  // Otherwise sipmly load the default PayPal EC rule and see if it's enabled.
  $rule = rules_config_load('commerce_payment_paypal_ec');
  $enabled = !empty($rule) && $rule->active;
  if (!empty($order) && $enabled) {
    $enabled = !empty($order->data['payment_method']) && $order->data['payment_method'] == 'paypal_ec|commerce_payment_paypal_ec';
  }
  return $enabled;
}

/**
 * Returns an array of enabled payment method rules that enable payment via
 * Express Checkout.
 *
 * @return
 *   An associative array where the key and value are a matching Rule name and
 *   label for payment method rules that enable payment via Express Checkout.
 */
function commerce_paypal_ec_enabled_rules($enabled = FALSE) {
  $rules = array();

  // Loop over all of the enabled payment method rules.
  $event = rules_get_cache('event_commerce_payment_methods');
  if (!empty($event)) {
    foreach ($event as $rule) {

      // Look in all of the Rule's actions for a payment method settings array
      // that enables Express Checkout.
      foreach ($rule
        ->actions() as $action) {
        if (!empty($action->settings['payment_method']['method_id']) && $action->settings['payment_method']['method_id'] == 'paypal_ec') {

          // Add the present rule to the return value.
          $rules[$rule->name] = $rule->label;
        }
      }
    }
  }
  return $rules;
}

/**
 * Returns the URL to an Express Checkout button.
 *
 * @todo Allow for localization by changing en_US to a supported locale code.
 */
function commerce_paypal_ec_button_url() {
  return 'https://www.paypalobjects.com/en_US/i/btn/btn_xpressCheckout.gif';
}

/**
 * Returns the URL to a PayPal Credit button.
 *
 * @todo Allow for localization by changing en_US to a supported locale code.
 */
function commerce_paypal_ec_bml_button_url() {
  return 'https://www.paypalobjects.com/webstatic/en_US/btn/btn_bml_SM.png';
}

/**
 * Returns the URL to a remote PayPal mark.
 *
 * @todo Determine if we shouldn't actually be using a remote URL like
 * https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_37x23.jpg.
 * The logo there isn't actually 37x23 pixels and doesn't look like any other
 * PayPal logo.
 *
 * @see https://www.paypal.com/webapps/mpp/logo-center
 */
function commerce_paypal_ec_mark_url() {
  return file_create_url(drupal_get_path('module', 'commerce_paypal_ec') . '/images/paypal-ec-logo.gif');
}

/**
 * Implements hook_theme().
 */
function commerce_paypal_ec_theme() {
  return array(
    'paypal_ec_mark_image' => array(),
  );
}

/**
 * Returns a themed PayPal mark image.
 */
function theme_paypal_ec_mark_image() {
  $variables = array(
    'path' => commerce_paypal_ec_mark_url(),
    'title' => t('Pay with PayPal'),
    'alt' => t('Pay with PayPal'),
    'attributes' => array(
      'class' => array(
        'commerce-paypal-ec-icon',
      ),
    ),
  );
  return '<span class="commerce-paypal-ec-icon-wrapper">' . theme('image', $variables) . '</span>';
}

/**
 * Returns the checkout pane IDs of checkout panes that should be embedded in
 * the Express Checkout review and confirm page.
 */
function commerce_paypal_ec_embedded_checkout_panes() {
  return array_filter(variable_get('commerce_paypal_ec_review_embedded_panes', array()));
}

/**
 * Implements hook_block_info().
 */
function commerce_paypal_ec_block_info() {
  $blocks = array();

  // @todo Do not hard code the payment method instance ID.
  $payment_method = commerce_payment_method_instance_load('paypal_ec|commerce_payment_paypal_ec');

  // Only define the PayPal banners program block if PayPal Credit is enabled.
  if (isset($payment_method['settings']['enable_bml']) && $payment_method['settings']['enable_bml'] == TRUE) {
    $blocks['commerce_paypal_ec_banners'] = array(
      'info' => t('PayPal Credit banner'),
      'cache' => DRUPAL_CACHE_GLOBAL,
    );
  }
  return $blocks;
}

/**
 * Returns the default settings for the PayPal EC payment method.
 */
function commerce_paypal_ec_banners_default_settings() {
  return array(
    'banner_agreement' => TRUE,
    'api_email' => '',
    'banner_size' => '120x240',
    'server' => 'live',
  );
}

/**
 * Implements hook_block_configure().
 */
function commerce_paypal_ec_block_configure($delta = '') {
  $form = array();
  if ($delta == 'commerce_paypal_ec_banners') {

    // Load the PayPal banners settings.
    $settings = (array) variable_get('commerce_paypal_ec_banners_settings', array()) + commerce_paypal_ec_banners_default_settings();
    $form['paypal_ec_banners'] = array(
      '#type' => 'fieldset',
      '#title' => t('PayPal Credit¹ Banners program settings'),
      '#description' => commerce_paypal_ec_banners_help_text(),
      '#collapsible' => FALSE,
      '#collapsed' => FALSE,
    );
    $form['paypal_ec_banners']['settings'] = array(
      '#tree' => TRUE,
    );

    // API Email.
    $form['paypal_ec_banners']['settings']['api_email'] = array(
      '#type' => 'textfield',
      '#title' => t('PayPal account e-mail address'),
      '#description' => t('Enter the same e-mail address used in your Express Checkout configuration.'),
      '#default_value' => $settings['api_email'],
    );
    $form['paypal_ec_banners']['settings']['banner_size'] = array(
      '#type' => 'select',
      '#title' => t('Banner size'),
      '#description' => t('Select the image size of the banner you want to display in this block.'),
      '#options' => array(
        t('Small') => array(
          '120x90' => '120 x 90',
          '150x100' => '150 x 100',
          '170x100' => '170 x 100',
          '190x100' => '190 x 100',
          '234x60' => '234 x 60',
        ),
        t('Medium') => array(
          '120x240' => '120 x 240',
          '250x250' => '250 x 250',
          '468x60' => '468 x 60',
          '728x90' => '728 x 90',
          '800x66' => '800 x 66',
        ),
        t('Large') => array(
          '120x600' => '120 x 600',
          '234x400' => '234 x 400',
          '280x280' => '280 x 280',
          '300x250' => '300 x 250',
          '336x280' => '336 x 280',
          '540x200' => '540 x 200',
        ),
      ),
      '#default_value' => $settings['banner_size'],
    );
  }
  $form['#attached']['css'][] = drupal_get_path('module', 'commerce_paypal_ec') . '/theme/commerce_paypal_ec.theme.css';
  return $form;
}

/**
 * Returns the PayPal banners program help text.
 */
function commerce_paypal_ec_banners_help_text() {
  return '<p>' . t('Give your sales a boost when you advertise financing!') . '</p><p>' . t('PayPal Credit¹ helps turn browsers into buyers with special financing from PayPal Credit. Your customers can pay over time, while you get paid up front - at no additional cost to you.') . '</p><p>' . t("Use PayPal Credit's free banners that let you advertise PayPal Credit financing as a payment option when your customers check out with PayPal. When they know they can pay over time for their purchases, shoppers are more likely to make more purchases: 53% of PayPal Credit users say they would not have made all their purchases if PayPal Credit weren't available.²") . '</p><p><small>' . t('¹Subject to consumer credit approval.') . '</small><br /><small>' . t("²As reported in Nielsen's PayPal Credit Average Order Value study for activity occurring from April 2015 to March 2016 (small merchants) and October 2015 to March 2016 (midsize merchants), which compared PayPal Credit transactions to credit and debit card transactions on websites that offer PayPal Credit as a payment option or within the PayPal Wallet. Nielsen measured 284,890 transactions across 27 mid and small merchants. Copyright Nielsen 2016.") . '</small></p>';
}

/**
 * Implements hook_block_save().
 *
 * Saves the settings filled in and request PayPal's API to get a payerId and a
 * publisherId.
 */
function commerce_paypal_ec_block_save($delta = '', $edit = array()) {
  if ($delta == 'commerce_paypal_ec_banners') {
    if (!empty($edit['settings'])) {

      // Clean the entered values.
      $edit['settings'] = array_map('trim', $edit['settings']);

      /**
      * Using the staging server is for now unavailable, so we simply default
      * to using the live key and secret.
      *
            if ($edit['settings']['server'] == 'live') {
       $edit['settings']['api_key'] = PAYPAL_BANNER_API_KEY;
       $edit['settings']['api_secret'] = PAYPAL_BANNER_API_SECRET;
            }
            else {
       $edit['settings']['api_key'] = PAYPAL_BANNER_API_KEY_STAGING;
       $edit['settings']['api_secret'] = PAYPAL_BANNER_API_SECRET_STAGING;
            }
      */
      $edit['settings']['api_key'] = PAYPAL_BANNER_API_KEY;
      $edit['settings']['api_secret'] = PAYPAL_BANNER_API_SECRET;

      // Gets the payerId and the publisherId.
      if (($banner_account = commerce_paypal_ec_request_banner_account($edit['settings'])) && (isset($banner_account['payerId']) && isset($banner_account['publisherId']))) {
        $edit['settings']['payerId'] = $banner_account['payerId'];
        $edit['settings']['publisherId'] = $banner_account['publisherId'];
      }
      variable_set('commerce_paypal_ec_banners_settings', $edit['settings']);
    }
  }
}

/**
 * Implements hook_block_view().
 *
 * Adds PayPal's banners script into the block to display the ad.
 */
function commerce_paypal_ec_block_view($delta = '') {
  $block = array();
  if ($delta == 'commerce_paypal_ec_banners') {

    // Get the PayPal Banners settings.
    $settings = (array) variable_get('commerce_paypal_ec_banners_settings', array()) + commerce_paypal_ec_banners_default_settings();
    if (!empty($settings['publisherId'])) {
      $script = '<script type="text/javascript" data-pp-pubid="' . $settings['publisherId'] . '" data-pp-placementtype="' . $settings['banner_size'] . '">
          (function (d, t) {
            "use strict";
            var s = d.getElementsByTagName(t)[0], n = d.createElement(t);
            n.src = "//paypal.adtag.where.com/merchant.js";
            s.parentNode.insertBefore(n, s);
          }(document, "script"));
        </script>';
      $block['content'] = array(
        '#markup' => $script,
        '#prefix' => '<span class="commerce-paypal-banner-wrapper">',
        '#suffix' => '</span>',
      );
    }
  }
  return $block;
}

/*
 * Requests a banner account to PayPal.
 */
function commerce_paypal_ec_request_banner_account($settings) {
  $settings = (array) variable_get('commerce_paypal_ec_banners_settings', array()) + commerce_paypal_ec_banners_default_settings();
  $data = array(
    'bnCode' => PAYPAL_BANNER_BN_CODE,
    'emailAddress' => $settings['api_email'],
  );

  // A timestamp value in milliseconds since Epoch.
  $timestamp = round(microtime(TRUE) * 1000);
  $token = sha1($settings['api_secret'] . $timestamp);

  // @todo Replace with drupal_http_request().
  $headers = array(
    'Authorization: FPA ' . $settings['api_key'] . ':' . $token . ':' . $timestamp,
    'Content-Type: application/json',
    'Accept: application/json',
  );
  $ch = curl_init(commerce_paypal_ec_banner_url($settings['server']));
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  $response = curl_exec($ch);
  curl_close($ch);
  if (($result = drupal_json_decode($response)) && is_array($result)) {
    if (isset($result['errors'])) {
      watchdog('commerce_paypal_ec', 'The server was not able to get the banner account. The request returned from PayPal with the following data: !data', array(
        '!data' => '<pre>' . check_plain(print_r($result, TRUE)) . '</pre>',
      ), WATCHDOG_ERROR);
      drupal_set_message(t('An error occurred during account validation. Double check the e-mail address you entered and try again.'), 'error');
    }
    return $result;
  }
  watchdog('commerce_paypal_ec', 'The PayPal banners server could not be reached.', WATCHDOG_ERROR);
  drupal_set_message(t('The PayPal banners server could not be reached. Please, try again.'), 'error');
  return $response;
}

/**
 * Returns the URL to the specified PayPal Banner server.
 *
 * @param string $server
 *   Either sandbox or live indicating which server to get the URL for.
 *
 * @return string
 *   The URL to use to submit requests to the PayPal Banner server.
 */
function commerce_paypal_ec_banner_url($server) {
  switch ($server) {
    case 'sandbox':
      return 'https://api.financing-mint.paypal.com/finapi/v1/publishers/';
    case 'live':
      return 'https://api.financing.paypal.com/finapi/v1/publishers/';
  }
}

Functions

Namesort descending Description
commerce_paypal_bml_order_form Displays a PayPal Credit button as a form that redirects to PayPal.
commerce_paypal_ec_banners_default_settings Returns the default settings for the PayPal EC payment method.
commerce_paypal_ec_banners_help_text Returns the PayPal banners program help text.
commerce_paypal_ec_banner_url Returns the URL to the specified PayPal Banner server.
commerce_paypal_ec_block_configure Implements hook_block_configure().
commerce_paypal_ec_block_info Implements hook_block_info().
commerce_paypal_ec_block_save Implements hook_block_save().
commerce_paypal_ec_block_view Implements hook_block_view().
commerce_paypal_ec_bml_button_url Returns the URL to a PayPal Credit button.
commerce_paypal_ec_bml_help_text Returns the PayPal Credit help text HTML.
commerce_paypal_ec_button_url Returns the URL to an Express Checkout button.
commerce_paypal_ec_capture_void_access Determines access to the prior authorization capture form or void form for Paypal EC credit card transactions.
commerce_paypal_ec_checkout_url Returns the URL to the specified PayPal EC checkout page.
commerce_paypal_ec_commerce_checkout_page_info Implements hook_commerce_checkout_page_info().
commerce_paypal_ec_commerce_checkout_pane_info Implements hook_commerce_checkout_pane_info().
commerce_paypal_ec_commerce_checkout_router Implements hook_commerce_checkout_router().
commerce_paypal_ec_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_paypal_ec_customer_profile Creates or updates a customer profile for an order based on information obtained from PayPal after an Express Checkout.
commerce_paypal_ec_default_settings Returns the default settings for the PayPal EC payment method.
commerce_paypal_ec_do_payment Confirm an Express Checkout payment for an order for the specified charge amount with a DoExpressCheckoutPayment API request.
commerce_paypal_ec_embedded_checkout_panes Returns the checkout pane IDs of checkout panes that should be embedded in the Express Checkout review and confirm page.
commerce_paypal_ec_enabled Returns whether or not Express Checkout is enabled, in general or for a specific order.
commerce_paypal_ec_enabled_rules Returns an array of enabled payment method rules that enable payment via Express Checkout.
commerce_paypal_ec_form_alter Implements hook_form_alter().
commerce_paypal_ec_itemize_order Returns a name-value pair array of information to the API request.
commerce_paypal_ec_mark_url Returns the URL to a remote PayPal mark.
commerce_paypal_ec_menu Implements hook_menu().
commerce_paypal_ec_order_form Displays an Express Checkout button as a form that redirects to PayPal.
commerce_paypal_ec_order_form_submit Submit handler: redirect to PayPal Express Checkout.
commerce_paypal_ec_order_form_validate Validate handler: ensures PayPal Express Checkout has been configured.
commerce_paypal_ec_page_alter Implements hook_page_alter().
commerce_paypal_ec_paypal_ipn_process Payment method callback: process an IPN once it's been validated.
commerce_paypal_ec_paypal_ipn_validate Payment method callback: validate an IPN based on receiver e-mail address, price, and other parameters as possible.
commerce_paypal_ec_redirect_form Payment method callback: redirect form.
commerce_paypal_ec_redirect_form_back Payment method callback: redirect form back callback.
commerce_paypal_ec_redirect_form_submit Payment method callback: redirect form return submission.
commerce_paypal_ec_redirect_form_validate Payment method callback: redirect form return validation.
commerce_paypal_ec_refund_access Determines access to the refund form for Paypal EC credit card transactions.
commerce_paypal_ec_request_banner_account
commerce_paypal_ec_service_description Returns a service description and registration link for the specified method.
commerce_paypal_ec_settings_form Payment method callback: settings form.
commerce_paypal_ec_set_express_checkout Submits a SetExpressCheckout request to PayPal for the given order.
commerce_paypal_ec_submit_form Payment method callback: adds a message to the submission form if enabled in the payment method settings.
commerce_paypal_ec_submit_form_submit Payment method callback: submit form submission.
commerce_paypal_ec_submit_form_validate Payment method callback: submit form validation.
commerce_paypal_ec_theme Implements hook_theme().
theme_paypal_ec_mark_image Returns a themed PayPal mark image.

Constants