You are here

commerce_cardonfile.module in Commerce Card on File 7.2

Same filename and directory in other branches
  1. 7 commerce_cardonfile.module

Supports card on file functionality for credit card payment methods by associating card data reference IDs from payment gateways with user accounts.

File

commerce_cardonfile.module
View source
<?php

/**
 * @file
 * Supports card on file functionality for credit card payment methods by
 * associating card data reference IDs from payment gateways with user accounts.
 */

// -----------------------------------------------------------------------
// Constants
// Card on file process status codes
define('COMMERCE_COF_PROCESS_CODE_INSUFFICIENT_DATA', 'insufficient');
define('COMMERCE_COF_PROCESS_CODE_CARD_NA', 'card_na');
define('COMMERCE_COF_PROCESS_CODE_CARD_NOT_CHARGEABLE', 'card_not_chargeable');
define('COMMERCE_COF_PROCESS_CODE_CARD_OK', 'card_ok');
define('COMMERCE_COF_PROCESS_CODE_METHOD_EMPTY', 'no_method');
define('COMMERCE_COF_PROCESS_CODE_METHOD_NOT_CAPABLE', 'method_not_capable');
define('COMMERCE_COF_PROCESS_CODE_METHOD_SUCCESS', 'method_success');
define('COMMERCE_COF_PROCESS_CODE_CARD_EXPIRED', 'card_expired');

// For the sake of backwards compatibility, we leave this generic failure status
// here.
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE', 'method_failure');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_INSUFFICIENT_FUNDS', 'method_failure_insufficient_funds');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_LIMIT_EXCEEDED', 'method_failure_limit_exceeded');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_CALL_ISSUER', 'method_failure_call_issuer');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_TEMPORARY_HOLD', 'method_failure_temporary_hold');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GENERIC_DECLINE', 'method_failure_generic_decline');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_HARD_DECLINE', 'method_failure_hard_decline');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GATEWAY_ERROR', 'method_failure_gateway_error');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GATEWAY_UNAVAILABLE', 'method_failure_gateway_unavailable');
define('COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GATEWAY_CONFIGURATION_ERROR', 'method_failure_gateway_configuration_error');

/**
 * Implements hook_menu().
 */
function commerce_cardonfile_menu() {
  $items = array();
  $items['admin/commerce/config/cardonfile'] = array(
    'title' => 'Card on file settings',
    'description' => 'Configure your card on file settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_cardonfile_settings_form',
    ),
    'access arguments' => array(
      'configure cardonfile',
    ),
    'file' => 'includes/commerce_cardonfile.admin.inc',
  );
  $items['admin/commerce/config/cardonfile/settings'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['user/%user/cards/add'] = array(
    'title' => 'Add a card',
    'page callback' => 'commerce_cardonfile_add_page',
    'access callback' => 'commerce_cardonfile_add_any_access',
    'access arguments' => array(
      1,
    ),
    'type' => MENU_LOCAL_ACTION,
    'file' => 'includes/commerce_cardonfile.pages.inc',
  );
  $create_implements = commerce_cardonfile_payment_method_implements('create callback');
  foreach ($create_implements as $method_id => $method_function) {
    $payment_method_instances = _commerce_cardonfile_payment_method_instances($method_id);
    foreach ($payment_method_instances as $instance_id => $payment_method) {
      $new_card = commerce_cardonfile_new(array(
        'instance_id' => $instance_id,
        'payment_method' => $payment_method['method_id'],
      ));
      $items['user/%user/cards/add/' . drupal_hash_base64($instance_id)] = array(
        'title' => 'Add a card for payments with !name',
        'title arguments' => array(
          '!name' => $payment_method['display_title'],
        ),
        'page callback' => 'commerce_cardonfile_card_form_page',
        'page arguments' => array(
          'create',
          $new_card,
          1,
        ),
        'access callback' => 'commerce_cardonfile_access',
        'access arguments' => array(
          'create',
          $new_card,
          1,
        ),
        'file' => 'includes/commerce_cardonfile.pages.inc',
      );
    }
  }
  $items['user/%user/cards/%commerce_cardonfile/edit'] = array(
    'title callback' => 'commerce_cardonfile_card_title',
    'title arguments' => array(
      3,
    ),
    'page callback' => 'commerce_cardonfile_card_form_page',
    'page arguments' => array(
      'update',
      3,
      1,
    ),
    'access callback' => 'commerce_cardonfile_access',
    'access arguments' => array(
      'update',
      3,
    ),
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'weight' => -10,
    'file' => 'includes/commerce_cardonfile.pages.inc',
  );
  $items['user/%user/cards/%commerce_cardonfile/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_cardonfile_delete_form',
      3,
    ),
    'access callback' => 'commerce_cardonfile_access',
    'access arguments' => array(
      'delete',
      3,
    ),
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 5,
    'file' => 'includes/commerce_cardonfile.pages.inc',
  );
  return $items;
}

/**
 * Implements hook_admin_paths().
 */
function commerce_cardonfile_admin_paths() {

  // The user module defines user/*/edit as an admin path, so we must explicitly
  // declare user/*/cards/* as non-admin paths.
  $paths = array(
    'user/*/cards/*' => FALSE,
  );
  return $paths;
}

/**
 * Implements hook_theme().
 */
function commerce_cardonfile_theme() {
  return array(
    'card_add_list' => array(
      'variables' => array(
        'content' => array(),
      ),
      'file' => 'includes/commerce_cardonfile.pages.inc',
    ),
  );
}

/**
 * Implements of hook_entity_info().
 */
function commerce_cardonfile_entity_info() {
  $data = array();
  $data['commerce_cardonfile'] = array(
    'label' => t('Commerce Card on File'),
    'entity class' => 'CommerceCardOnFile',
    'controller class' => 'EntityAPIController',
    'base table' => 'commerce_cardonfile',
    'fieldable' => TRUE,
    'module' => 'commerce_cardonfile',
    'entity keys' => array(
      'id' => 'card_id',
    ),
    'label callback' => 'entity_class_label',
    'bundles' => array(
      'commerce_cardonfile' => array(
        'label' => t('Card on File'),
        'admin' => array(
          'path' => 'admin/commerce/config/cardonfile',
          'access arguments' => array(
            'configure cardonfile',
          ),
        ),
      ),
    ),
    'load hook' => 'commerce_cardonfile_load',
    'view modes' => array(
      'administrator' => array(
        'label' => t('Administrator'),
        'custom settings' => FALSE,
      ),
      'customer' => array(
        'label' => t('Customer'),
        'custom settings' => FALSE,
      ),
    ),
    'access callback' => 'commerce_cardonfile_access',
    'token type' => 'commerce-cardonfile',
    'metadata controller class' => '',
    'extra fields controller class' => 'CommerceCardOnFileExtraFieldsController',
  );
  return $data;
}

/**
 * Implements hook_flush_caches().
 *
 * Creates the commerce_cardonfile_profile field on the commerce_cardonfile
 * entity type if missing.
 */
