diff --git a/commerce_paypal.module b/commerce_paypal.module
index 8767833..539211d 100644
--- a/commerce_paypal.module
+++ b/commerce_paypal.module
@@ -290,65 +290,132 @@ function commerce_paypal_ipn_pending_reason($pending_reason) {
  * This function may be used for any PayPal payment method that uses the same
  * settings array structure as these other payment methods and whose API
  * requests should be submitted to the same URLs as determined by the function
- * commerce_paypal_api_server_url().
+ * commerce_paypal_api_server_url(). (This is the Paypal Classic API using the
+ * name-value-pairs endpoint.)
+ *
+ * Alternatively, the $params array can contain a sub-array of settings for the
+ * PayPal REST API under the [restapi] key, which will tell this function to 
+ * switch to the REST API endpoint as specified. The rest of the $params array 
+ * will be transformed into the JSON request object for the PayPal REST API.
  *
  * @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.
+ * @param $params
+ *   Parameters for this API request.
+ *   
+ *   For Classic API, this is the set of name-value pairs describing the transaction to submit.
+ *
+ *   For REST API, this must contain a sub-array under the [restapi] key which defines the 
+ *     REST API call to be made. The rest of the params array is serialized into the JSON 
+ *     request object.
  * @param $order
  *   The order the payment request is being made for.
  *
  * @return
  *   The response array from PayPal if successful or FALSE on error.
  */
-function commerce_paypal_api_request($payment_method, $nvp = array(), $order = NULL) {
-  // Get the API endpoint URL for the payment method's transaction mode.
-  $url = commerce_paypal_api_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',
-  );
+function commerce_paypal_api_request($payment_method, $params = array(), $order = NULL) {
+
+  $rest = (isset($params['restapi']) ? $params['restapi'] : FALSE);
+  
+  // Get the API endpoint URL for the payment method's transaction mode.  
+  $url = ($rest ? commerce_paypal_rest_api_server_url($payment_method['settings']['server']) : commerce_paypal_api_server_url($payment_method['settings']['server']));
+  
+  if ($rest) {
+    // Set up our REST request based on the parameters we were sent.
+    // @todo: Figure out OAuth tokenization here.
+    $rest['token'] = commerce_paypal_rest_api_token($payment_method, $params);
+    if (!$rest['token']) {
+      $log_params = commerce_paypal_sanitize_api_params($params);
+      watchdog('commerce_paypal', 'PayPal REST API request was unable to retrieve authorization token: !param', array('!param' => '<pre>' . check_plain(print_r($log_params, TRUE)) . '</pre>'), WATCHDOG_ERROR);      
+    }
+    // Make sure we have a default method and version.
+    $rest += array(
+      'method' => 'GET',
+      'version' => 'v1',
+    );
+    // If we don't have an endpoint specified, fail out here with watchdog.
+    // For now we're relying on the requester to fill this in every time.
+    if (!isset($rest['endpoint']) || empty($rest['endpoint'])) {
+      $log_params = commerce_paypal_sanitize_api_params($params);
+      watchdog('commerce_paypal', 'PayPal REST API request had no endpoint given: !param', array('!param' => '<pre>' . check_plain(print_r($log_params, TRUE)) . '</pre>'), WATCHDOG_ERROR);
+      // @todo: Is FALSE the right thing to return here?
+      return FALSE;
+    }
+    
+    // Add version and endpoint to our URL.
+    $url = $url . '/' . $rest['version'] . '/' . $rest['endpoint'];
+  }
+  else {
+    // Add the default name-value pairs to the array.
+    $params += 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_api_request', $nvp, $order, $payment_method);
+  drupal_alter('commerce_paypal_api_request', $params, $order, $payment_method);
 
   // 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']));
+    // Sanitize the request to mask CC number, CVV, and account info.
+  
+    // This has been turned into a separate function for potential re-use and 
+    // so that REST API sanitization can be added to the same place easily.
+    $log_params = commerce_paypal_sanitize_api_params($params);
 
-    if (!empty($log_nvp['ACCT'])) {
-      $log_nvp['ACCT'] = str_repeat('X', strlen($log_nvp['ACCT']) - 4) . substr($log_nvp['ACCT'], -4);
-    }
+    watchdog('commerce_paypal', 'PayPal API request to @url: !param', array('@url' => $url, '!param' => '<pre>' . check_plain(print_r($log_params, TRUE)) . '</pre>'), WATCHDOG_DEBUG);
+  }
 
-    if (!empty($log_nvp['CVV2'])) {
-      $log_nvp['CVV2'] = str_repeat('X', strlen($log_nvp['CVV2']));
+  if ($rest) {
+    // Serialize our params into a JSON request object.
+    // @todo: Figure out if we need to unset the extraneous REST settings array.
+    unset($params['restapi']);
+  
+    $rest_json = json_encode($params);
+    $json_error = json_last_error();
+    // Make sure someone didn't pass us a bogus array to json_encode().
+    if (!$rest_json && $json_error !== JSON_ERROR_NONE) {
+      $log_params = commerce_paypal_sanitize_api_params($params);
+      watchdog('commerce_paypal', 'PayPal REST API request failed to json_encode(): !param', array('!param' => '<pre>' . check_plain(print_r($log_params, TRUE)) . '</pre>'), WATCHDOG_ERROR);
+      // @todo: Is FALSE the right thing to return here?
+      return FALSE;
     }
-
-    watchdog('commerce_paypal', 'PayPal API request to @url: !param', array('@url' => $url, '!param' => '<pre>' . check_plain(print_r($log_nvp, TRUE)) . '</pre>'), WATCHDOG_DEBUG);
   }
-
-  // Prepare the name-value pair array to be sent as a string.
-  $pairs = array();
-
-  foreach ($nvp as $key => $value) {
-    $pairs[] = $key . '=' . urlencode($value);
+  else {
+    // Prepare the name-value pair array to be sent as a string.
+    $pairs = array();
+  
+    foreach ($params as $key => $value) {
+      $pairs[] = $key . '=' . urlencode($value);
+    }
   }
 
   // Setup the cURL request.
   $ch = curl_init();
+  
+  if ($rest) {
+    // Set up the REST API info: JSON request/headers and OAuth token.
+    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $rest['method']);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, $rest_json);
+    curl_setopt($ch, CURLOPT_HEADER, 1);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+      'Content-Type: application/json', 
+      'Content-Length: ' . strlen($rest_json),
+      'Authorization: Bearer ' . $rest['token'],
+    ));
+  }
+  else {
+    curl_setopt($ch, CURLOPT_POST, 1);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $pairs));
+  }
+  
   curl_setopt($ch, CURLOPT_URL, $url);
   curl_setopt($ch, CURLOPT_VERBOSE, 0);
