diff --git a/commerce_paypal.module b/commerce_paypal.module
index e465c48..39cb115 100644
--- a/commerce_paypal.module
+++ b/commerce_paypal.module
@@ -406,3 +406,133 @@ function commerce_paypal_icons() {
 
   return $icons;
 }
+
+/**
+ * Submits a PayPal API request to PayPal.
+ *
+ * @param $payment_method
+ *   The payment method instance array associated with this API request.
+ * @param $nvp
+ *   The set of name-value pairs describing the transaction to submit.
+ */
+function commerce_paypal_request($payment_method, $nvp = array(), $order = NULL) {
+  // Get the API endpoint URL for the method's transaction mode.
+  $url = commerce_paypal_server_url($payment_method['settings']['server']);
+
+  // Add the default name-value pairs to the array.
+  $nvp += array(
+    // API credentials
+    'USER' => $payment_method['settings']['api_username'],
+    'PWD' => $payment_method['settings']['api_password'],
+    'SIGNATURE' => $payment_method['settings']['api_signature'],
+    'VERSION' => '76.0',
+  );
+
+  // Allow modules to alter parameters of the API request.
+  drupal_alter('commerce_paypal_wpp_request', $nvp, $order);
+
+  // Log the request if specified.
+  if ($payment_method['settings']['log']['request'] == 'request') {
+    // Mask the credit card number and CVV.
+    $log_nvp = $nvp;
+    $log_nvp['PWD'] = str_repeat('X', strlen($log_nvp['PWD']));
+    $log_nvp['SIGNATURE'] = str_repeat('X', strlen($log_nvp['SIGNATURE']));
+
+    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_paypal', 'PayPal 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) {
+    $pairs[] = $key . '=' . urlencode($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_SSL_VERIFYPEER, 0);
+  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
+  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+  $result = curl_exec($ch);
+
+  // Log any errors to the watchdog.
+  if ($error = curl_error($ch)) {
+    watchdog('commerce_paypal', '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_paypal', 'PayPal response: !param', array('!param' => '<pre>' . check_plain(print_r($response, TRUE)) . '</pre>', WATCHDOG_DEBUG));
+  }
+
+  return $response;
+}
+
+/**
+ * Returns the URL to the specified PayPal server.
+ *
+ * @param $server
+ *   Either sandbox or live indicating which server to get the URL for.
+ *
+ * @return
+ *   The URL to use to submit requests to the PayPal server.
+ */
+function commerce_paypal_server_url($server) {
+  switch ($server) {
+    case 'sandbox':
+      return 'https://api-3t.sandbox.paypal.com/nvp';
+    case 'live':
+      return 'https://api-3t.paypal.com/nvp';
+  }
+}
+
+/**
+ * Returns the relevant PayPal payment action for a given transaction type.
+ *
+ * @param $txn_type
+ *   The type of transaction whose payment action should be returned; currently
+ *   supports COMMERCE_CREDIT_AUTH_CAPTURE and COMMERCE_CREDIT_AUTH_ONLY.
+ */
+function commerce_paypal_payment_action($txn_type) {
+  switch ($txn_type) {
+    case COMMERCE_CREDIT_AUTH_ONLY:
+      return 'Authorization';
+    case COMMERCE_CREDIT_AUTH_CAPTURE:
+      return 'Sale';
+  }
+}
+
+/**
+ * Returns the description of a transaction type for a PayPal payment action.
+ */
+function commerce_paypal_reverse_payment_action($payment_action) {
+  switch (strtoupper($payment_action)) {
+    case 'AUTHORIZATION':
+      return t('Authorization only');
+    case 'SALE':
+      return t('Authorization and capture');
+  }
+}
diff --git a/modules/ec/commerce_paypal_ec.css b/modules/ec/commerce_paypal_ec.css
new file mode 100644
index 0000000..ab26319
--- /dev/null
+++ b/modules/ec/commerce_paypal_ec.css
@@ -0,0 +1,6 @@
+#edit-paypal-ec {
+	background: none;
+	border: none;
+	margin: 0;
+	vertical-align: middle;
+}
\ No newline at end of file
diff --git a/modules/ec/commerce_paypal_ec.info b/modules/ec/commerce_paypal_ec.info
new file mode 100644
index 0000000..f87a39b
--- /dev/null
+++ b/modules/ec/commerce_paypal_ec.info
@@ -0,0 +1,9 @@
+name = PayPal Express Checkout
+description = Implements PayPal Express Checkout in Drupal Commerce checkout.
+package = Commerce (PayPal)
+dependencies[] = commerce
+dependencies[] = commerce_ui
+dependencies[] = commerce_payment
+dependencies[] = commerce_order
+dependencies[] = commerce_paypal
+core = 7.x
diff --git a/modules/ec/commerce_paypal_ec.module b/modules/ec/commerce_paypal_ec.module
new file mode 100644
index 0000000..54d20d0
--- /dev/null
+++ b/modules/ec/commerce_paypal_ec.module
@@ -0,0 +1,731 @@
+<?php
+
+/**
+ * @file
+ * Implements PayPal Express Checkout in Drupal Commerce checkout.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function commerce_paypal_ec_menu() {
+  $items['express-checkout'] = array(
+    'title' => 'Paypal Express Checkout',
+    'page callback' => 'commerce_paypal_ec_router',
+    'access arguments' => array('access checkout'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['express-checkout/%test'] = array(
+    'title' => 'Paypal Express Checkout',
+    'page callback' => 'commerce_paypal_ec_router',
+    'page arguments' => array(1),
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * 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',
+    'title' => t('PayPal Express Checkout'),
+    'short_title' => t('PayPal'),
+    'display_title' => t('PayPal Express Checkout'),
+    'description' => t('PayPal Express Checkout'),
+    'terminal' => FALSE,
+    'offsite' => TRUE,
+    'offsite_autoredirect' => TRUE,
+  );
+
+  return $payment_methods;
+}
+
+/**
+ * Returns the default settings for the PayPal WPS payment method.
+ */
+function commerce_paypal_ec_default_settings() {
+  $default_currency = variable_get('commerce_default_currency', 'USD');
+
+  return array(
+    'api_username' => '',
+    'api_password' => '',
+    'api_signature' => '',
+    'server' => 'sandbox',
+    'currency_code' => in_array($default_currency, array_keys(commerce_paypal_wpp_currencies())) ? $default_currency : 'USD',
+    'allow_supported_currencies' => FALSE,
+    'txn_type' => COMMERCE_CREDIT_AUTH_CAPTURE,
+    'log' => array('request' => 0, 'response' => 0),
+  );
+}
+
+/**
+ * Implements hook_commerce_line_item_summary_link_info().
+ */
+function commerce_paypal_ec_commerce_line_item_summary_link_info() {
+  return array(
+    'commerce_paypal_ec' => array(
+      'title' => t('Checkout with PayPal'),
+      'href' => 'express-checkout/' . drupal_get_token('express-checkout'),
+      'attributes' => array('rel' => 'nofollow'),
+      'weight' => 5,
+      'access' => user_access('access checkout'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_views_default_views_alter().
+ */
+function commerce_paypal_ec_views_default_views_alter(&$views) {
+  if (!empty($views['commerce_cart_block'])) {
+    $views['commerce_cart_block']->display['default']->display_options['footer']['line_item_summary']['links']['commerce_paypal_ec'] = 'commerce_paypal_ec';
+  }
+}
+
+/**
+ * Implements hook_commerce_checkout_page_info_alter().
+ */
+function commerce_paypal_ec_commerce_checkout_page_info_alter(&$checkout_pages) {
+  if (commerce_paypal_ec_checkout()) {
+    $checkout_pages['payment']['weight'] = -5;
+  }
+}
+
+/**
+ * Implements hook_commerce_checkout_pane_info_alter().
+ */
+function commerce_paypal_ec_commerce_checkout_pane_info_alter(&$checkout_panes) {
+  if (commerce_paypal_ec_checkout()) {
+    $disable_panes = array('account', 'customer_profile_billing', 'customer_profile_shipping');
+
+    foreach ($checkout_panes as $pane_id => &$pane) {
+      if (in_array($pane_id, $disable_panes) && !(arg(2) == 'review' && !empty($pane['review']))) {
+        $pane['page'] = 'disabled';
+        $pane['enabled'] = FALSE;
+      }
+      elseif ($pane_id == 'cart_contents') {
+        $pane['page'] = 'checkout';
+        $pane['enabled'] = TRUE;
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function commerce_paypal_ec_form_alter(&$form, &$form_state, $form_id) {
+  if (commerce_paypal_ec_enabled() && strpos($form_id, 'views_form_commerce_cart_form_') === 0) {
+    // Only add the Checkout button if the cart form View shows line items.
+    $view = reset($form_state['build_info']['args']);
+
+    if (!empty($view->result)) {
+      $form['actions']['paypal_ec'] = array(
+        '#type' => 'image_button',
+        '#value' => t('PayPal Express Checkout'),
+        '#src' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
+        '#weight' => 5,
+        '#access' => user_access('access checkout'),
+        '#submit' => array_merge($form['actions']['checkout']['#submit'], array('commerce_paypal_ec_line_item_views_form_submit')),
+        '#attached' => array('css' =>
+          array(
+            drupal_get_path('module', 'commerce_paypal_ec') . '/commerce_paypal_ec.css',
+          ),
+        ),
+      );
+    }
+  }
+}
+
+/**
+ * Submit handler used to redirect to the checkout page.
+ */
+function commerce_paypal_ec_line_item_views_form_submit($form, &$form_state) {
+  $order = $form_state['order'];
+
+  $order->data['payment_method'] = 'paypal_ec|commerce_payment_paypal_ec';
+  commerce_order_save($order);
+
+  // Redirect to the checkout page if specified.
+  if ($form_state['triggering_element']['#value'] == $form['actions']['paypal_ec']['#value']) {
+    $form_state['redirect'] = 'checkout/' . $order->order_id;
+  }
+}
+
+/**
+ * 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['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_wpp_currencies(),
+    '#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 credit card transaction type'),
+    '#description' => t('The default will be used to process transactions during checkout.'),
+    '#options' => array(
+      COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'),
+      COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual or automated capture after checkout)'),
+    ),
+    '#default_value' => $settings['txn_type'],
+  );
+  $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'],
+  );
+
+  return $form;
+}
+
+/**
+ * Extracts customer information from the Express Checkout API response
+ * and creates a customer profile from it.
+ *
+ * @param $order
+ *    The order that's being used in Express Checkout.
+ *
+ * @param $type
+ *    The type of customer profile that should be created.
+ *
+ * @param $response
+ *    The API response from the Express Checkout API.
+ */
+function commerce_paypal_ec_customer_profile($order, $type, $response, $prefix) {
+  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+  $profile = NULL;
+
+  // If the associated order field has been set...
+  if ($field_name = variable_get('commerce_customer_profile_' . $type . '_field', '')) {
+    $profile = $order_wrapper->{$field_name}->value();
+  }
+  else {
+    // Or try the association stored in the order's data array if no field is set.
+    if (!empty($order->data['profiles']['customer_profile_' . $type])) {
+      $profile = commerce_customer_profile_load($order->data['profiles']['customer_profile_' . $type]);
+    }
+  }
+
+  // Create a new profile of the specified type if it hasn't already been made.
+  if (empty($profile)) {
+    $profile = commerce_customer_profile_new($type, $order->uid);
+  }
+
+  $commerce_customer_address = addressfield_default_values();
+
+  $commerce_customer_address['first_name'] = $response['FIRSTNAME'];
+  $commerce_customer_address['last_name'] = $response['LASTNAME'];
+
+  $field_mapping = 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,
+  );
+
+  foreach ($commerce_customer_address as $field => &$value) {
+    if (empty($field_mapping[$field])) {
+      continue;
+    }
+
+    $response_field = $prefix . $field_mapping[$field];
+
+    if (!empty($response[$response_field])) {
+      $value = $response[$response_field];
+    }
+  }
+
+  // Add the address value.
+  $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile);
+
+  $profile_wrapper->commerce_customer_address = $commerce_customer_address;
+
+  // Save the profile, reference it from the order, and save the order.
+  $profile_wrapper->save();
+
+  $order_wrapper->{'commerce_customer_' . $type} = $profile_wrapper;
+
+  $order_wrapper->save();
+}
+
+/**
+ * Payment method callback: redirect form.
+ */
+function commerce_paypal_ec_redirect_form($form, &$form_state, $order, $payment_method) {
+  // Return an error if the enabling action's settings haven't been configured.
+  $required_settings = array('api_username', 'api_password', 'api_signature');
+  foreach ($required_settings as $setting) {
+    if (empty($payment_method['settings'][$setting])) {
+      drupal_set_message(t('PayPal Express Checkout is not configured for use. No PayPal !setting address has been specified.', array('!setting' => $setting)), 'error');
+
+      return array();
+    }
+  }
+
+  $wrapper = entity_metadata_wrapper('commerce_order', $order);
+
+  $order_total = $wrapper->commerce_order_total->value();
+
+  $currency_code = $order_total['currency_code'];
+  $amount = $order_total['amount'];
+
+  $tax = 0;
+
+  foreach ($order_total['data']['components'] as $component) {
+    if (substr($component['name'], 0, 3) == 'tax') {
+      //$tax += $component['price']['amount'];
+    }
+  }
+
+  // Build a name-value pair array for this transaction.
+  $nvp = array(
+    'METHOD' => 'SetExpressCheckout',
+    'PAYMENTREQUEST_0_AMT' => commerce_currency_amount_to_decimal($amount, $currency_code),
+    'PAYMENTREQUEST_0_TAXAMT' => commerce_currency_amount_to_decimal($tax, $currency_code),
+    'PAYMENTREQUEST_0_CURRENCYCODE' => $currency_code,
+    'PAYMENTREQUEST_0_PAYMENTACTION' => commerce_paypal_payment_action($payment_method['settings']['txn_type']),
+    '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)),
+  );
+
+  // Add line items to NVP.
+  $i = 0;
+
+  foreach ($wrapper->commerce_line_items as $delta => $line_item_wrapper) {
+    // If the deleted line item is a product...
+    if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) {
+      $title = $line_item_wrapper->commerce_product->title->value();
+    }
+
+    // if no defined title then take the SKU
+    if (empty($title)) {
+      $title = $line_item_wrapper->line_item_label->value();
+    }
+
+    $unit_price = $line_item_wrapper->commerce_unit_price->value();
+
+    $item_currency_code = $unit_price['currency_code'];
+    $item_amount = $unit_price['amount'];
+
+    $item_tax = 0;
+
+    foreach ($unit_price['data']['components'] as $component) {
+      if (substr($component['name'], 0, 3) == 'tax') {
+        //$item_tax += $component['price']['amount'];
+      }
+    }
+
+    $line_item = array(
+      // Item name
+      'L_PAYMENTREQUEST_0_NAME' . $i => $title,
+      // Cost of item.
+      'L_PAYMENTREQUEST_0_AMT' . $i => commerce_currency_amount_to_decimal($item_amount, $item_currency_code),
+      // Tax included in cost.
+      'L_PAYMENTREQUEST_0_TAXAMT' . $i => commerce_currency_amount_to_decimal($item_tax, $item_currency_code),
+      // Item quantity.
+      'L_PAYMENTREQUEST_0_QTY' . $i => round($line_item_wrapper->quantity->value()),
+    );
+
+    $nvp += $line_item;
+
+    $i ++;
+  }
+
+  // Submit the request to PayPal.
+  $response = commerce_paypal_request($payment_method, $nvp, $order);
+
+  $message = array();
+  $action = commerce_paypal_reverse_payment_action($nvp['PAYMENTREQUEST_0_PAYMENTACTION']);
+
+  $order->data['commerce_paypal_ec'] = array(
+    'token' => FALSE,
+    'payerid' => FALSE,
+  );
+
+  switch ($response['ACK']) {
+    case 'SuccessWithWarning':
+    case 'Success':
+      if ($response['ACK'] == 'SuccessWithWarning') {
+        $message[0] = '<b>' . t('@action - Success (with warning)', array('@action' => $action)) . '</b>';
+        $message[] = t('@severity @code: @message', array('@severity' => $response['L_SEVERITYCODE0'], '@code' => $response['L_ERRORCODE0'], '@message' => $response['L_LONGMESSAGE0']));
+      }
+      else {
+        $message[] = '<b>' . t('@action - Success', array('@action' => $action)) . '</b>';
+      }
+
+      // Add the AVS response if present.
+      if (!empty($response['AVSCODE'])) {
+        $message[] = t('AVS response: @avs', array('@avs' => commerce_paypal_avs_code_message($response['AVSCODE'])));
+      }
+
+      $order->data['commerce_paypal_ec']['token'] = $response['TOKEN'];
+
+      break;
+
+    case 'FailureWithWarning':
+    case 'Failure':
+    default:
+      $message[] = '<b>' . t('@action - Failure', array('@action' => $action)) . '</b>';
+      $message[] = t('@severity @code: @message', array('@severity' => $response['L_SEVERITYCODE0'], '@code' => $response['L_ERRORCODE0'], '@message' => $response['L_LONGMESSAGE0']));
+  }
+
+  $settings = $payment_method['settings'];
+
+  if (empty($order->data['commerce_paypal_ec']['token'])) {
+    $order->data['payment_method'] = '';
+    unset($order->data['commerce_paypal_ec']);
+
+    $form['#action'] = url('cart');
+
+    drupal_set_message(t('PayPal Express Checkout received a failed response from the API server.'), 'error');
+  }
+  else {
+    $form['#action'] = commerce_paypal_ec_checkout_url($settings['server'], $order->data['commerce_paypal_ec']['token']);
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Proceed'),
+  );
+
+  commerce_order_save($order);
+
+  return $form;
+}
+
+/**
+ * Payment method callback: redirect form return validation.
+ */
+function commerce_paypal_ec_redirect_form_validate($order, $payment_method) {
+  if (!empty($payment_method['settings']['ipn_logging']) &&
+    $payment_method['settings']['ipn_logging'] == 'full_ipn') {
+    watchdog('commerce_paypal_wps', '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;
+  }
+
+  if (empty($order->data['commerce_paypal_ec']['token'])) {
+    return FALSE;
+  }
+}
+
+/**
+ * Payment method callback: redirect form return submission.
+ */
+function commerce_paypal_ec_redirect_form_submit(&$order, $payment_method) {
+  // Build a name-value pair array for this transaction.
+  $nvp = array(
+    'METHOD' => 'GetExpressCheckoutDetails',
+    'TOKEN' => $order->data['commerce_paypal_ec']['token'],
+  );
+
+  // Submit the request to PayPal.
+  $response = commerce_paypal_request($payment_method, $nvp, $order);
+
+  $order->data['commerce_paypal_ec']['payerid'] = $response['PAYERID'];
+
+  if (empty($order->mail)) {
+    $order->mail = $response['EMAIL'];
+  }
+
+  commerce_paypal_ec_customer_profile($order, 'billing', $response, 'PAYMENTREQUEST_0_');
+
+  if (module_exists('commerce_shipping')) {
+    commerce_paypal_ec_customer_profile($order, 'shipping', $response, 'PAYMENTREQUEST_0_');
+  }
+
+  $order_status = commerce_order_status_load($order->status);
+  $checkout_page = commerce_checkout_page_load($order_status['checkout_page']);
+
+  $checkout_panes = commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id']));
+
+  // If there are no visible panes on the next page, skip it.
+  if ((count($checkout_panes) == 1) && (key($checkout_panes) == 'cart_contents')) {
+    if ($checkout_page['next_page']) {
+      // Update the order status to reflect the next checkout page.
+      $order = commerce_order_status_update($order, 'checkout_' . $checkout_page['next_page'], FALSE, NULL, t('Customer continued to the next checkout page via a submit button.'));
+    }
+  }
+
+  commerce_order_save($order);
+}
+
+/**
+ * Payment method callback: redirect form back callback.
+ */
+function commerce_paypal_ec_redirect_form_back(&$order, $payment_method) {
+  $order->data['payment_method'] = '';
+  unset($order->data['commerce_paypal_ec']);
+
+  commerce_order_save($order);
+}
+
+/**
+ * Payment method callback: submit form.
+ */
+function commerce_paypal_ec_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
+  $form = array();
+
+  // Merge in values from the order.
+  if (!empty($order->data['commerce_paypal_ec'])) {
+    $pane_values += $order->data['commerce_paypal_ec'];
+  }
+
+  $form['token'] = array(
+    '#type' => 'value',
+    '#value' => $pane_values['token'],
+  );
+
+  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()) {
+  if (empty($order->data['commerce_paypal_ec']['token']) || empty($order->data['commerce_paypal_ec']['payerid'])) {
+    drupal_set_message(t('PayPal Express Checkout should be selected before entering checkout.'), '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) {
+  if (empty($payment_method['settings'])) {
+    drupal_set_message(t('This payment method must be configured by an administrator before it can be used.'), 'error');
+    return FALSE;
+  }
+
+  // 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_wpp_currencies()))) {
+    $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'],
+    'PAYMENTREQUEST_0_AMT' => commerce_currency_amount_to_decimal($amount, $currency_code),
+    'PAYMENTREQUEST_0_CURRENCYCODE' => $currency_code,
+    'PAYMENTREQUEST_0_PAYMENTACTION' => commerce_paypal_payment_action($payment_method['settings']['txn_type']),
+  );
+
+  // Submit the request to PayPal.
+  $response = commerce_paypal_request($payment_method, $nvp, $order);
+
+  if (!in_array($response['PAYMENTINFO_0_PAYMENTSTATUS'], array('Failed', 'Voided', 'Pending', 'Completed', 'Refunded'))) {
+    drupal_set_message(t('PayPal Express Checkout received an unknown response from the API server.'), 'error');
+
+    // Provide a more descriptive error message in the failed transaction and
+    // the watchdog.
+    $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] = array();
+    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+    $transaction->message = t('The order did not contain the information required to finish through PayPal Express Checkout.');
+    commerce_payment_transaction_save($transaction);
+
+    watchdog('commerce_paypal_ec', 'A PayPal Express Checkout transaction failed because of an unknown payment status.', NULL, WATCHDOG_ERROR);
+
+    return FALSE;
+  }
+
+  // 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->remote_status = $response['PAYMENTINFO_0_PAYMENTSTATUS'];
+  $transaction->remote_id = $response['PAYMENTINFO_0_TRANSACTIONID'];
+  $transaction->amount = $amount;
+  $transaction->currency_code = $currency_code;
+  $transaction->payload[REQUEST_TIME] = $response;
+
+  // Build a meaningful response message.
+  $message = array();
+  $action = commerce_paypal_reverse_payment_action($nvp['PAYMENTREQUEST_0_PAYMENTACTION']);
+
+  // If we didn't get an approval response code...
+  switch ($response['PAYMENTINFO_0_PAYMENTSTATUS']) {
+    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($response['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' => $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.'), 'error');
+
+    return FALSE;
+  }
+}
+
+/**
+ * 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/webscr?cmd=_express-checkout&token=' . $token;
+    case 'live':
+      return 'https://www.paypal.com/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.
+ */
+function commerce_paypal_ec_enabled($order = NULL) {
+  $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 whether or not the current user is in Paypal Express Checkout.
+ */
+function commerce_paypal_ec_checkout() {
+  if (arg(0) == 'checkout' && $order_id = arg(1)) {
+    $order = commerce_order_load($order_id);
+
+    if (commerce_paypal_ec_enabled($order)) {
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+/**
+ * Page callback for Express Checkout.
+ */
+function commerce_paypal_ec_router($token = '') {
+  global $user;
+
+  if (drupal_valid_token($token, 'express-checkout') && $order = commerce_cart_order_load($user->uid)) {
+    $order->data['payment_method'] = 'paypal_ec|commerce_payment_paypal_ec';
+    commerce_order_save($order);
+
+    drupal_goto('checkout');
+  }
+  else {
+    drupal_goto('cart');
+  }
+}
diff --git a/modules/wpp/commerce_paypal_wpp.module b/modules/wpp/commerce_paypal_wpp.module
index fe5d2d8..e17dac0 100644
--- a/modules/wpp/commerce_paypal_wpp.module
+++ b/modules/wpp/commerce_paypal_wpp.module
@@ -311,7 +311,7 @@ function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pa
   // Build a name-value pair array for this transaction.
   $nvp = array(
     'METHOD' => 'DoDirectPayment',
-    'PAYMENTACTION' => commerce_paypal_wpp_payment_action($payment_method['settings']['txn_type']),
+    'PAYMENTACTION' => commerce_paypal_payment_action($payment_method['settings']['txn_type']),
     'NOTIFYURL' => commerce_paypal_ipn_url($payment_method['instance_id']),
 
     'CREDITCARDTYPE' => commerce_paypal_wpp_card_type($pane_values['credit_card']['type']),
@@ -377,7 +377,7 @@ function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pa
   );
 
   // Submit the request to PayPal.
-  $response = commerce_paypal_wpp_request($payment_method, $nvp, $order);
+  $response = commerce_paypal_request($payment_method, $nvp, $order);
 
   // Prepare a transaction object to log the API response.
   $transaction = commerce_payment_transaction_new('paypal_wpp', $order->order_id);
@@ -388,7 +388,7 @@ function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pa
 
   // Build a meaningful response message.
   $message = array();
-  $action = commerce_paypal_wpp_reverse_payment_action($nvp['PAYMENTACTION']);
+  $action = commerce_paypal_reverse_payment_action($nvp['PAYMENTACTION']);
 
   // Set the remote ID and transaction status based on the acknowledgment code.
   switch ($response['ACK']) {
@@ -453,136 +453,6 @@ function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pa
 }
 
 /**
- * Submits a PayPal WPP API request to PayPal.
- *
- * @param $payment_method
- *   The payment method instance array associated with this API request.
- * @param $nvp
- *   The set of name-value pairs describing the transaction to submit.
- */
-function commerce_paypal_wpp_request($payment_method, $nvp = array(), $order = NULL) {
-  // Get the API endpoint URL for the method's transaction mode.
-  $url = commerce_paypal_wpp_server_url($payment_method['settings']['server']);
-
-  // Add the default name-value pairs to the array.
-  $nvp += array(
-    // API credentials
-    'USER' => $payment_method['settings']['api_username'],
-    'PWD' => $payment_method['settings']['api_password'],
-    'SIGNATURE' => $payment_method['settings']['api_signature'],
-    'VERSION' => '76.0',
-  );
-
-  // Allow modules to alter parameters of the API request.
-  drupal_alter('commerce_paypal_wpp_request', $nvp, $order);
-
-  // Log the request if specified.
-  if ($payment_method['settings']['log']['request'] == 'request') {
-    // Mask the credit card number and CVV.
-    $log_nvp = $nvp;
-    $log_nvp['PWD'] = str_repeat('X', strlen($log_nvp['PWD']));
-    $log_nvp['SIGNATURE'] = str_repeat('X', strlen($log_nvp['SIGNATURE']));
-
-    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_paypal', 'PayPal WPP 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) {
-    $pairs[] = $key . '=' . urlencode($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_SSL_VERIFYPEER, 0);
-  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
-  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
-  $result = curl_exec($ch);
-
-  // Log any errors to the watchdog.
-  if ($error = curl_error($ch)) {
-    watchdog('commerce_paypal', '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_paypal', 'PayPal WPP response: !param', array('!param' => '<pre>' . check_plain(print_r($response, TRUE)) . '</pre>', WATCHDOG_DEBUG));
-  }
-
-  return $response;
-}
-
-/**
- * Returns the URL to the specified PayPal WPP server.
- *
- * @param $server
- *   Either sandbox or live indicating which server to get the URL for.
- *
- * @return
- *   The URL to use to submit requests to the PayPal WPP server.
- */
-function commerce_paypal_wpp_server_url($server) {
-  switch ($server) {
-    case 'sandbox':
-      return 'https://api-3t.sandbox.paypal.com/nvp';
-    case 'live':
-      return 'https://api-3t.paypal.com/nvp';
-  }
-}
-
-/**
- * Returns the relevant PayPal payment action for a given transaction type.
- *
- * @param $txn_type
- *   The type of transaction whose payment action should be returned; currently
- *   supports COMMERCE_CREDIT_AUTH_CAPTURE and COMMERCE_CREDIT_AUTH_ONLY.
- */
-function commerce_paypal_wpp_payment_action($txn_type) {
-  switch ($txn_type) {
-    case COMMERCE_CREDIT_AUTH_ONLY:
-      return 'Authorization';
-    case COMMERCE_CREDIT_AUTH_CAPTURE:
-      return 'Sale';
-  }
-}
-
-/**
- * Returns the description of a transaction type for a PayPal WPP payment action.
- */
-function commerce_paypal_wpp_reverse_payment_action($payment_action) {
-  switch (strtoupper($payment_action)) {
-    case 'AUTHORIZATION':
-      return t('Authorization only');
-    case 'SALE':
-      return t('Authorization and capture');
-  }
-}
-
-/**
  * Returns the value for a credit card type expected by PayPal.
  */
 function commerce_paypal_wpp_card_type($card_type) {