function commerce_cardonfile_flush_caches() {
  $field = field_info_field('commerce_cardonfile_profile');
  if (!$field) {
    $field = array(
      'field_name' => 'commerce_cardonfile_profile',
      'type' => 'commerce_customer_profile_reference',
      'cardinality' => 1,
      'entity_types' => array(
        'commerce_cardonfile',
      ),
      'translatable' => FALSE,
      'settings' => array(
        'profile_type' => 'billing',
      ),
    );
    $field = field_create_field($field);
  }
  $instance = field_info_instance('commerce_cardonfile', 'commerce_cardonfile_profile', 'commerce_cardonfile');
  if (!$instance) {
    $instance = array(
      'field_name' => 'commerce_cardonfile_profile',
      'entity_type' => 'commerce_cardonfile',
      'bundle' => 'commerce_cardonfile',
      'label' => 'Billing Profile',
      'widget' => array(
        'type' => 'commerce_customer_profile_manager',
        'weight' => -5,
      ),
      'display' => array(),
    );

    // Set the default display formatters for various view modes.
    foreach (array(
      'default',
      'customer',
      'administrator',
    ) as $view_mode) {
      $instance['display'][$view_mode] = array(
        'label' => 'above',
        'type' => 'commerce_customer_profile_reference_display',
        'weight' => -5,
      );
    }
    field_create_instance($instance);
  }
}

/**
 * Implements hook_commerce_order_status_info().
 */
function commerce_cardonfile_commerce_order_status_info() {
  $order_statuses = array();
  $order_statuses['cardonfile_payment_failed_soft_decline'] = array(
    'name' => 'cardonfile_payment_failed_soft_decline',
    'title' => t('Payment failed (soft decline)'),
    'state' => 'pending',
    'weight' => -2,
  );
  $order_statuses['cardonfile_payment_error_hard_decline'] = array(
    'name' => 'cardonfile_payment_error_hard_decline',
    'title' => t('Payment failed (hard decline)'),
    'state' => 'pending',
    'weight' => -3,
  );
  return $order_statuses;
}

/**
 * Implements hook_hook_info().
 */
function commerce_cardonfile_hook_info() {
  $base_info = array(
    'group' => 'commerce',
  );
  $hooks = array(
    'commerce_cardonfile_insert' => $base_info,
    'commerce_cardonfile_update' => $base_info,
    'commerce_cardonfile_delete' => $base_info,
    'commerce_cardonfile_chargeable_cards' => $base_info,
    'commerce_cardonfile_checkout_pane_form_alter' => $base_info,
  );
  return $hooks;
}

/**
 * Implements hook_views_api().
 */
function commerce_cardonfile_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'commerce_cardonfile') . '/includes/views',
  );
}

/**
 * Implement hook_views_pre_render().
 */
function commerce_cardonfile_views_pre_render(&$view) {
  if (in_array($view->name, array(
    'commerce_card_on_file_user_cards',
  ))) {
    drupal_add_css(drupal_get_path('module', 'commerce_cardonfile') . '/theme/commerce_cardonfile.view.css');
  }
}

/**
 * Implements hook_permission().
 */
function commerce_cardonfile_permission() {
  return array(
    'configure cardonfile' => array(
      'title' => t('Configure Card on File'),
      'description' => t('Update the Card on File configuration in the Store back end.'),
      'restrict access' => TRUE,
    ),
    'administer card data' => array(
      'title' => t('Administer card data'),
      'description' => t("Access and update any user's stored card data."),
      'restrict access' => TRUE,
    ),
    'view any card data' => array(
      'title' => t('View any card data'),
      'restrict access' => TRUE,
    ),
    'view own card data' => array(
      'title' => t('View own card data'),
    ),
    'create any card data' => array(
      'title' => t('Create any card data'),
      'restrict access' => TRUE,
    ),
    'create own card data' => array(
      'title' => t('Create own card data'),
    ),
    'edit any card data' => array(
      'title' => t('Edit any card data'),
      'restrict access' => TRUE,
    ),
    'edit own card data' => array(
      'title' => t('Edit own card data'),
    ),
    'delete any card data' => array(
      'title' => t('Delete any card data'),
      'restrict access' => TRUE,
    ),
    'delete own card data' => array(
      'title' => t('Delete own card data'),
    ),
  );
}

/**
 * Determines if the user can create a card on any payment method.
 *
 * @param $account
 *   The user account for which access should be checked.
 */