-  curl_setopt($ch, CURLOPT_POST, 1);
-  curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $pairs));
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
@@ -375,12 +442,30 @@ function commerce_paypal_api_request($payment_method, $nvp = array(), $order = N
   }
   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);
+  if ($rest) {
+    // We need to replicate the headers magic from drupal_http_request here.
+    // REST API callers need to expect a similar $response object due to the 
+    // need for the HTTP response code in particular.
+    $response = new stdClass();
+    list($headers, $response_body) = preg_split("/\r\n\r\n|\n\n|\r\r/", $result, 2);
+    $headers = preg_split("/\r\n|\n|\r/", $headers);    
+    list($protocol, $code, $status_message) = explode(' ', trim(array_shift($headers)), 3);
+    $response->protocol = $protocol;
+    $response->status_message = $status_message;
+    $response->headers = $headers;
+    $response->code = $code;
+    
+    // Decode the REST API response object.
+    $response->data = json_decode($response_body);
+  }
+  else {
+    // Make the response an array.
+    $response = array();
+  
+    foreach (explode('&', $result) as $params) {
+      list($key, $value) = explode('=', $params);
+      $response[urldecode($key)] = urldecode($value);
+    }
   }
 
   // Log the response if specified.
@@ -392,7 +477,109 @@ function commerce_paypal_api_request($payment_method, $nvp = array(), $order = N
 }
 
 /**
- * Returns the URL to the specified PayPal API server.
+ * Get a REST API OAuth2 token. Use a secondary call to generate one if it does not yet exist.
+ */
+function commerce_paypal_rest_api_token($payment_method, $params) {
+  $token_settings = variable_get('commerce_paypal_rest_api_token', array());
+  if (empty($token_settings) || empty($token_settings['access_token']) || $token_settings['expires'] <= (time() + 60)) {
+    // No token yet, or it is expired or close enough to refresh.
+    $token_settings = commerce_paypal_rest_api_token_fetch($payment_method, $params);
+    if (!$token_settings) {
+      // The _fetch function watchdogs its own failure so we just return FALSE here.
+      return FALSE;
+    }
+    variable_set('commerce_paypal_rest_api_token', $token_settings);
+  }
+  // By now we should have a token or we should have returned FALSE earlier.
+  return $token_settings['access_token'];
+}
+
+function commerce_paypal_rest_api_token_fetch($payment_method, $params) {
+  $url = commerce_paypal_rest_api_server_url($payment_method['settings']['server']);
+  $url .= '/v1/oauth2/token';
+  
+  $ch = curl_init();
+  curl_setopt($ch, CURLOPT_URL, $url);
+  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json'));  
+  curl_setopt($ch, CURLOPT_USERPWD, $payment_method['settings']['rest_api_clientid'] . ":" . $payment_method['settings']['rest_api_secret']);
+  curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
+  curl_setopt($ch, CURLOPT_POST, 1);
+  curl_setopt($ch, CURLOPT_VERBOSE, 0);
+  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
+  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);  
+
+  if (variable_get('commerce_paypal_cacert', FALSE)) {
+    curl_setopt($ch, CURLOPT_CAINFO, variable_get('commerce_paypal_cacert', ''));
+  }
+
+  $result = curl_exec($ch);
+  
+  // Log any errors to the watchdog.
+  if ($error = curl_error($ch)) {
+    watchdog('commerce_paypal', 'cURL error: @error', array('@error' => $error), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  curl_close($ch);
+  
+  // Give our soon-to-be-serialized-as-a-variable array a real expiration time.
+  $response = json_decode($result, TRUE);
+  $response['expires'] = $response['expires_in'] + time();
+  unset($response['expires_in']);
+  
+  return $response;
+}
+
+/**
+ * Sanitizes API request parameters for logging to watchdog.
+ *
+ * @param $params
+ *   The request parameters (either NVP or the parameters for the REST API)
+ *   which need to be sanitized to remove credit card number and CVV codes.
+ */
+function commerce_paypal_sanitize_api_params($params) {
+  if (!isset($params['restapi'])) {
+    $params['PWD'] = str_repeat('X', strlen($params['PWD']));
+    $params['SIGNATURE'] = str_repeat('X', strlen($params['SIGNATURE']));
+  
+    if (!empty($params['ACCT'])) {
+      $params['ACCT'] = str_repeat('X', strlen($params['ACCT']) - 4) . substr($params['ACCT'], -4);
+    }
+  
+    if (!empty($params['CVV2'])) {
+      $params['CVV2'] = str_repeat('X', strlen($params['CVV2']));
+    }
+  }
+  else {
+    // @todo: Sanitize other possible locations for CVV and cardnumber in the REST API params.
+    $params['number'] = str_repeat('X', strlen($params['number']) - 4) . substr($params['number'], -4);
+    
+  }
+  
+  return $params;
+}
+
+/**
+ * Returns the URL to the specified PayPal REST API 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 API server.
+ */
+function commerce_paypal_rest_api_server_url($server) {
+  switch ($server) {
+    case 'sandbox':
+      return 'https://api.sandbox.paypal.com';
+    case 'live':
+      return 'https://api.paypal.com';
+  }
+}
+
+/**
+ * Returns the URL to the specified PayPal Classic API (NVP endpoint) server.
  *
  * @param $server
  *   Either sandbox or live indicating which server to get the URL for.
diff --git a/modules/vault/commerce_paypal_vault.info b/modules/vault/commerce_paypal_vault.info
new file mode 100755
index 0000000..e8b7555
--- /dev/null
+++ b/modules/vault/commerce_paypal_vault.info
@@ -0,0 +1,11 @@
+name = PayPal Vault
+description = Implements PayPal Vault in Drupal Commerce Card On File.
+package = Commerce (PayPal)
+dependencies[] = commerce
+dependencies[] = commerce_ui
+dependencies[] = commerce_payment
+dependencies[] = commerce_order
+dependencies[] = commerce_paypal
+dependencies[] = commerce_cardonfile
+core = 7.x
+
diff --git a/modules/vault/commerce_paypal_vault.module b/modules/vault/commerce_paypal_vault.module
new file mode 100755
index 0000000..2c248d5
--- /dev/null
+++ b/modules/vault/commerce_paypal_vault.module
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * @file
+ * Implements PayPal Vault (via WPP checkout process) in Drupal Commerce Card On File.
+ */
+
+function commerce_paypal_vault_commerce_payment_method_info_alter(&$methods) {
+  // The WPP payment method is the one that accepts credit cards directly,
+  // rather than as a redirected payment method. As such, we hook into it
+  // in order to enable Card On File functionality through its payment method.
+  if (module_exists('commerce_paypal_wpp') && isset($methods['paypal_wpp'])) {
+    $methods['paypal_wpp']['cardonfile'] = array(
+      'create callback' => 'commerce_paypal_vault_cardonfile_create',
+      // @todo: This callback is implemented but it doesn't work because there 
+      // is no documentation on the PATCH method via the PayPal REST API.
+      // Argh.
+      // 'update callback' => 'commerce_paypal_vault_cardonfile_update',
+      'delete callback' => 'commerce_paypal_vault_cardonfile_delete',
+      'charge callback' => 'commerce_paypal_vault_cardonfile_charge',
+    );
+  }
+}
+
+function commerce_paypal_vault_cardonfile_create($form, $form_state, $payment_method, $card) {
+  $card_number = $form_state['values']['credit_card']['number'];
+  $card_expire_month = $form_state['values']['credit_card']['exp_month'];
+  $card_expire_year = $form_state['values']['credit_card']['exp_year'];
+  $card_expire = $card_expire_year . '-' . $card_expire_month;
+  $card_code = $form_state['values']['credit_card']['code'];
+  $card_type = $form_state['values']['credit_card']['type'];
+  $card_owner = $form_state['values']['credit_card']['owner'];
+  $name_fragments = explode(' ', $card_owner);
+  $last_name = array_pop($name_fragments);
+  $first_name = implode(' ', $name_fragments);
+ 
+  // Create the params array for the REST API request.
+  $params = array(
+    'restapi' => array(
+      'endpoint' => 'vault/credit-card',
+      'method' => 'POST',
+      'version' => 'v1',
+    ),
+    'payer_id' => 'user_' . $card->uid,
+    'type' => $card_type,
+    'number' => $card_number,
+    'expire_month' => $card_expire_month,
+    'expire_year' => $card_expire_year,
+    'first_name' => $first_name,
+    'last_name' => $last_name,
+  );
+  
+  $response = commerce_paypal_api_request($payment_method, $params);
+  
+  if (!$response) {
+    // Our request failed. Whoops.
+    // @todo: Return FALSE or $card here?
+    return $card;
+    drupal_set_message(t('This card was not able to be added. Please contact the site administrator for additional information.'), 'error');  
+  }
+  else {
+    // Op success!
+    $card->remote_id = $response->data->id;
+    $card->card_name = $card_owner;
+    return $card;
+  }
+  
+}
+
+function commerce_paypal_vault_cardonfile_delete($form, &$form_state, $payment_method, $card) {
+  // Miraculously, deleting a PayPal Vault card is really easy.
+  // None of the rigmarole from the various CC gateways.
+  $params = array(
+    'restapi' => array(
+      'method' => 'DELETE',
+      'endpoint' => 'vault/credit-card/' . $card->remote_id,
+      'version' => 'v1',
+    ),
+  );
+  
+  $response = commerce_paypal_api_request($payment_method, $params);
+  // PayPal Vault returns a 204 on a successful deletion.
+  if ($response->code == 204) {
+    return TRUE;
+  }
+  else {
+    // Something screwed up here. Watchdog the response.
+    watchdog('commerce_paypal', 'PayPal Vault card on file deletion failed: !param', array('!param' => '<pre>' . check_plain(print_r($response, TRUE)) . '</pre>'), WATCHDOG_ERROR);
+    return FALSE;
+  }
+}
+
+function commerce_paypal_vault_cardonfile_update($form, &$form_state, $payment_method, $card) {
+  // @todo: Are these still the right form values? Let's find out!
+  $card_number = $form_state['values']['credit_card']['number'];
+  $card_expire_month = $form_state['values']['credit_card']['exp_month'];
+  $card_expire_year = $form_state['values']['credit_card']['exp_year'];
+  $card_expire = $card_expire_year . '-' . $card_expire_month;
+  $card_code = $form_state['values']['credit_card']['code'];
+  $card_type = $form_state['values']['credit_card']['type'];
+  $billing_address = $form_state['values']['commerce_customer_address'][LANGUAGE_NONE][0];
+  // Modifying an existing card appears to be similarly easy.
+  // @todo: Unfortunately there is no documentation on using the PATCH method
+  // and sending the _create set of params barfs out 401 Bad Request nonsense.
+  $params = array(
+    'restapi' => array(
+      'method' => 'PATCH',
+      'endpoint' => 'vault/credit-card/' . $card->remote_id,
+      'version' => 'v1',
+    ),
+    'payer_id' => 'user_' . $card->uid,
+    'type' => $card_type,
+    'number' => $card_number,
+    'expire_month' => $card_expire_month,
+    'expire_year' => $card_expire_year,
+    'first_name' => $billing_address['first_name'],
+    'last_name' => $billing_address['last_name'],    
+  );
+  
+  $response = commerce_paypal_api_request($payment_method, $params);
+  
+  // @todo: What do we do here? Who knows! There is no documentation and all I get are 401s!
+  // Even the official SDKs don't implement this even though there is a HATEOAS link provided!
+  // Argh.
+  
+  return FALSE;
+}
+
+function commerce_paypal_vault_cardonfile_charge($payment_method, $card, $order, $charge = NULL) {
+
+  if (!isset($charge)) {
+    $wrapper = entity_metadata_wrapper('commerce_order', $order);
+    $charge = commerce_line_items_total($wrapper->commerce_line_items);
+    
+  }
+  $amount = commerce_currency_amount_to_decimal($charge['amount'], $charge['currency_code']);
+  
+  $params = array(
+    'restapi' => array(
+      'method' => 'POST',
+      'version' => 'v1',
+      'endpoint' => 'payments/payment',
+    ),
+    'intent' => ($payment_method['settings']['txn_type'] == 'auth_capture' ? 'sale' : 'authorization'),
+    'payer' => array(
+      'payment_method' => 'credit_card',
+      'funding_instruments' => array(
+        array(
+          'credit_card_token' => array(
+            'credit_card_id' => $card->remote_id,
+            'payer_id' => 'user_' . $card->uid,
+          ),
+        ),
+      ),
+    ),
+    'transactions' => array(
+      array(
+        'amount' => array(
+          'total' => $amount,
+          'currency' => $charge['currency_code'],
+        ),
+        'description' => "Order number $order->order_number",
+      ),
+    ),
+  );
+  
+  $response = commerce_paypal_api_request($payment_method, $params, $order);
+  
+  $txn = commerce_payment_transaction_new('paypal_vault', $order->order_id);
+  $txn->instance_id = $payment_method['instance_id'];
+  $txn->remote_id = $response->data->id;
+  $txn->amount = $amount;
+  $txn->currency_code = $charge['currency_code'];
+  $txn->payload[REQUEST_TIME] = $response;
+  if ($response && ($response->data->state == 'approved' || $response->data->state == 'created')) {
+    switch ($payment_method['settings']['txn_type']) {
+      case COMMERCE_CREDIT_AUTH_ONLY:
+        $txn->status = COMMERCE_PAYMENT_STATUS_PENDING;
+        break;
+      case COMMERCE_CREDIT_AUTH_CAPTURE:
+        $txn->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+        break;
+      case COMMERCE_CREDIT_CAPTURE_ONLY:
+        $txn->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+        break;    
+    }
+  }
+  else {
+    $txn->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+  }
+  $txn->remote_status = $response->data->state;
+  
+  commerce_payment_transaction_save($txn); 
+
+  // @todo: Error messages and deactivation of failed card here.
+  
+  
+  
+  return TRUE;
+  
+  
+}
+
+function commerce_paypal_vault_wpp_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
+  // First attempt to load the card on file.
+  $card = commerce_cardonfile_load($pane_values['cardonfile']);
+
+  // Fail now if it is no longer available or the card is inactive.
+  if (empty($card) || $card->status == 0) {
+    drupal_set_message(t('The requested card on file is no longer valid.'), 'error');
+    return FALSE;
+  }
+  // Yay!
+  
+  return commerce_paypal_vault_cardonfile_charge($payment_method, $card, $order, $charge);
+}
diff --git a/modules/wpp/commerce_paypal_wpp.module b/modules/wpp/commerce_paypal_wpp.module
index 1916792..29402e5 100644
--- a/modules/wpp/commerce_paypal_wpp.module
+++ b/modules/wpp/commerce_paypal_wpp.module
@@ -83,6 +83,8 @@ function commerce_paypal_wpp_default_settings() {
     'api_username' => '',
     'api_password' => '',
     'api_signature' => '',
+    'rest_api_clientid' => '',
+    'rest_api_secret' => '',
     'server' => 'sandbox',
     'code' => TRUE,
     'card_types' => drupal_map_assoc(array('visa', 'mastercard', 'amex', 'discover')),
@@ -118,6 +120,16 @@ function commerce_paypal_wpp_settings_form($settings = array()) {
     '#title' => t('Signature'),
     '#default_value' => $settings['api_signature'],
   );
+  $form['rest_api_clientid'] = array(
+    '#type' => 'textfield',
+    '#title' => t('REST API Client ID'),
+    '#default_value' => $settings['rest_api_clientid'],  
+  );
+  $form['rest_api_secret'] = array(
+    '#type' => 'textfield',
+    '#title' => t('REST API Secret (Password)'),
+    '#default_value' => $settings['rest_api_secret'],  
+  );  
   $form['server'] = array(
     '#type' => 'radios',
     '#title' => t('PayPal server'),
@@ -236,6 +248,11 @@ function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pa
     drupal_set_message(t('This payment method must be configured by an administrator before it can be used.'), 'error');
     return FALSE;
   }
+  
+  if (module_exists('commerce_cardonfile') && $payment_method['settings']['cardonfile'] &&
+    !empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') {
+    return commerce_paypal_vault_wpp_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge);
+  }  
 
   // Ensure we can determine a valid IPv4 IP address as required by PayPal WPP.
   $ip_address = ip_address();
