commerce_cardonfile.module in Commerce Card on File 7.2
Same filename and directory in other branches
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.moduleView 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;
}