function commerce_cardonfile_add_any_access($account) {
  $create_implements = commerce_cardonfile_payment_method_implements('create callback');
  foreach ($create_implements as $method_id => $method_function) {
    $payment_method_instances = _commerce_cardonfile_payment_method_instances($method_id);
    foreach ($payment_method_instances as $instance_id => $payment_method) {
      $new_card = commerce_cardonfile_new(array(
        'instance_id' => $instance_id,
        'payment_method' => $payment_method['method_id'],
      ));
      if (commerce_cardonfile_access('create', $new_card, $account)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Determines if the user has access to perform an operation on the given card.
 *
 * @param $op
 *   The operation being performed. One of 'view', 'edit', 'update', 'manage',
 *   'create' or 'delete'.
 * @param $card
 *   The card entity.
 * @param $account
 *   The user account for which access should be checked.
 *
 * @return
 *   TRUE if the current user has access, FALSE otherwise.
 */
function commerce_cardonfile_access($op = 'view', $card = NULL, $account = NULL) {
  global $user;
  $account = isset($account) ? $account : $user;

  // resolve operation
  if ($op == 'manage' || $op == 'update') {
    $op = 'edit';
  }

  // load payment method instance and rule
  $payment_method = array();
  $payment_rule = NULL;
  if (!empty($card->instance_id)) {
    $payment_method = commerce_payment_method_instance_load($card->instance_id);
    if (!empty($payment_method)) {

      // Explode the method key into its component parts.
      list($payment_method_id, $instance_rule_name) = explode('|', $payment_method['instance_id']);
      $payment_rule = rules_config_load($instance_rule_name);
    }
  }

  // DENY for operations that require a payment method callback
  if (empty($payment_method) && $op != 'view') {
    return FALSE;
  }

  // Operation specific access checks
  $op_callback = TRUE;
  switch ($op) {
    case 'edit':

      // set operation callback
      $op_callback = commerce_cardonfile_payment_method_callback($payment_method, 'update callback');
      break;
    case 'create':

      // set operation callback
      $op_callback = commerce_cardonfile_payment_method_callback($payment_method, 'create callback');

      // DENY if payment rule has been disabled
      if ($op_callback && !empty($payment_rule) && empty($payment_rule->active)) {
        return FALSE;
      }
      break;
    case 'delete':

      // set operation callback
      $op_callback = commerce_cardonfile_payment_method_callback($payment_method, 'delete callback');
      break;
    default:
      $op_callback = TRUE;
      break;
  }

  // DENY if callback does NOT exist for specific operations
  if (empty($op_callback)) {
    return FALSE;
  }

  // ALLOW access for any user with administer permission.
  if (user_access('administer card data') || user_access("{$op} any card data")) {
    return TRUE;
  }

  // ALLOW access to create new cards to any user with the 'create own card data'
  // permission when they are accessing to their own profile.
  if ($op == 'create' && user_access('create own card data') && $account->uid == $user->uid) {
    return TRUE;
  }

  // ALLOW access for users with permission to manage their own card data.
  if ($op != 'create' && user_access("{$op} own card data") && !empty($card->uid) && $account->uid == $card->uid) {
    return TRUE;
  }

  // DENY by default
  return FALSE;
}

/**
 * Determines if the current user has access to the account's stored cards.
 */
function commerce_cardonfile_user_access($account) {
  global $user;
  if (user_access('administer card data') || user_access('view any card data')) {

    // Grant access for any user with administer permission or view permission
    // to any cards.
    return TRUE;
  }
  elseif ($account->uid != $user->uid) {

    // Otherwise deny the access if the account doesn't belong to the currently
    // logged in user.
    return FALSE;
  }

  // create a stub data array for access checks
  $card_stub = commerce_cardonfile_new(array(
    'uid' => $account->uid,
  ));

  // DENY if the user DOES NOT have view access
  if (!commerce_cardonfile_access('view', $card_stub, $account)) {
    return FALSE;
  }

  // load active cards
  $stored_cards = commerce_cardonfile_load_multiple_by_uid($account->uid);

  // if no cards, then check create access
  if (empty($stored_cards)) {
    return commerce_cardonfile_add_any_access($account);
  }

  // ALLOW by default
  return TRUE;
}

/**
 * Implements hook_form_alter().
 *
 * This implementation alters any checkout form looking for the payment pane
 * and seeing if its details are currently for a credit card payment method. If
 * so, it adds the necessary form elements for Card on File payment, including a
 * select element to use previously stored credit card information and a
 * checkbox on the credit card data entry form to store the given credit card on
 * file for future usage.
 */
function commerce_cardonfile_form_alter(&$form, &$form_state, $form_id) {

  // Exit if the current form ID is for a checkout page form...
  if (strpos($form_id, 'commerce_checkout_form_') !== 0 || !commerce_checkout_page_load(substr($form_id, 23))) {
    return;
  }

  // Exit if the current page's form does no include the payment checkout pane...
  if (empty($form['commerce_payment'])) {
    return;
  }

  // Exit if no payment method instance id
  if (empty($form['commerce_payment']['payment_method']['#default_value'])) {
    return;
  }

  // Extact payment method instance id
  $instance_id = $form['commerce_payment']['payment_method']['#default_value'];

  // Check to see if the currently selected payment method is Card on File
  // enabled (via the cardonfile boolean in its info array).
  $payment_method = commerce_payment_method_instance_load($instance_id);

  // Exit if payment method is not capable of Card on File, or Card on File
  // functionality is disabled in the payment method instance.
  if (!_commerce_cardonfile_capable_payment_method_check($payment_method) || isset($payment_method['settings']['cardonfile']) && !$payment_method['settings']['cardonfile']) {
    return;
  }

  // For onsite payment methods add a checkbox to the credit card
  // details container to store the credit card for future use.
  if (!$payment_method['offsite']) {
    $storage = variable_get('commerce_cardonfile_storage', 'opt-in');
    if (in_array($storage, array(
      'opt-in',
      'opt-out',
    ))) {
      $form['commerce_payment']['payment_details']['credit_card']['cardonfile_store'] = array(
        '#type' => 'checkbox',
        '#title' => t('Store this credit card on file for future use.'),
        '#default_value' => $storage == 'opt-out',
      );
    }
    else {
      $form['commerce_payment']['payment_details']['credit_card']['cardonfile_store'] = array(
        '#type' => 'value',
        '#value' => TRUE,
      );
    }
  }

  // Load existing active cards for the payment method instance and user.
  $stored_cards = commerce_cardonfile_load_multiple_by_uid($form_state['account']->uid, $payment_method['instance_id']);

  // Build options form
  $cardonfile_options_form = array();
  $instance_default_card_id = NULL;

  // If have stored cards ...
  if (!empty($stored_cards)) {
    $valid_cards = array_filter($stored_cards, 'commerce_cardonfile_validate_card_expiration');

    // If have un-expired cards ...
    if (!empty($valid_cards)) {

      // get options list with labels
      $card_options = commerce_cardonfile_element_options_list($valid_cards);

      // determine default option
      $card_options_default_value = key($card_options);
      foreach (array_keys($card_options) as $card_id) {
        if (isset($valid_cards[$card_id]) && !empty($valid_cards[$card_id]->instance_default)) {
          $card_options_default_value = $instance_default_card_id = $card_id;

          // move instance default to the top of the list
          $card_option_label = $card_options[$card_id];
          unset($card_options[$card_id]);
          $card_options = array(
            $card_id => $card_option_label,
          ) + $card_options;
          break;
        }
      }
      $cardonfile_options_form = array(
        '#type' => variable_get('commerce_cardonfile_selector', 'radios'),
        '#title' => t('Select a stored card'),
        '#options' => $card_options,
        '#default_value' => $card_options_default_value,
        '#weight' => -10,
        '#ajax' => array(
          'callback' => 'commerce_payment_pane_checkout_form_details_refresh',
          'wrapper' => 'payment-details',
        ),
      );
    }
  }

  // update form with options
  if (!empty($cardonfile_options_form)) {
    $form['commerce_payment']['payment_details']['cardonfile'] = $cardonfile_options_form;

    // Add the CSS to hide a sole credit card icon if specified.
    if (variable_get('commerce_cardonfile_hide_cc_radio_button', TRUE)) {
      if (count($form['commerce_payment']['payment_method']['#options']) == 1) {
        $form['commerce_payment']['payment_method']['#attached']['css'][] = drupal_get_path('module', 'commerce_cardonfile') . '/theme/commerce_cardonfile.checkout.css';
      }
    }

    // If the current value for the card selection element is not to use
    // a different credit card, then hide the credit card form elements.
    if (empty($form_state['values']['commerce_payment']['payment_details']['cardonfile']) || $form_state['values']['commerce_payment']['payment_details']['cardonfile'] !== 'new') {
      $form['commerce_payment']['payment_details']['credit_card']['#access'] = FALSE;
    }
  }
  else {
    $form['commerce_payment']['payment_details']['cardonfile'] = array(
      '#type' => 'value',
      '#value' => 'new',
    );
  }

  // Add mark as default element
  $instance_default_default_value = 0;
  if (!empty($instance_default_card_id)) {
    if (empty($form_state['values']) || !empty($form_state['values']['commerce_payment']['payment_details']['cardonfile']) && $form_state['values']['commerce_payment']['payment_details']['cardonfile'] == $instance_default_card_id) {
      $instance_default_default_value = 1;
    }
  }
  $force_instance_default = empty($stored_cards);
  $form['commerce_payment']['payment_details']['cardonfile_instance_default'] = array(
    '#type' => 'checkbox',
    '#title' => t('Set as your default card'),
    '#default_value' => $instance_default_default_value || $force_instance_default,
    '#access' => !$instance_default_default_value,
    '#disabled' => $force_instance_default,
    '#states' => array(
      'invisible' => array(
        ':input[name$="[cardonfile]"]' => array(
          'value' => 'new',
        ),
      ),
      'visible' => array(
        ':input[name$="[cardonfile_store]"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );

  // Allow others to alter this alter
  drupal_alter('commerce_cardonfile_checkout_pane_form', $form['commerce_payment']['payment_details'], $form, $form_state);

  // Add submit handler
  if (isset($form['buttons']['continue'])) {
    $form['buttons']['continue']['#submit'][] = 'commerce_cardonfile_commerce_checkout_form_submit';
  }
}

/**
 * Checkout form submit callback to process card on file options
 */
function commerce_cardonfile_commerce_checkout_form_submit($form, &$form_state) {
  if (!isset($form_state['order']) || empty($form_state['values']['commerce_payment'])) {
    return;
  }
  $pane_values =& $form_state['values']['commerce_payment'];
  $store_card = !empty($pane_values['payment_details']['credit_card']['cardonfile_store']);

  // Exit if no card selection
  if (empty($pane_values['payment_details']['cardonfile'])) {
    return;
  }

  // Load a fresh copy of the order stored in the form.
  $order = commerce_order_load($form_state['order']->order_id);
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $account = $form_state['account'];
  $instance_id = $pane_values['payment_method'];

  // get card on file value
  $card_id_selected = NULL;
  if ($pane_values['payment_details']['cardonfile'] != 'new') {
    $card_id_selected = intval($pane_values['payment_details']['cardonfile']);
  }

  // Submit actions for card selected
  if (!empty($card_id_selected)) {

    // Mark as default
    if (!empty($pane_values['payment_details']['cardonfile_instance_default'])) {
      commerce_cardonfile_set_default_card($card_id_selected);
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds card on file options to the admin payment terminal for card on file
 * enabled payment methods.
 */
function commerce_cardonfile_form_commerce_payment_order_transaction_add_form_alter(&$form, &$form_state) {
  $payment_method = isset($form_state['payment_method']) ? $form_state['payment_method'] : NULL;
  $form['payment_terminal']['#prefix'] = '<div id="payment-terminal-ajax-wrapper">';
  $form['payment_terminal']['#suffix'] = '</div>';
  if (!_commerce_cardonfile_capable_payment_method_check($payment_method) || isset($payment_method['settings']['cardonfile']) && !$payment_method['settings']['cardonfile']) {
    return;
  }

  // Load existing active cards for the payment method instance and user.
  $stored_cards = commerce_cardonfile_load_multiple_by_uid($form_state['order']->uid, $payment_method['instance_id']);

  // Build options form
  $cardonfile_options_form = array();
  $instance_default_card_id = NULL;

  // If have stored cards ...
  if (!empty($stored_cards)) {
    $valid_cards = array_filter($stored_cards, 'commerce_cardonfile_validate_card_expiration');

    // If have un-expired cards ...
    if (!empty($valid_cards)) {

      // get options list with labels
      $card_option_element_type = variable_get('commerce_cardonfile_selector', 'radios');
      $card_options = commerce_cardonfile_element_options_list($valid_cards, $card_option_element_type);

      // determine default option
      $card_options_default_value = key($card_options);
      foreach (array_keys($card_options) as $card_id) {
        if (isset($valid_cards[$card_id]) && !empty($valid_cards[$card_id]->instance_default)) {
          $card_options_default_value = $instance_default_card_id = $card_id;

          // move instance default to the top of the list
          $card_option_label = $card_options[$card_id];
          unset($card_options[$card_id]);
          $card_options = array(
            $card_id => $card_option_label,
          ) + $card_options;
          break;
        }
      }

      // create options element
      $cardonfile_options_form = array(
        '#type' => $card_option_element_type,
        '#title' => t('Select a stored card'),
        '#options' => $card_options,
        '#default_value' => $card_options_default_value,
        '#weight' => -10,
        '#ajax' => array(
          'callback' => 'commerce_cardonfile_payment_terminal_ajax',
          'wrapper' => 'payment-terminal-ajax-wrapper',
        ),
      );
    }
  }

  // update form with options
  if (!empty($cardonfile_options_form)) {
    $form['payment_terminal']['payment_details']['cardonfile'] = $cardonfile_options_form;

    // If the current value for the card selection element is not to use
    // a different credit card, then hide the credit card form elements.
    if (empty($form_state['values']['payment_details']['cardonfile']) || $form_state['values']['payment_details']['cardonfile'] !== 'new') {
      $form['payment_terminal']['payment_details']['credit_card']['#access'] = FALSE;
    }
  }
  else {
    $form['payment_terminal']['payment_details']['cardonfile'] = array(
      '#type' => 'value',
      '#value' => 'new',
    );
  }

  // Allow other modules to alter the card on file form elements.
  drupal_alter('commerce_cardonfile_payment_terminal_form', $form, $form_state);
}

/**
 * Ajax callback for payment terminal card on file operations.
 */
function commerce_cardonfile_payment_terminal_ajax($form, &$form_state) {
  return $form['payment_terminal'];
}

/**
 * Returns an options array for selecting a card on file during checkout
 *
 * @param $stored_cards
 *   An array of stored card data arrays keyed by card_id.
 *
 * @return
 *   An options array for selecting a card on file.
 */
function commerce_cardonfile_element_options_list($stored_cards) {
  $options = array();
  foreach ($stored_cards as $card_id => $card) {
    $replacements = array(
      '@card' => $card
        ->label(),
      '@card_exp_month' => str_pad($card->card_exp_month, 2, '0', STR_PAD_LEFT),
      '@card_exp_year' => $card->card_exp_year,
    );
    $options[$card_id] = t('@card, Expires @card_exp_month/@card_exp_year', $replacements);
  }

  // Add the special option for adding a new card.
  $options['new'] = t('Use a different credit card');
  return $options;
}

/**
 * Returns an initialized card entity.
 *
 * @param $values
 *   An array of values to set, keyed by property name.
 *
 * @return
 *   A card entity with all default fields initialized.
 */
function commerce_cardonfile_new(array $values = array()) {
  return entity_create('commerce_cardonfile', $values);
}

/**
 * Loads a card by ID.
 *
 * @param $card_id
 *   The ID of the card to load.
 *
 * @return
 *   The loaded card entity or FALSE if not found.
 */
function commerce_cardonfile_load($card_id) {
  return entity_load_single('commerce_cardonfile', $card_id);
}

/**
 * Loads multiple cards by ID or based on a set of matching conditions.
 *
 * @see entity_load()
 *
 * @param $card_ids
 *   An array of card IDs.
 * @param $conditions
 *   An array of conditions on the {commerce_cardonfile} table in the form
 *     'field' => $value.
 * @param $reset
 *   Whether to reset the internal cache.
 *
 * @return
 *   An array of card entities indexed by card_id.
 */
function commerce_cardonfile_load_multiple($card_ids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('commerce_cardonfile', $card_ids, $conditions, $reset);
}

/**
 * Loads multiple cards belonging to a specific user.
 *
 * Only active cards (status = 1) are returned.
 *
 * @param $uid
 *   The uid of the card owner.
 * @param $instance_id
 *   (Optional) The instance id of the payment method.
 * @param $default_only
 *   Whether to load only default cards. A user can have one default card per
 *   payment method used. Defaults to FALSE.
 *
 * @return
 *   An array of card entities indexed by card_id.
 */
function commerce_cardonfile_load_multiple_by_uid($uid, $instance_id = NULL, $default_only = FALSE) {

  // Never load any cards that may be created by anonymous users.
  if ($uid == 0) {
    return;
  }
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_cardonfile')
    ->propertyCondition('uid', $uid)
    ->propertyCondition('status', 1);
  if (!empty($instance_id)) {
    $query
      ->propertyCondition('instance_id', $instance_id);
  }
  if ($default_only) {
    $query
      ->propertyCondition('instance_default', TRUE);
  }
  $result = $query
    ->execute();
  if (isset($result['commerce_cardonfile'])) {
    return commerce_cardonfile_load_multiple(array_keys($result['commerce_cardonfile']));
  }
  else {
    return array();
  }
}

/**
 * Saves a card.
 *
 * @param $card
 *   The card entity to save.
 *
 * @return
 *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
 */
function commerce_cardonfile_save($card) {
  $order = menu_get_object('commerce_order');
  if (empty($card->order_id) && isset($order)) {
    $card->order_id = $order->order_id;
  }
  return entity_save('commerce_cardonfile', $card);
}

/**
 * Deletes a card by ID.
 *
 * @param $card_id
 *   The ID of the card to delete.
 *
 * @return
 *   TRUE on success, FALSE otherwise.
 */
function commerce_cardonfile_delete($card_id) {
  return entity_delete('commerce_cardonfile', $card_id);
}

/**
 * Deletes multiple cards by ID.
 *
 * @param $card_ids
 *   An array of card IDs to delete.
 *
 * @return
 *   TRUE on success, FALSE otherwise.
 */
function commerce_cardonfile_delete_multiple($card_ids) {
  return entity_delete_multiple('commerce_cardonfile', $card_ids);
}

/**
 * Implements hook_entity_view().
 */
function commerce_cardonfile_entity_view($entity, $entity_type, $view_mode, $langcode) {
  if ($entity_type == 'commerce_cardonfile') {

    // Hide the labels of properties rendered as extra fields.
    foreach ($entity->content as &$render_array) {
      if (isset($render_array['#theme']) && $render_array['#theme'] == 'entity_property') {
        $render_array['#label_hidden'] = TRUE;
      }
    }
  }
}

/**
 * Implements hook_commerce_customer_profile_can_delete().
 *
 * Don't allow a customer profile to be deleted (and trigger duplication)
 * if it's referenced by a card.
 */
function commerce_cardonfile_commerce_customer_profile_can_delete($profile) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_cardonfile')
    ->fieldCondition('commerce_cardonfile_profile', 'profile_id', $profile->profile_id)
    ->count();
  $count = $query
    ->execute();
  return $count == 0;
}

/**
 * Set a card's instance default value.
 *
 * @deprecated
 *   Set $card->instance_default directly instead.
 */
function commerce_cardonfile_set_default_card($card_id) {
  if ($card = commerce_cardonfile_load($card_id)) {
    $card->instance_default = 1;
    return $card
      ->save();
  }
}

/**
 * Validates the card expiration date.
 *
 * @return
 *   TRUE if the card hasn't expired, FALSE otherwise.
 */
function commerce_cardonfile_validate_card_expiration($card) {
  commerce_cardonfile_load_credit_card_helpers();
  $check = commerce_payment_validate_credit_card_exp_date($card->card_exp_month, $card->card_exp_year);
  return $check === TRUE;
}

/**
 * Formats a truncated credit card number for display.
 *
 * While most credit cards have 16 numbers, some like American Express have
 * less. This function respects those rules in order to generate a more
 * believable mask.
 *
 * @param $card_number
 *   Truncated card number (last 4 digits).
 * @param $card_type
 *   The card type.
 *
 * @return
 *   The formatted credit card number with unknown numbers represented by an X.
 *   Thus a visa ending with "1234" gets formatted as "XXXX-XXXX-XXXX-1234".
 */
function commerce_cardonfile_format_credit_card_number($card_number, $card_type) {
  $special_lengths = array(
    'amex' => 15,
    'cb' => 14,
    'dc' => 14,
  );
  $length = isset($special_lengths[$card_type]) ? $special_lengths[$card_type] : 16;
  $padded_number = str_pad($card_number, $length, 'X', STR_PAD_LEFT);
  $new_number = chunk_split($padded_number, 4, '-');

  // chunk_split() returns an extra '-' at the end of the number, ignore it.
  return substr($new_number, 0, -1);
}

/**
 * Returns an associative array of credit card types.
 *
 * Wraps commerce_payment_credit_card_types().
 */
function commerce_cardonfile_credit_card_types() {
  commerce_cardonfile_load_credit_card_helpers();
  return commerce_payment_credit_card_types();
}

/**
 * Loads credit card helper functions in commerce_payment
 */
function commerce_cardonfile_load_credit_card_helpers() {
  static $loaded = FALSE;
  if (!$loaded) {
    $loaded = module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
  }
}

/**
 * Returns an array of all available payment method cardonfile callbacks
 */
function commerce_cardonfile_payment_method_available_callbacks() {
  return array(
    'create callback',
    'create form callback',
    'update callback',
    'update form callback',
    'delete callback',
    'charge callback',
  );
}

/**
 * Returns TRUE if the card can be charged
 */
function commerce_cardonfile_can_charge($card) {

  // DENY if card is disabled
  if (empty($card->status)) {
    return FALSE;
  }

  // DENY if there is no payment method instance
  if (empty($card->instance_id)) {
    return FALSE;
  }

  // DENY if card is expired
  if (!commerce_cardonfile_validate_card_expiration($card)) {
    return FALSE;
  }

  // load payment method related to the card
  $payment_method = commerce_payment_method_instance_load($card->instance_id);

  // DENY if not a valid payment method
  if (empty($payment_method)) {
    return FALSE;
  }

  // ALLOW/DENY based on payment method capabilities
  $callback = commerce_cardonfile_payment_method_callback($payment_method, 'charge callback');
  return $callback ? TRUE : FALSE;
}

/**
 * Returns the Card on File callback function for the given payment method.
 *
 * @param $payment_method
 *   The payment method object.
 * @param $callback
 *   The callback function to return, one of:
 *    - 'create callback'
 *    - 'create form callback'
 *    - 'update callback'
 *    - 'update form callback'
 *    - 'delete callback'
 *    - 'charge callback'
 *
 * @return
 *   A string containing the name of the callback function or FALSE if it could
 *   not be found.
 */
function commerce_cardonfile_payment_method_callback($payment_method, $callback) {
  if (!empty($payment_method) && !empty($payment_method['method_id'])) {
    $implements = commerce_cardonfile_payment_method_implements($callback);
    if (!empty($implements) && !empty($implements[$payment_method['method_id']])) {
      return $implements[$payment_method['method_id']];
    }
  }
}

/**
 * Returns all payment method instances that implement a specific callback
 *
 * @param $callback
 *   The callback function to return, one of:
 *    - 'create callback'
 *    - 'create form callback'
 *    - 'update callback'
 *    - 'update form callback'
 *    - 'delete callback'
 *    - 'charge callback'
 *
 * @return
 *   An array of callback function names keyed by payment method id
 */
function commerce_cardonfile_payment_method_implements($callback) {
  $cache =& drupal_static(__FUNCTION__);
  if (!isset($cache)) {
    $cache = array();

    // get payment methods and load module implements for hook_commerce_payment_method_info()
    // so that cardonfile callback can be in same hook file, ie mymodule.commerce.inc
    $payment_methods = _commerce_cardonfile_capable_payment_methods();
    $available_callbacks = commerce_cardonfile_payment_method_available_callbacks();
    foreach ($payment_methods as $method_id => $payment_method) {
      foreach ($available_callbacks as $available_callback) {
        if (!empty($payment_method['cardonfile'][$available_callback])) {

          // Include the payment method file if specified.
          if (!empty($payment_method['file'])) {
            $parts = explode('.', $payment_method['file']);
            module_load_include(array_pop($parts), $payment_method['module'], implode('.', $parts));
          }
          $func = $payment_method['cardonfile'][$available_callback];
          if (function_exists($func)) {
            $cache[$available_callback][$method_id] = $func;
          }
        }
      }
    }
  }
  return isset($cache[$callback]) ? $cache[$callback] : array();
}

/**
 * Select the card on file that can be charged for an order.
 */
function commerce_cardonfile_order_select_card($order, $forced_instance_id = NULL) {
  $response = array(
    'code' => COMMERCE_COF_PROCESS_CODE_CARD_NA,
    'message' => 'No chargeable card on file is available for the order @order_id and user @uid.',
  );
  if ($possible_cards = commerce_cardonfile_load_multiple_by_uid($order->uid, $forced_instance_id, TRUE)) {

    // Determine any chargeable cards ...
    $unchargeable_cards = array();
    foreach ($possible_cards as $card_id => $possible_card) {
      if (commerce_cardonfile_order_can_charge_card($order, $possible_card) && (!$forced_instance_id || !isset($possible_card->instance_id) || $possible_card->instance_id == $forced_instance_id)) {
        $response['card_chosen'] = $possible_card;
        $response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_OK;
        $response['message'] = 'Card can be attempted to charge for @order_id.';
        break;
      }
      else {
        $unchargeable_cards[$card_id] = $possible_card;
      }
    }
    if (empty($response['card_chosen']) && !empty($unchargeable_cards)) {

      // check first unchargeable card for expiration
      $unchargeable_card_top = reset($unchargeable_cards);
      $response['card_chosen'] = $unchargeable_card_top;
      if (!commerce_cardonfile_validate_card_expiration($unchargeable_card_top)) {
        $response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_EXPIRED;
        $response['message'] = 'Card on file has expired for user @uid\'s card @card_id when attempting to process order @order_id.';
      }
    }
  }
  return $response;
}

/**
 * Process a charge for a given an order
 *
 * Wrapper function for _commerce_cardonfile_order_invoke_process_card() to
 * trigger rules events
 *
 * @param $order
 *   An order object
 * @param $charge
 *   Charge array of amount, currency_code
 * @param $card
 *   The card entity.
 * @param $forced_instance_id
 *   Payment instance ID to enforce the usage of a specific payment gateway
 *
 * @return
 *   TRUE if the order was processed successfully
 */
function commerce_cardonfile_order_charge_card($order, $charge = array(), $card = NULL, $forced_instance_id = NULL) {
  $response = array(
    'status' => FALSE,
    'code' => COMMERCE_COF_PROCESS_CODE_INSUFFICIENT_DATA,
    'message' => '',
    'message_variables' => array(),
  );

  // Exit if no order id
  if (empty($order->order_id)) {
    $response['message'] = 'Order ID is not provided.';
    return $response;
  }
  $response['message_variables'] += array(
    '@order_id' => $order->order_id,
  );

  // Exit if no user associated with the order
  if (empty($order->uid)) {
    $response['message'] = 'Order owner not provided for order @order_id.';
    return $response;
  }
  $response['message_variables'] += array(
    '@uid' => $order->uid,
  );

  // determine charge amount
  // set charge to order balance if none provided
  if (empty($charge)) {
    $charge = commerce_payment_order_balance($order);
  }

  // exit if no charge
  if (empty($charge) || empty($charge['amount']) || empty($charge['currency_code'])) {
    $response['message'] = 'Charge amount not provided for order @order_id.';
    return $response;
  }
  $response['message_variables'] += array(
    '@charge' => commerce_currency_format($charge['amount'], $charge['currency_code']),
  );

  // exit if no card data provided
  if (empty($card)) {
    $response['message'] = 'Card data not provided for order @order_id.';
    return $response;
  }
  $response['card_chosen'] = $card;
  if (!commerce_cardonfile_order_can_charge_card($order, $card)) {

    // check for expiration to set a specific code
    if (!commerce_cardonfile_validate_card_expiration($card)) {
      $response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_EXPIRED;
      $response['message'] = 'Card on file has expired for user @uid\'s card @card_id when attempting to process order @order_id.';
    }
    else {
      $response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_NOT_CHARGEABLE;
      $response['message'] = 'Card provided cannot be charged for the order @order_id and user @uid.';
    }
    return $response;
  }

  // resolve payment method instance input
  $instance_is_forced = FALSE;
  if (!empty($forced_instance_id)) {
    $instance_is_forced = TRUE;

    // set the order data so chargeable hook can use to determine the card
    $order->data['payment_method'] = $forced_instance_id;
    $response['message_variables'] += array(
      '@instance_id' => $forced_instance_id,
    );
  }
  else {
    $forced_instance_id = NULL;
  }
  if ($instance_is_forced && isset($card->instance_id) && $card->instance_id != $forced_instance_id) {
    $response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_NOT_CHARGEABLE;
    $response['message'] = 'Card provided is not registered with the requested payment method: Order @order_id, user @uid, payment instance @instance_id';
    return $response;
  }

  // Wrap up the order
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

  // load payment method
  $payment_method = commerce_payment_method_instance_load($card->instance_id);
  if (empty($payment_method)) {
    $response['code'] = COMMERCE_COF_PROCESS_CODE_METHOD_EMPTY;
    $response['message'] = 'The payment method instance (@instance_id) is not available on the system.';
    return $response;
  }
  $response['message_variables'] += array(
    '@method' => isset($payment_method['short_title']) ? $payment_method['short_title'] : $payment_method['method_id'],
  );

  // determine payment method's callback function
  $func = commerce_cardonfile_payment_method_callback($payment_method, 'charge callback');

  // Exit if no callback - sanity check since it should have this if "can charge"
  if (empty($func)) {
    $response['code'] = COMMERCE_COF_PROCESS_CODE_METHOD_NOT_CAPABLE;
    $response['message'] = 'The payment method @method instance (@instance_id) does not implement a valid card on file "charge callback".';
    return $response;
  }

  // invoke callback function
  $method_return = $func($payment_method, $card, $order, $charge);

  // process return from gateway module
  // Backwards compatibility: returned value can be a boolean FALSE or a
  // specified failure code.
  if ($method_return === FALSE || !in_array($method_return, array(
    COMMERCE_PAYMENT_STATUS_SUCCESS,
    COMMERCE_PAYMENT_STATUS_PENDING,
  ))) {

    // Failure
    $response['status'] = FALSE;

    // If the returned value doesn't specify the failure code, save a generic
    // one.
    $response['code'] = $method_return === FALSE ? COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE : $method_return;
    $response['message'] = 'The payment method @method instance (@instance_id) failed for order @order_id, user @uid, card @card_id, charge amount @charge.';
  }
  else {

    // Success
    $response['status'] = TRUE;
    $response['code'] = $method_return;
    $response['message'] = 'The payment method  @method instance (@instance_id) was successful for order @order_id, user @uid, card @card_id, charge amount @charge.';

    // load a fresh order in case it was modified during method callback
    $order = commerce_order_load($order->order_id);
    $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

    // Store the last processed card on the order card reference field
    if (isset($order_wrapper->commerce_cardonfile)) {
      $order_ref_card_id = $order_wrapper->commerce_cardonfile
        ->value();
      if (empty($order_ref_card_id) || $order_ref_card_id != $card->card_id) {
        $order_wrapper->commerce_cardonfile = array(
          'card_id' => $card->card_id,
        );
        $order_wrapper
          ->save();
      }
    }
  }
  if (!empty($response['card_chosen'])) {
    $card_chosen = $response['card_chosen'];
  }
  elseif (!empty($card)) {
    $card_chosen = $card;
  }
  if (empty($response) || empty($response['status'])) {
    rules_invoke_all('commerce_cardonfile_charge_failed', $card_chosen, $order, $charge, $response);
  }
  else {
    rules_invoke_all('commerce_cardonfile_charge_success', $card_chosen, $order, $charge, $response);
  }
  return $response;
}

/**
 * Returns TRUE if the card can be charged for the given order
 */
function commerce_cardonfile_order_can_charge_card($order, $card) {

  // DENY if no order id
  if (empty($order->order_id)) {
    return FALSE;
  }

  // DENY if no owner provided
  if (empty($order->uid) || empty($card->uid)) {
    return FALSE;
  }

  // DENY if owners do not match
  if ($card->uid != $order->uid) {
    return FALSE;
  }
  return commerce_cardonfile_can_charge($card);
}

/**
 * Returns all payment methods that are card on file capable
 *
 * @return
 *  An associative array of payment method objects keyed by the method_id.
 */
function _commerce_cardonfile_capable_payment_methods() {
  $capable_methods = array();
  $payment_methods = commerce_payment_methods();
  foreach ($payment_methods as $method_id => $payment_method) {
    if (_commerce_cardonfile_capable_payment_method_check($payment_method)) {
      $capable_methods[$method_id] = $payment_method;
    }
  }
  return $capable_methods;
}

/**
 * Returns TRUE if a payment method is capable of card on file
 *
 * @param $payment_method
 *   An associative array of payment method objects keyed by the method_id.
 *
 * @return
 *   TRUE if payment method is capable of card on file
 */
function _commerce_cardonfile_capable_payment_method_check($payment_method) {
  return !empty($payment_method['cardonfile']) ? $payment_method['cardonfile'] : FALSE;
}

/**
 * Returns all payment method instances for a given payment method id
 *
 * @param $method_id
 *   A payment method id
 * @param $include_disabled
 *   Return enabled and disabled instances
 *
 * @return
 *   An array of all loaded payment method instances keyed by instance_id
 */
function _commerce_cardonfile_payment_method_instances($method_id, $include_disabled = FALSE) {
  $cached_ids =& drupal_static(__FUNCTION__, array());
  $include_disabled = !empty($include_disabled);
  if (!array_key_exists($method_id, $cached_ids)) {
    $cached_ids[$method_id] = array();

    // load all rules ... no easier way
    $rules_configs = rules_config_load_multiple(FALSE);

    // find all rules with an action to enable this method
    foreach ($rules_configs as $rule_name => $rule) {

      // Only rules and sub-types have actions.
      if (!$rule instanceof Rule) {
        continue;
      }

      // fast skip if rule does not depend on commerce_payment
      if (!isset($rule->dependencies) || !in_array('commerce_payment', $rule->dependencies)) {
        continue;
      }
      foreach ($rule
        ->actions() as $action) {

        // skip any actions that are not simple rules actions, ie loops
        if (!$action instanceof RulesAction) {
          continue;
        }
        if ($action
          ->getElementName() == 'commerce_payment_enable_' . $method_id) {
          $instance_id = commerce_payment_method_instance_id($method_id, $rule);
          $cached_ids[$method_id][$instance_id] = $rule->active;
          continue 2;

          // skip to next rule
        }
      }
    }
  }

  // load instances
  $instances = array();
  if (!empty($cached_ids[$method_id])) {
    foreach ($cached_ids[$method_id] as $instance_id => $instance_active) {
      if ($instance_active || $include_disabled) {
        $instances[$instance_id] = commerce_payment_method_instance_load($instance_id);
      }
    }
  }
  return $instances;
}

/**
 * Returns a list of card statuses.
 */
function commerce_cardonfile_statuses() {
  $statuses = array(
    0 => t('Disabled'),
    1 => t('Active'),
    2 => t('Not deletable'),
    3 => t('Declined'),
  );
  return $statuses;
}

/**
 * Entity Metadata getter callback.
 */
function commerce_cardonfile_get_properties($item, array $options, $name, $type, $context) {
  switch ($name) {
    case 'card_number':
      return commerce_cardonfile_format_credit_card_number($item->card_number, $item->card_type);
    case 'card_exp':
      $month = str_pad($item->card_exp_month, 2, '0', STR_PAD_LEFT);
      $year = $item->card_exp_year;
      return format_string('@month/@year', array(
        '@month' => $month,
        '@year' => $year,
      ));
  }
}

/**
 * Defines property info for the charge response.
 */
function commerce_cardonfile_charge_card_response_property_info_callback() {
  $properties = array(
    'status' => array(
      'label' => t('Status'),
      'type' => 'boolean',
    ),
    'code' => array(
      'label' => t('Status Code'),
      'type' => 'text',
      'options list' => 'commerce_cardonfile_charge_card_status_code_options',
    ),
    'error_level' => array(
      'label' => t('Error Level'),
      'type' => 'integer',
      'options list' => 'commerce_cardonfile_charge_card_error_level_options',
      'getter callback' => 'commerce_cardonfile_charge_card_get_properties',
      'computed' => TRUE,
    ),
    'message' => array(
      'label' => t('Message'),
      'type' => 'text',
      'getter callback' => 'commerce_cardonfile_charge_card_get_properties',
      'computed' => TRUE,
    ),
    'card_chosen' => array(
      'label' => t('Card Chosen'),
      'type' => 'commerce_cardonfile',
    ),
  );
  return $properties;
}

/**
 * Implements hook_commerce_payment_transaction_status_info().
 */
function commerce_cardonfile_commerce_payment_transaction_status_info() {
  $statuses = array();
  foreach (commerce_cardonfile_charge_card_failure_status_code_options() as $status_name => $title) {
    $statuses[$status_name] = array(
      'status' => $status_name,
      'title' => $title,
      'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-failure.png',
      'total' => TRUE,
    );
  }
  return $statuses;
}

/**
 * Returns an options list of processing card response status codes
 */
function commerce_cardonfile_charge_card_status_code_options() {
  return array(
    COMMERCE_COF_PROCESS_CODE_INSUFFICIENT_DATA => t('Insufficient data'),
    COMMERCE_COF_PROCESS_CODE_CARD_NA => t('No card available'),
    COMMERCE_COF_PROCESS_CODE_CARD_NOT_CHARGEABLE => t('Card not chargeable'),
    COMMERCE_COF_PROCESS_CODE_METHOD_EMPTY => t('Payment method not valid'),
    COMMERCE_COF_PROCESS_CODE_METHOD_NOT_CAPABLE => t('Payment method not capable'),
    COMMERCE_COF_PROCESS_CODE_METHOD_SUCCESS => t('Payment method success'),
  ) + commerce_cardonfile_charge_card_failure_status_code_options();
}

/**
 * Returns an option list of processing card response failure status codes.
 *
 * These statuses are relevant when it comes to dunning management.
 */
function commerce_cardonfile_charge_card_failure_status_code_options() {
  return array(
    COMMERCE_COF_PROCESS_CODE_CARD_EXPIRED => t('Card expired'),
    // For the sake of backwards compatibility, we leave this generic failure
    // status here.
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE => t('Payment method failure'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_INSUFFICIENT_FUNDS => t('Insufficient funds'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_LIMIT_EXCEEDED => t('Limit exceeded'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_CALL_ISSUER => t('Call issuer'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_TEMPORARY_HOLD => t('Temporary hold'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GENERIC_DECLINE => t('Generic decline'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_HARD_DECLINE => t('Hard decline (will never succeed)'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GATEWAY_ERROR => t('Try again/gateway error'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GATEWAY_UNAVAILABLE => t('Issuer or gateway unavailable'),
    COMMERCE_COF_PROCESS_CODE_METHOD_FAILURE_GATEWAY_CONFIGURATION_ERROR => t('Communication/configuration error'),
  );
}

/**
 * Returns an option list of processing card response error level
 */
function commerce_cardonfile_charge_card_error_level_options() {
  return array(
    0 => t('Notice'),
    1 => t('Warning'),
    2 => t('Error'),
  );
}

/**
 * Callback for getting payment transaction properties.
 */
function commerce_cardonfile_charge_card_get_properties($data, array $options, $name) {
  switch ($name) {
    case 'message':
      if (!empty($data['message'])) {
        $message_variables = array();
        if (!empty($data['message_variables']) && is_array($data['message_variables'])) {
          $message_variables = $data['message_variables'];
        }
        return t($data['message'], $message_variables);
      }
      else {
        return '';
      }
    case 'error_level':
      return commerce_cardonfile_charge_card_error_level($data);
  }
}

/**
 * Returns the error level for a given card processing response array
 */
function commerce_cardonfile_charge_card_error_level($response) {
  $level = 0;
  $status_codes = commerce_cardonfile_charge_card_status_code_options();
  if (empty($response['code']) || !isset($status_codes[$response['code']])) {
    return $level;
  }
  switch ($response['code']) {
    case COMMERCE_COF_PROCESS_CODE_INSUFFICIENT_DATA:
    case COMMERCE_COF_PROCESS_CODE_CARD_NA:
      $level = 0;
      break;
    case COMMERCE_COF_PROCESS_CODE_METHOD_EMPTY:
    case COMMERCE_COF_PROCESS_CODE_METHOD_NOT_CAPABLE:
      $level = 1;
      break;
    default:

      // Every other possible status code falls into this level of error.
      $level = 2;
      break;
  }
  return $level;
}

Functions

Namesort descending Description
commerce_cardonfile_access Determines if the user has access to perform an operation on the given card.
commerce_cardonfile_add_any_access Determines if the user can create a card on any payment method.
commerce_cardonfile_admin_paths Implements hook_admin_paths().
commerce_cardonfile_can_charge Returns TRUE if the card can be charged
commerce_cardonfile_charge_card_error_level Returns the error level for a given card processing response array
commerce_cardonfile_charge_card_error_level_options Returns an option list of processing card response error level
commerce_cardonfile_charge_card_failure_status_code_options Returns an option list of processing card response failure status codes.
commerce_cardonfile_charge_card_get_properties Callback for getting payment transaction properties.
commerce_cardonfile_charge_card_response_property_info_callback Defines property info for the charge response.
commerce_cardonfile_charge_card_status_code_options Returns an options list of processing card response status codes
commerce_cardonfile_commerce_checkout_form_submit Checkout form submit callback to process card on file options
commerce_cardonfile_commerce_customer_profile_can_delete Implements hook_commerce_customer_profile_can_delete().
commerce_cardonfile_commerce_order_status_info Implements hook_commerce_order_status_info().
commerce_cardonfile_commerce_payment_transaction_status_info Implements hook_commerce_payment_transaction_status_info().
commerce_cardonfile_credit_card_types Returns an associative array of credit card types.
commerce_cardonfile_delete Deletes a card by ID.
commerce_cardonfile_delete_multiple Deletes multiple cards by ID.
commerce_cardonfile_element_options_list Returns an options array for selecting a card on file during checkout
commerce_cardonfile_entity_info Implements of hook_entity_info().
commerce_cardonfile_entity_view Implements hook_entity_view().
commerce_cardonfile_flush_caches Implements hook_flush_caches().
commerce_cardonfile_format_credit_card_number Formats a truncated credit card number for display.
commerce_cardonfile_form_alter Implements hook_form_alter().
commerce_cardonfile_form_commerce_payment_order_transaction_add_form_alter Implements hook_form_FORM_ID_alter().
commerce_cardonfile_get_properties Entity Metadata getter callback.
commerce_cardonfile_hook_info Implements hook_hook_info().
commerce_cardonfile_load Loads a card by ID.
commerce_cardonfile_load_credit_card_helpers Loads credit card helper functions in commerce_payment
commerce_cardonfile_load_multiple Loads multiple cards by ID or based on a set of matching conditions.
commerce_cardonfile_load_multiple_by_uid Loads multiple cards belonging to a specific user.
commerce_cardonfile_menu Implements hook_menu().
commerce_cardonfile_new Returns an initialized card entity.
commerce_cardonfile_order_can_charge_card Returns TRUE if the card can be charged for the given order
commerce_cardonfile_order_charge_card Process a charge for a given an order
commerce_cardonfile_order_select_card Select the card on file that can be charged for an order.
commerce_cardonfile_payment_method_available_callbacks Returns an array of all available payment method cardonfile callbacks
commerce_cardonfile_payment_method_callback Returns the Card on File callback function for the given payment method.
commerce_cardonfile_payment_method_implements Returns all payment method instances that implement a specific callback
commerce_cardonfile_payment_terminal_ajax Ajax callback for payment terminal card on file operations.
commerce_cardonfile_permission Implements hook_permission().
commerce_cardonfile_save Saves a card.
commerce_cardonfile_set_default_card Deprecated Set a card's instance default value.
commerce_cardonfile_statuses Returns a list of card statuses.
commerce_cardonfile_theme Implements hook_theme().
commerce_cardonfile_user_access Determines if the current user has access to the account's stored cards.
commerce_cardonfile_validate_card_expiration Validates the card expiration date.
commerce_cardonfile_views_api Implements hook_views_api().
commerce_cardonfile_views_pre_render Implement hook_views_pre_render().
_commerce_cardonfile_capable_payment_methods Returns all payment methods that are card on file capable
_commerce_cardonfile_capable_payment_method_check Returns TRUE if a payment method is capable of card on file
_commerce_cardonfile_payment_method_instances Returns all payment method instances for a given payment method id

Constants