@@ -452,6 +469,63 @@ function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pa
     drupal_set_message(t('We encountered an error processing your payment. Please verify your credit card details or try a different card.'), 'error');
     return FALSE;
   }
+  elseif (   module_exists('commerce_cardonfile') 
+          && !empty($payment_method['settings']['cardonfile']) 
+          && !empty($pane_values['credit_card']['cardonfile_store'])
+          && $pane_values['credit_card']['cardonfile_store']
+          ) {
+    // Check if we need to store a new card on file. If yes, then do so here.
+    // The card on file module's "create" callback is not invoked; we need
+    // to do this part ourselves.
+    
+    $card_number = $pane_values['credit_card']['number'];
+    $card_expire_month = $pane_values['credit_card']['exp_month'];
+    $card_expire_year = $pane_values['credit_card']['exp_year'];
+    $card_expire = $card_expire_year . '-' . $card_expire_month;
+    $card_code = $pane_values['credit_card']['code'];
+    $card_type = $pane_values['credit_card']['type'];
+    
+    $first_name = $billing_address['first_name'];
+    $last_name = $billing_address['last_name'];
+    
+    $card = commerce_cardonfile_new();
+    $card->uid = $order->uid;
+    $card->payment_method = $payment_method['method_id'];
+    $card->instance_id = $payment_method['instance_id'];
+    $card->card_type = !empty($card_type) ? $card_type : 'card';
+    $card->card_name = $first_name . ' ' . $last_name;
+    $card->card_number = substr($card_number, -4);
+    $card->status = 1;
+  
+    // Create the params array for the REST API request.
+    $params = array(
+      'restapi' => array(
+        'endpoint' => 'vault/credit-card',
+        'method' => 'POST',
+        'version' => 'v1',
+      ),
+      'payer_id' => 'user_' . $card->uid,
+      'type' => $card_type,
+      'number' => $card_number,
+      'expire_month' => $card_expire_month,
+      'expire_year' => $card_expire_year,
+      'first_name' => $first_name,
+      'last_name' => $last_name,
+    );
+    
+    $response = commerce_paypal_api_request($payment_method, $params);
+    
+    if (!$response) {
+      // Our request failed. Whoops.
+      return FALSE;
+      drupal_set_message(t('Your credit card was not able to be stored for future use. Please contact the site administrator for additional information.'), 'error');  
+    }
+    else {
+      // Op success!
+      $card->remote_id = $response->data->id;      
+      commerce_cardonfile_save($card);
+    }
+  }
 }
 
 /**
