You are here

uc_credit.module in Ubercart 6.2

Defines the credit card payment method and hooks in payment gateways.

File

payment/uc_credit/uc_credit.module
View source
<?php

/**
 * @file
 * Defines the credit card payment method and hooks in payment gateways.
 */

/** Just authorize an amount on a credit card account. */
define('UC_CREDIT_AUTH_ONLY', 'authorize');

/** Capture funds from a prior authorization. */
define('UC_CREDIT_PRIOR_AUTH_CAPTURE', 'prior_auth_capture');

/** Authorize and capture money all at once. */
define('UC_CREDIT_AUTH_CAPTURE', 'auth_capture');

/** Set up a credit card reference through the payment gateway. */
define('UC_CREDIT_REFERENCE_SET', 'reference_set');

/** Capture funds using a credit card reference. */
define('UC_CREDIT_REFERENCE_TXN', 'reference_txn');

/** Remove a reference from the payment gateway. */
define('UC_CREDIT_REFERENCE_REMOVE', 'reference_remove');

/** Credit funds to a reference at the payment gateway. */
define('UC_CREDIT_REFERENCE_CREDIT', 'reference_credit');

/** Credit funds to a credit card account. */
define('UC_CREDIT_CREDIT', 'credit');

/** Void a transaction before the transaction clears. */
define('UC_CREDIT_VOID', 'void');

/** Name of encryption key file. */
define('UC_CREDIT_KEYFILE_NAME', 'uc_credit.key');

/*******************************************************************************
 * Hook Functions (Drupal)
 ******************************************************************************/

/**
 * Implements hook_menu().
 */
function uc_credit_menu() {
  $items['cart/checkout/credit/cvv_info'] = array(
    'title' => 'CVV information',
    'page callback' => 'uc_credit_cvv_info',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'uc_credit.pages.inc',
  );
  $items['admin/store/orders/%uc_order/credit'] = array(
    'title callback' => 'uc_credit_terminal_title',
    'title arguments' => array(
      3,
    ),
    'description' => 'Displays a form to process a credit card payment.',
    'page callback' => 'uc_credit_terminal',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'process credit cards',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'uc_credit.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_perm().
 */
function uc_credit_perm() {
  return array(
    'administer credit cards',
    'view cc details',
    'view cc numbers',
    'process credit cards',
  );
}

/**
 * Implements hook_theme().
 */
function uc_credit_theme() {
  return array(
    'uc_credit_cvv_help' => array(
      'arguments' => array(),
      'file' => 'uc_credit.theme',
    ),
    'uc_payment_method_credit_form' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_init().
 */
function uc_credit_init() {
  global $conf;
  $conf['i18n_variables'][] = 'uc_credit_fail_message';
  $conf['i18n_variables'][] = 'uc_credit_policy';
}

/**
 * Implements hook_form_alter().
 */
function uc_credit_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'uc_payment_methods_form':
      if (user_access('administer credit cards')) {
        if (empty($_POST) && !uc_credit_encryption_key()) {

          // Report error and expand fieldset so the problem is easy to spot
          form_set_error('uc_credit_encryption_path', t('Credit card encryption must be configured to accept credit card payments.'));
          $form['method_credit']['#collapsed'] = FALSE;
        }
        $form['#validate'][] = 'uc_credit_settings_form_validate';
        $form['#submit'][] = 'uc_credit_settings_form_submit';
      }
      break;
    case 'uc_cart_checkout_form':
      if (isset($_POST['cc_number'])) {
        $order = new stdClass();
        uc_payment_method_credit('cart-process', $order, TRUE);
      }

      // Cache the CC details for use in other functions.
      if (isset($_SESSION['sescrd'])) {
        uc_credit_cache('save', $_SESSION['sescrd']);

        // Store the encrypted details to the form for processing on submit.
        $form['payment_details_data'] = array(
          '#type' => 'hidden',
          '#value' => $_SESSION['sescrd'],
        );

        // Clear the session of the details.
        unset($_SESSION['sescrd']);
      }
      unset($_SESSION['cc_pay']);
      break;
    case 'uc_cart_checkout_review_form':

      // Check if the customer paid by CC and refreshed on the review page.
      if (isset($_SESSION['cc_pay']) && !isset($_SESSION['sescrd']) && empty($_POST['sescrd'])) {

        // Send them back to the checkout form to put in their details again.
        drupal_set_message(t('To protect our customers from identity theft, credit card details are erased when a browser refreshes on the checkout review page.  Please enter your card details again and re-submit the form.'), 'error');
        $_SESSION['clear_cc'] = TRUE;
        unset($_SESSION['cc_pay']);
        drupal_goto('cart/checkout');
      }
      if (isset($_SESSION['sescrd'])) {

        // Cache the CC details for use in other functions.
        uc_credit_cache('save', $_SESSION['sescrd']);

        // Store the encrypted details to the form for processing on submit.
        $form['sescrd'] = array(
          '#type' => 'hidden',
          '#value' => base64_encode($_SESSION['sescrd']),
        );

        // Clear the session of the details.
        unset($_SESSION['sescrd']);
      }
      else {
        $form['sescrd'] = array(
          '#type' => 'hidden',
          '#value' => '',
        );
      }

      // Add submit handler to preserve CC details for the back button and
      // failed order submissions.
      $form['back']['#submit'][] = 'uc_credit_cart_review_back_submit';

      // Reconstruct the submit handler array for before and after processing.
      $submit = array_merge(array(
        'uc_credit_cart_review_pre_form_submit',
      ), $form['#submit']);
      $submit[] = 'uc_credit_cart_review_post_form_submit';
      $form['#submit'] = $submit;
      break;
    case 'uc_payment_gateways_form':

      // Loop through each of the gateways on the form.
      foreach (element_children($form['gateways']) as $key) {

        // Get the transaction types associated with this gateway.
        $gateway_types = uc_credit_gateway_txn_types($key);

        // Loop through all the available transaction types.
        $options = array();
        $txn_types = array(
          UC_CREDIT_AUTH_ONLY => t('Authorization only'),
          UC_CREDIT_AUTH_CAPTURE => t('Authorize and capture immediately'),
          UC_CREDIT_REFERENCE_SET => t('Set a reference only'),
        );
        foreach ($txn_types as $type => $title) {

          // Add the current one to the options if the gateway supports it.
          if (in_array($type, $gateway_types)) {
            $options[$type] = $title;
          }
        }
        $form['gateways'][$key]['uc_pg_' . $key . '_cc_txn_type'] = array(
          '#type' => 'radios',
          '#title' => t('Default credit transaction type'),
          '#description' => t('Only available transaction types are listed. The default will be used unless an administrator chooses otherwise through the terminal.'),
          '#options' => $options,
          '#default_value' => variable_get('uc_pg_' . $key . '_cc_txn_type', UC_CREDIT_AUTH_CAPTURE),
          '#weight' => -5,
        );
      }
  }
}

/**
 * Implements hook_cron().
 */
function uc_credit_cron() {

  // Truncate stored data when in debug per the credit card method settings.
  if (variable_get('uc_credit_debug', FALSE)) {
    $time = strtotime(variable_get('uc_credit_number_duration', '1') . ' ' . variable_get('uc_credit_number_unit', 'years') . ' ago');
    $key = uc_credit_encryption_key();
    $crypt = new uc_encryption_class();
    $result = db_query("SELECT order_id FROM {uc_orders} WHERE payment_method = 'credit' AND modified <= %d AND order_status = '%s'", $time, variable_get('uc_credit_clear_status', uc_order_state_default('completed')));
    while ($order = db_fetch_array($result)) {

      // Load up the existing data array.
      $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $order['order_id']));
      $data = unserialize($data);
      $cache = uc_credit_cache('save', $data['cc_data']);

      // Save only some limited, PCI compliant data.
      $cache['cc_number'] = substr($cache['cc_number'], -4);
      unset($cache['cc_cvv']);
      $data['cc_data'] = $crypt
        ->encrypt(uc_credit_encryption_key(), base64_encode(serialize($cache)));
      uc_store_encryption_errors($crypt, 'uc_credit');

      // Save it again.
      db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order['order_id']);
    }
  }
}

/**
 * Implements hook_exit().
 */
function uc_credit_exit() {

  // Make sure sensitive checkout session data doesn't persist on other pages.
  if (isset($_SESSION['sescrd'])) {
    if (isset($_GET['q'])) {

      // Separate the args ourself since the arg() function may not be loaded.
      $args = explode('/', $_GET['q']);
      if (!isset($args[1]) || $args[1] != 'checkout') {
        unset($_SESSION['sescrd']);
      }
    }
    else {
      unset($_SESSION['sescrd']);
    }
  }
}

/*******************************************************************************
 * Hook Functions (Ubercart)
 ******************************************************************************/

/**
 * Implements hook_store_status().
 */
function uc_credit_store_status() {

  // Throw up an error row if encryption has not been set up yet.
  if ($key = uc_credit_encryption_key()) {
    $statuses[] = array(
      'status' => 'ok',
      'title' => t('Credit card encryption'),
      'desc' => t('Credit card data is encrypted during checkout for maximum security.'),
    );
  }
  else {
    $statuses[] = array(
      'status' => 'error',
      'title' => t('Credit card encryption'),
      'desc' => t('You must review your <a href="!url">credit card security settings</a> and enable encryption before you can accept credit card payments.', array(
        '!url' => url('admin/store/settings/payment/edit/methods'),
      )),
    );
  }
  if (variable_get('uc_credit_debug', FALSE)) {
    $statuses[] = array(
      'status' => 'warning',
      'title' => t('Credit card debug mode'),
      'desc' => t('Because you are using debug mode, credit card details may be stored in violation of PCI security standards. Debug mode is only recommended for testing transactions with fake credit card details.'),
    );
  }
  return $statuses;
}

/**
 * Implements hook_order().
 */
function uc_credit_order($op, $arg1, $arg2) {

  // Set up the encryption key and object for saving and loading.
  if (isset($arg1->payment_method) && $arg1->payment_method == 'credit' && ($op == 'save' || $op == 'load')) {

    // Log an error if encryption isn't configured properly.
    if (!uc_credit_encryption_key()) {
      watchdog('uc_credit', 'Credit card encryption must be set up to process credit cards.');
    }
  }
  switch ($op) {
    case 'submit':
      if ($arg1->payment_method == 'credit') {

        // Clear out that session variable denoting this as a CC paid order.
        unset($_SESSION['cc_pay']);

        // Process CC transactions when an order is submitted after review.
        if (variable_get('uc_credit_checkout_process', TRUE)) {

          // Stuff the transaction type into the data array.
          $gateway_id = uc_credit_default_gateway();
          $data = array(
            'txn_type' => variable_get('uc_pg_' . $gateway_id . '_cc_txn_type', UC_CREDIT_AUTH_CAPTURE),
          );

          // Attempt to process the CC payment.
          $pass = uc_payment_process('credit', $arg1->order_id, $arg1->order_total, $data, TRUE, NULL, FALSE);

          // If the payment failed, store the data back in the session and
          // halt the checkout process.
          if (!$pass) {
            $message = variable_get('uc_credit_fail_message', t('We were unable to process your credit card payment. Please verify your details and try again.  If the problem persists, contact us to complete your order.'));
            return array(
              array(
                'pass' => FALSE,
                'message' => $message,
              ),
            );
          }
        }
        elseif (variable_get('uc_credit_debug', FALSE)) {

          // If we aren't set to process transactions immediately and we're in
          // debug mode, store the CC data in the order so it can be viewed
          // later for test processing.
          $cc_data = $arg1->payment_details;
          _save_cc_data_to_order($cc_data, $arg1->order_id);
        }
      }
      break;
    case 'save':
      if ($arg1->payment_method == 'credit') {

        // Build an array of CC data to store with the order.
        if (!empty($arg1->payment_details)) {

          // Check for debug mode.
          if (variable_get('uc_credit_debug', FALSE) && arg(1) != 'checkout') {

            // If enabled, store the full payment details.
            $cc_data = $arg1->payment_details;
          }
          else {

            // Save only some limited, PCI compliant data.
            $cc_data = $arg1->payment_details;
            $cc_data['cc_number'] = substr($cc_data['cc_number'], -4);
            unset($cc_data['cc_cvv']);
          }
          _save_cc_data_to_order($cc_data, $arg1->order_id);
        }
      }
      break;
    case 'load':
      if ($arg1->payment_method == 'credit') {

        // Load the CC details from the credit cache if available.
        $arg1->payment_details = uc_credit_cache('load');

        // Otherwise load any details that might be stored in the data array.
        if (empty($arg1->payment_details) && isset($arg1->data['cc_data'])) {
          $arg1->payment_details = uc_credit_cache('save', $arg1->data['cc_data']);
        }
      }
      break;
  }
}

/**
 * Implements hook_payment_method().
 */
function uc_credit_payment_method() {
  if (arg(0) == 'cart' && uc_credit_encryption_key() === FALSE) {
    return;
  }
  $path = base_path() . drupal_get_path('module', 'uc_credit');
  $title = t('Credit card:');
  $cc_types = array(
    'visa' => t('Visa'),
    'mastercard' => t('MasterCard'),
    'discover' => t('Discover'),
    'amex' => t('American Express'),
  );
  foreach ($cc_types as $type => $label) {
    if (variable_get('uc_credit_' . $type, TRUE)) {
      $title .= ' ' . theme('image', drupal_get_path('module', 'uc_credit') . '/images/' . $type . '.gif', $label, '', array(
        'class' => 'uc-credit-cctype uc-credit-cctype-' . $type,
      ));
    }
  }
  $methods[] = array(
    'id' => 'credit',
    'name' => t('Credit card'),
    'title' => $title,
    'desc' => t('Pay by credit card.'),
    'callback' => 'uc_payment_method_credit',
    'weight' => 2,
    'checkout' => TRUE,
  );
  return $methods;
}

/*******************************************************************************
 * Callback Functions, Forms, and Tables
 ******************************************************************************/

/**
 * Callback function for the Credit Card payment method.
 */
function uc_payment_method_credit($op, &$arg1, $silent = FALSE) {
  switch ($op) {
    case 'cart-details':
      $details = drupal_get_form('uc_payment_method_credit_form', $arg1);
      return uc_strip_form($details);
    case 'cart-process':

      // Fetch the CC details from the $_POST directly.
      $cc_data = array(
        'cc_type' => isset($_POST['cc_type']) ? $_POST['cc_type'] : '',
        'cc_owner' => isset($_POST['cc_owner']) ? $_POST['cc_owner'] : '',
        'cc_number' => str_replace(' ', '', $_POST['cc_number']),
        'cc_start_month' => isset($_POST['cc_start_month']) ? $_POST['cc_start_month'] : '',
        'cc_start_year' => isset($_POST['cc_start_year']) ? $_POST['cc_start_year'] : '',
        'cc_exp_month' => $_POST['cc_exp_month'],
        'cc_exp_year' => $_POST['cc_exp_year'],
        'cc_issue' => isset($_POST['cc_issue']) ? $_POST['cc_issue'] : '',
        'cc_cvv' => $_POST['cc_cvv'],
        'cc_bank' => isset($_POST['cc_bank']) ? $_POST['cc_bank'] : '',
      );

      // Recover cached CC data in $_POST if it exists.
      if (isset($_POST['payment_details_data'])) {
        $cache = uc_credit_cache('save', $_POST['payment_details_data']);
      }

      // Account for partial CC numbers when masked by the system.
      if (substr($cc_data['cc_number'], 0, strlen(t('(Last4)'))) == t('(Last4)')) {

        // Recover the number from the encrypted data in $_POST if truncated.
        if (isset($cache['cc_number'])) {
          $cc_data['cc_number'] = $cache['cc_number'];
        }
        else {
          $cc_data['cc_number'] = '';
        }
      }

      // Account for masked CVV numbers.
      if (!empty($cc_data['cc_cvv']) && $cc_data['cc_cvv'] == str_repeat('-', strlen($cc_data['cc_cvv']))) {

        // Recover the number from the encrypted data in $_POST if truncated.
        if (isset($cache['cc_cvv'])) {
          $cc_data['cc_cvv'] = $cache['cc_cvv'];
        }
        else {
          $cc_data['cc_cvv'] = '';
        }
      }

      // Go ahead and put the CC data in the payment details array.
      $arg1->payment_details = $cc_data;

      // Default our value for validation.
      $return = TRUE;

      // Make sure an owner value was entered.
      if (variable_get('uc_credit_owner_enabled', FALSE) && empty($cc_data['cc_owner'])) {
        if (!$silent) {
          drupal_set_message(t('Enter the owner name as it appears on the card.'), 'error');
        }
        $return = FALSE;
      }

      // Validate the CC number if that's turned on/check for non-digits.
      if (variable_get('uc_credit_validate_numbers', TRUE) && !_valid_card_number($cc_data['cc_number']) || !ctype_digit($cc_data['cc_number'])) {
        if (!$silent) {
          drupal_set_message(t('You have entered an invalid credit card number.'), 'error');
        }
        $return = FALSE;
      }

      // Validate the start date (if entered).
      if (variable_get('uc_credit_start_enabled', FALSE) && !_valid_card_start($cc_data['cc_start_month'], $cc_data['cc_start_year'])) {
        if (!$silent) {
          drupal_set_message(t('The start date you entered is invalid.'), 'error');
        }
        $return = FALSE;
      }

      // Validate the card expiration date.
      if (!_valid_card_expiration($cc_data['cc_exp_month'], $cc_data['cc_exp_year'])) {
        if (!$silent) {
          drupal_set_message(t('The credit card you entered has expired.'), 'error');
        }
        $return = FALSE;
      }

      // Validate the issue number (if entered).  With issue numbers, '01' is
      // different from '1', but is_numeric() is still appropriate.
      if (variable_get('uc_credit_issue_enabled', FALSE) && !_valid_card_issue($cc_data['cc_issue'])) {
        if (!$silent) {
          drupal_set_message(t('The issue number you entered is invalid.'), 'error');
        }
        $return = FALSE;
      }

      // Validate the CVV number if enabled.
      if (variable_get('uc_credit_cvv_enabled', TRUE) && !_valid_cvv($cc_data['cc_cvv'])) {
        if (!$silent) {
          drupal_set_message(t('You have entered an invalid CVV number.'), 'error');
        }
        $return = FALSE;
      }

      // Validate the bank name if enabled.
      if (variable_get('uc_credit_bank_enabled', FALSE) && empty($cc_data['cc_bank'])) {
        if (!$silent) {
          drupal_set_message(t('You must enter the issuing bank for that card.'), 'error');
        }
        $return = FALSE;
      }

      // Initialize the encryption key and class.
      $key = uc_credit_encryption_key();
      $crypt = new uc_encryption_class();

      // Store the encrypted details in the session for the next pageload.
      // We are using base64_encode() because the encrypt function works with a
      // limited set of characters, not supporting the full Unicode character
      // set or even extended ASCII characters that may be present.
      // base64_encode() converts everything to a subset of ASCII, ensuring that
      // the encryption algorithm does not mangle names.
      $_SESSION['sescrd'] = $crypt
        ->encrypt($key, base64_encode(serialize($arg1->payment_details)));

      // Log any errors to the watchdog.
      uc_store_encryption_errors($crypt, 'uc_credit');

      // If we're going to the review screen, set a variable that lets us know
      // we're paying by CC.
      if ($return) {
        $_SESSION['cc_pay'] = TRUE;
      }
      return $return;
    case 'cart-review':
      if (variable_get('uc_credit_type_enabled', FALSE)) {
        $review[] = array(
          'title' => t('Card Type'),
          'data' => check_plain($arg1->payment_details['cc_type']),
        );
      }
      if (variable_get('uc_credit_owner_enabled', FALSE)) {
        $review[] = array(
          'title' => t('Card Owner'),
          'data' => check_plain($arg1->payment_details['cc_owner']),
        );
      }
      $review[] = array(
        'title' => t('Card Number'),
        'data' => uc_credit_display_number($arg1->payment_details['cc_number']),
      );
      if (variable_get('uc_credit_start_enabled', FALSE)) {
        $start = check_plain($arg1->payment_details['cc_start_month'] . '/' . $arg1->payment_details['cc_start_year']);
        $review[] = array(
          'title' => t('Start Date'),
          'data' => strlen($start) > 1 ? $start : '',
        );
      }
      $review[] = array(
        'title' => t('Expiration'),
        'data' => check_plain($arg1->payment_details['cc_exp_month'] . '/' . $arg1->payment_details['cc_exp_year']),
      );
      if (variable_get('uc_credit_issue_enabled', FALSE)) {
        $review[] = array(
          'title' => t('Issue Number'),
          'data' => user_access('view cc numbers') ? check_plain($arg1->payment_details['cc_issue']) : str_repeat('-', strlen($arg1->payment_details['cc_issue'])),
        );
      }
      if (variable_get('uc_credit_cvv_enabled', TRUE)) {
        $review[] = array(
          'title' => t('CVV'),
          'data' => user_access('view cc numbers') ? check_plain($arg1->payment_details['cc_cvv']) : str_repeat('-', strlen($arg1->payment_details['cc_cvv'])),
        );
      }
      if (variable_get('uc_credit_bank_enabled', FALSE)) {
        $review[] = array(
          'title' => t('Issuing Bank'),
          'data' => check_plain($arg1->payment_details['cc_bank']),
        );
      }
      return $review;
    case 'order-view':
      $output = '';

      // Add the hidden span for the CC details if possible.
      if (user_access('view cc details')) {
        drupal_add_js(drupal_get_path('module', 'uc_credit') . '/uc_credit.js');
        $output .= '<span onclick="toggle_card_details();" style="cursor: pointer;">' . '<a id="cc_details_title" href="" onclick="return false;" style="display: none;">' . t('View card details.') . '</a>';
        $output .= '<span id="cc_details"><table style="width: auto;">';
        if (variable_get('uc_credit_type_enabled', TRUE)) {
          $type = check_plain($arg1->payment_details['cc_type']);
          if (strlen($type) > 0) {
            $output .= '<tr><td>' . t('Card Type:') . ' </td><td>' . $type . '</td></tr>';
          }
        }
        if (variable_get('uc_credit_owner_enabled', FALSE) && isset($arg1->payment_details['cc_owner'])) {
          $owner = check_plain($arg1->payment_details['cc_owner']);
          if (strlen($owner) > 0) {
            $output .= '<tr><td>' . t('Card Owner:') . ' </td><td>' . $owner . '</td></tr>';
          }
        }
        $output .= '<tr><td>' . t('Card Number:') . ' </td><td>' . uc_credit_display_number($arg1->payment_details['cc_number']) . '</td></tr>';
        $exp = check_plain($arg1->payment_details['cc_exp_month'] . '/' . $arg1->payment_details['cc_exp_year']);
        if (strlen($exp) > 1) {
          $output .= '<tr><td>' . t('Expiration:') . ' </td><td>' . $exp . '</td></tr>';
        }
        if (variable_get('uc_credit_debug', FALSE)) {
          if (variable_get('uc_credit_start_enabled', FALSE) && isset($arg1->payment_details['cc_start_month']) && isset($arg1->payment_details['cc_start_year'])) {
            $start = check_plain($arg1->payment_details['cc_start_month'] . '/' . $arg1->payment_details['cc_start_year']);
            if (strlen($start) > 1) {
              $output .= '<tr><td>' . t('Start Date:') . ' </td><td>' . $start . '</td></tr>';
            }
          }
          if (variable_get('uc_credit_issue_enabled', FALSE) && isset($arg1->payment_details['cc_issue'])) {
            $issue = check_plain($arg1->payment_details['cc_issue']);
            if (strlen($issue) > 0) {
              $output .= '<tr><td>' . t('Issue Number:') . ' </td><td>' . $issue . '</td></tr>';
            }
          }
          if (variable_get('uc_credit_cvv_enabled', TRUE) && isset($arg1->payment_details['cc_cvv'])) {
            $cvv = user_access('view cc numbers') ? check_plain($arg1->payment_details['cc_cvv']) : str_repeat('-', strlen($arg1->payment_details['cc_cvv']));
            if (strlen($cvv) > 0) {
              $output .= '<tr><td>' . t('CVV:') . ' </td><td>' . $cvv . '</td></tr>';
            }
          }
          if (variable_get('uc_credit_bank_enabled', TRUE) && isset($arg1->payment_details['cc_bank'])) {
            $bank = check_plain($arg1->payment_details['cc_bank']);
            if (strlen($bank) > 0) {
              $output .= '<tr><td>' . t('Issuing Bank:') . ' </td><td>' . $bank . '</td></tr>';
            }
          }
        }
        $output .= '</table></span></span>';

        // Add the form to process the card if applicable.
        if (user_access('process credit cards')) {
          $output .= drupal_get_form('uc_credit_order_view_form', $arg1->order_id);
        }
      }
      return $output;
    case 'customer-view':
      $output = t('Card Number:') . '<br />' . uc_credit_display_number($arg1->payment_details['cc_number'], TRUE);
      return $output;
    case 'order-details':
      if (variable_get('uc_credit_debug', FALSE)) {
        $details = drupal_get_form('uc_payment_method_credit_form', $arg1);
        return uc_strip_form($details);
      }
      else {
        return t('Use the terminal available through the<br />%button button on the View tab to<br />process credit card payments.', array(
          '%button' => t('Process card'),
        ));
      }
    case 'edit-process':
      if (!variable_get('uc_credit_debug', FALSE)) {
        return;
      }
      $cache = uc_credit_cache('load');
      $changes['payment_details']['cc_type'] = $_POST['cc_type'];
      $changes['payment_details']['cc_owner'] = $_POST['cc_owner'];
      if (strpos($_POST['cc_number'], t('(Last 4) ')) !== 0) {
        $changes['payment_details']['cc_number'] = $_POST['cc_number'];
      }
      else {
        $changes['payment_details']['cc_number'] = $cache['cc_number'];
      }
      $changes['payment_details']['cc_exp_month'] = $_POST['cc_exp_month'];
      $changes['payment_details']['cc_exp_year'] = $_POST['cc_exp_year'];
      if ($_POST['cc_cvv'] !== str_repeat('-', strlen($_POST['cc_cvv']))) {
        $changes['payment_details']['cc_cvv'] = $_POST['cc_cvv'];
      }
      else {
        $changes['payment_details']['cc_cvv'] = isset($cache['cc_cvv']) ? $cache['cc_cvv'] : '';
      }
      $changes['payment_details']['cc_bank'] = $_POST['cc_bank'];
      return $changes;
    case 'settings':
      if (!user_access('administer credit cards')) {
        $form['notice'] = array(
          '#value' => '<div>' . t('You must have access to <b>administer credit cards</b> to adjust these settings.') . '</div>',
        );
        return $form;
      }

      // Form elements that deal specifically with card number security.
      $form['cc_security'] = array(
        '#type' => 'fieldset',
        '#title' => t('Credit card data security'),
        '#description' => t('You are responsible for the security of your website, including the protection of credit card numbers.  Please be aware that choosing some settings in this section may decrease the security of credit card data on your website and increase your liability for damages in the case of fraud.'),
        '#collapsible' => FALSE,
      );
      $form['cc_security']['uc_credit_encryption_path'] = array(
        '#type' => 'textfield',
        '#title' => t('Encryption key directory'),
        '#description' => t('The card type, expiration date and last four digits of the card number are encrypted and stored temporarily while the customer is in the process of checking out.<br /><b>You must enable encryption</b> by following the <a href="!url">encryption instructions</a> in order to accept credit card payments.<br />In short, you must enter the path of a directory outside of your document root where the encryption key may be stored.<br />Relative paths will be resolved relative to the Drupal installation directory.<br />Once this directory is set, you should not change it.', array(
          '!url' => 'http://drupal.org/node/1309226',
        )),
        '#default_value' => uc_credit_encryption_key() ? variable_get('uc_credit_encryption_path', t('Not configured.')) : t('Not configured.'),
      );
      $form['cc_security']['uc_credit_debug'] = array(
        '#type' => 'checkbox',
        '#title' => t('Operate in credit card debug mode.'),
        '#description' => t('In debug mode, the full credit card number is stored which may be in violation of PCI security standards.<br />Debug mode is only recommended for testing transactions with fake credit card details.'),
        '#default_value' => variable_get('uc_credit_debug', FALSE),
      );

      // Form elements that deal with the credit card workflow during checkout.
      $form['cc_workflow'] = array(
        '#type' => 'fieldset',
        '#title' => t('Checkout workflow'),
        '#description' => t('These settings alter the way credit card data is collected and used during checkout.'),
        '#collapsible' => FALSE,
      );
      $form['cc_workflow']['uc_credit_validate_numbers'] = array(
        '#type' => 'checkbox',
        '#title' => t('Validate credit card numbers at checkout.'),
        '#description' => t('Invalid card numbers will show an error message to the user so they can correct it.<br />This feature is recommended unless you are in debug mode.'),
        '#default_value' => variable_get('uc_credit_validate_numbers', TRUE),
      );
      $form['cc_workflow']['uc_credit_checkout_process'] = array(
        '#type' => 'checkbox',
        '#title' => t('Attempt to process credit card payments at checkout.'),
        '#description' => t('Failed attempts will prevent checkout completion and display the error message from above.<br />This box must be checked to process customer credit cards if you are not in debug mode.'),
        '#default_value' => variable_get('uc_credit_checkout_process', TRUE),
      );

      // Form elements to handle the automatic clearing of card data.
      $form['cc_clear'] = array(
        '#type' => 'fieldset',
        '#title' => t('Debug mode data clearing'),
        '#description' => t('Specify below the status and age of orders whose credit card details will be removed.  This setting only applies when operating in debug mode. <strong>Cron must be running for this feature to work.</strong>', array(
          '!url' => url('admin/store/settings/cart/edit'),
        )),
        '#collapsible' => FALSE,
      );
      foreach (uc_order_status_list() as $status) {
        $options[$status['id']] = $status['title'];
      }
      $form['cc_clear']['uc_credit_clear_status'] = array(
        '#type' => 'select',
        '#title' => t('Order status'),
        '#options' => $options,
        '#default_value' => variable_get('uc_credit_clear_status', uc_order_state_default('completed')),
        '#prefix' => '<div style="float: left; margin-right: 1em;">',
        '#suffix' => '</div>',
      );
      $form['cc_clear']['uc_credit_number_duration'] = array(
        '#type' => 'select',
        '#title' => t('Age'),
        '#options' => drupal_map_assoc(range(1, 24)),
        '#default_value' => variable_get('uc_credit_number_duration', '3'),
        '#prefix' => '<div style="float: left; margin-right: 1em;">',
        '#suffix' => '</div>',
      );
      $form['cc_clear']['uc_credit_number_unit'] = array(
        '#type' => 'select',
        '#title' => t('Unit of time'),
        '#options' => array(
          'hours' => t('hour(s)'),
          'days' => t('day(s)'),
          'weeks' => t('week(s)'),
          'years' => t('year(s)'),
        ),
        '#default_value' => variable_get('uc_credit_number_unit', 'days'),
        '#prefix' => '<div style="float: left;">',
        '#suffix' => '</div>',
      );

      // Form elements that deal with the type of data requested at checkout.
      $form['cc_fields'] = array(
        '#type' => 'fieldset',
        '#title' => t('Credit card fields'),
        '#description' => t('Specify what information to collect from customers in addition to the card number.'),
        '#collapsible' => FALSE,
      );
      $form['cc_fields']['uc_credit_cvv_enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable CVV text field on checkout form.'),
        '#description' => t('The CVV is an added security measure on credit cards. On Visa, Mastercard, and Discover cards it is a three digit number, and on AmEx cards it is a four digit number. If your credit card processor or payment gateway requires this information, you should enable this feature here.'),
        '#default_value' => variable_get('uc_credit_cvv_enabled', TRUE),
      );
      $form['cc_fields']['uc_credit_owner_enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable card owner text field on checkout form.'),
        '#default_value' => variable_get('uc_credit_owner_enabled', FALSE),
      );
      $form['cc_fields']['uc_credit_start_enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable card start date on checkout form.'),
        '#default_value' => variable_get('uc_credit_start_enabled', FALSE),
      );
      $form['cc_fields']['uc_credit_issue_enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable card issue number text field on checkout form.'),
        '#default_value' => variable_get('uc_credit_issue_enabled', FALSE),
      );
      $form['cc_fields']['uc_credit_bank_enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable issuing bank text field on checkout form.'),
        '#default_value' => variable_get('uc_credit_bank_enabled', FALSE),
      );
      $form['cc_fields']['uc_credit_type_enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable card type selection on checkout form.'),
        '#description' => t('If enabled, specify in the textarea below which card options to populate the select box with.'),
        '#default_value' => variable_get('uc_credit_type_enabled', FALSE),
      );
      $form['cc_fields']['uc_credit_accepted_types'] = array(
        '#type' => 'textarea',
        '#title' => t('Card type select box options'),
        '#description' => t('Enter one card type per line. These fields will populate the card type select box if it is enabled.'),
        '#default_value' => variable_get('uc_credit_accepted_types', implode("\r\n", array(
          t('Visa'),
          t('Mastercard'),
          t('Discover'),
          t('American Express'),
        ))),
        '#wysiwyg' => FALSE,
      );

      // From elements that deal with card types accepted.
      $form['cc_types'] = array(
        '#type' => 'fieldset',
        '#title' => t('Accepted card types (for validation)'),
        '#description' => t('Use the checkboxes to specify which card types you accept for payment. Selected card types will show their icons in the payment method selection list and be used for card number validation.'),
      );
      $form['cc_types']['uc_credit_visa'] = array(
        '#type' => 'checkbox',
        '#title' => t('Visa'),
        '#default_value' => variable_get('uc_credit_visa', TRUE),
      );
      $form['cc_types']['uc_credit_mastercard'] = array(
        '#type' => 'checkbox',
        '#title' => t('Mastercard'),
        '#default_value' => variable_get('uc_credit_mastercard', TRUE),
      );
      $form['cc_types']['uc_credit_discover'] = array(
        '#type' => 'checkbox',
        '#title' => t('Discover'),
        '#default_value' => variable_get('uc_credit_discover', TRUE),
      );
      $form['cc_types']['uc_credit_amex'] = array(
        '#type' => 'checkbox',
        '#title' => t('American Express'),
        '#default_value' => variable_get('uc_credit_amex', TRUE),
      );

      // Form elements that deal with credit card messages to customers.
      $form['cc_messages'] = array(
        '#type' => 'fieldset',
        '#title' => t('Customer messages'),
        '#description' => t('Here you can alter messages displayed to customers using credit cards.'),
        '#collapsible' => FALSE,
      );
      $form['cc_messages']['uc_credit_policy'] = array(
        '#type' => 'textarea',
        '#title' => t('Credit card payment policy'),
        '#description' => t('Instructions for customers on the checkout page above the credit card fields.'),
        '#default_value' => variable_get('uc_credit_policy', t('Your billing information must match the billing address for the credit card entered below or we will be unable to process your payment.')),
        '#rows' => 3,
      );
      $form['cc_messages']['uc_credit_fail_message'] = array(
        '#type' => 'textarea',
        '#title' => t('Card processing failure message'),
        '#description' => t('Error message displayed to customers when an attempted payment fails at checkout.'),
        '#default_value' => variable_get('uc_credit_fail_message', t('We were unable to process your credit card payment. Please verify your details and try again.  If the problem persists, contact us to complete your order.')),
      );
      return $form;
  }
}

/**
 * Validates the encryption key directory and key file.
 *
 * Checks that the encryption key directory has been specified, that it
 * exists, and is readable. and writeable so
 *
 * @see uc_credit_settings_form()
 * @see uc_credit_settings_form_submit()
 */
function uc_credit_settings_form_validate($form, &$form_state) {

  // Trim trailing whitespace and any trailing / or \ from the key path name.
  $key_path = rtrim(trim($form_state['values']['uc_credit_encryption_path']), '/\\');

  // Test to see if a path was entered.
  if (empty($key_path)) {
    form_set_error('uc_credit_encryption_path', t('Key path must be specified in security settings tab.'));
  }

  // Construct complete key file path.
  $key_file = $key_path . '/' . UC_CREDIT_KEYFILE_NAME;

  // Shortcut - test to see if we already have a usable key file.
  if (file_exists($key_file)) {
    if (is_readable($key_file)) {

      // Test contents - must contain 32-character hexadecimal string.
      $key = uc_credit_encryption_key();
      if ($key) {
        if (!preg_match("([0-9a-fA-F]{32})", $key)) {
          form_set_error('uc_credit_encryption_path', t('Key file already exists in directory, but it contains an invalid key.'));
        }
        else {

          // Key file exists and is valid, save result of trim() back into
          // $form_state and proceed to submit handler.
          $form_state['values']['uc_credit_encryption_path'] = $key_path;
          return;
        }
      }
    }
    else {
      form_set_error('uc_credit_encryption_path', t('Key file already exists in directory, but is not readable. Please verify the file permissions.'));
    }
  }

  // Check if directory exists and is writeable.
  if (is_dir($key_path)) {

    // The entered directory is valid and in need of a key file.
    // Flag this condition for the submit handler.
    $form_state['uc_credit']['update_cc_encrypt_dir'] = TRUE;

    // Can we open for writing?
    $file = @fopen($key_path . '/encrypt.test', 'w');
    if ($file === FALSE) {
      form_set_error('uc_credit_encryption_path', t('Cannot write to directory, please verify the directory permissions.'));
      unset($form_state['uc_credit']['update_cc_encrypt_dir']);
    }
    else {

      // Can we actually write?
      if (@fwrite($file, '0123456789') === FALSE) {
        form_set_error('uc_credit_encryption_path', t('Cannot write to directory, please verify the directory permissions.'));
        unset($form_state['uc_credit']['update_cc_encrypt_dir']);
        fclose($file);
      }
      else {

        // Can we read now?
        fclose($file);
        $file = @fopen($key_path . '/encrypt.test', 'r');
        if ($file === FALSE) {
          form_set_error('uc_credit_encryption_path', t('Cannot read from directory, please verify the directory permissions.'));
          unset($form_state['uc_credit']['update_cc_encrypt_dir']);
        }
        else {
          fclose($file);
        }
      }
      unlink($key_path . '/encrypt.test');
    }
  }
  else {

    // Directory doesn't exist.
    form_set_error('uc_credit_encryption_path', t('You have specified a non-existent directory.'));
  }

  // If validation succeeds, save result of trim() back into $form_state.
  $form_state['values']['uc_credit_encryption_path'] = $key_path;
}

/**
 * Creates the encryption key file if it doesn't already exist.
 *
 * @see uc_credit_settings_form()
 * @see uc_credit_settings_form_validate()
 */
function uc_credit_settings_form_submit($form, &$form_state) {

  // Check to see if we need to create a key file.
  if (isset($form_state['uc_credit']['update_cc_encrypt_dir']) && $form_state['uc_credit']['update_cc_encrypt_dir'] === TRUE) {
    $key_path = $form_state['values']['uc_credit_encryption_path'];
    $key_file = $key_path . '/' . UC_CREDIT_KEYFILE_NAME;
    if (!file_exists($key_file)) {
      if (!($file = fopen($key_file, 'wb'))) {
        drupal_set_message(t('Credit card encryption key file creation failed for file @file. Check your filepath settings and directory permissions.', array(
          '@file' => $key_file,
        )), 'error');
        watchdog('uc_credit', 'Credit card encryption key file creation failed for file @file. Check your filepath settings and directory permissions.', array(
          '@file' => $key_file,
        ), WATCHDOG_ERROR);
      }
      else {

        // Replacement key generation suggested by Barry Jaspan
        // for increased security.
        fwrite($file, md5(drupal_get_token(serialize($_REQUEST) . serialize($_SERVER) . time())));
        fclose($file);
        drupal_set_message(t('Credit card encryption key file generated. Card data will now be encrypted.'));
        watchdog('uc_credit', 'Credit card encryption key file generated. Card data will now be encrypted.');
      }
    }
  }
}

/**
 * Displays the credit card details form on the checkout screen.
 */
function uc_payment_method_credit_form($form_state, $order) {

  // Normally the CC data is posted in via AJAX.
  if (!empty($_POST['payment-details-data']) && arg(0) == 'cart') {
    $order->payment_details = uc_credit_cache('save', $_POST['payment-details-data']);
  }

  // But we have to accommodate failed checkout form validation here.
  if (isset($_SESSION['sescrd'])) {
    $order->payment_details = uc_credit_cache('save', $_SESSION['sescrd']);
    unset($_SESSION['sescrd']);
  }
  $form['cc_policy'] = array(
    '#value' => variable_get('uc_credit_policy', t('Your billing information must match the billing address for the credit card entered below or we will be unable to process your payment.')),
  );
  if (variable_get('uc_credit_type_enabled', FALSE)) {
    $types = variable_get('uc_credit_accepted_types', implode("\r\n", array(
      t('Visa'),
      t('Mastercard'),
      t('Discover'),
      t('American Express'),
    )));
    if (empty($types)) {
      $types = array(
        t('N/A'),
      );
    }
    else {
      $types = explode("\r\n", $types);
    }
    foreach ($types as $type) {
      $options[check_plain($type)] = $type;
    }
    $options_keys = array_keys($options);
    $form['cc_type'] = array(
      '#type' => 'select',
      '#title' => t('Card type'),
      '#options' => $options,
      '#default_value' => $order->payment_details['cc_type'] ? $order->payment_details['cc_type'] : array_shift($options_keys),
    );
  }
  if (variable_get('uc_credit_owner_enabled', FALSE)) {
    $form['cc_owner'] = array(
      '#type' => 'textfield',
      '#title' => t('Card owner'),
      '#default_value' => $order->payment_details['cc_owner'],
      '#attributes' => array(
        'autocomplete' => 'off',
      ),
      '#size' => 32,
      '#maxlength' => 64,
    );
  }

  // Set up the default CC number on the credit card form.
  if (isset($_SESSION['clear_cc']) || !isset($order->payment_details['cc_number'])) {
    $default_num = NULL;
  }
  elseif (variable_get('uc_credit_validate_numbers', TRUE) && !_valid_card_number($order->payment_details['cc_number'])) {

    // Display the number as is if it does not validate so it can be corrected.
    $default_num = $order->payment_details['cc_number'];
  }
  elseif (user_access('view cc numbers') && strlen($order->payment_details['cc_number']) > 4) {

    // Display the full number to those with access.
    $default_num = $order->payment_details['cc_number'];
  }
  else {

    // Otherwise default to the last 4 digits.
    $default_num = t('(Last 4) ') . substr($order->payment_details['cc_number'], -4);
  }
  $form['cc_number'] = array(
    '#type' => 'textfield',
    '#title' => t('Card number'),
    '#default_value' => $default_num,
    '#attributes' => array(
      'autocomplete' => 'off',
    ),
    '#size' => 20,
    '#maxlength' => 19,
  );
  if (variable_get('uc_credit_start_enabled', FALSE)) {
    $month = isset($order->payment_details['cc_start_month']) ? $order->payment_details['cc_start_month'] : NULL;
    $year = isset($order->payment_details['cc_start_year']) ? $order->payment_details['cc_start_year'] : NULL;
    $form['cc_start_month'] = uc_select_month(t('Start Month'), $month, TRUE);
    $form['cc_start_year'] = uc_select_year(t('Start Year'), $year, date('Y') - 10, date('Y'), TRUE);
  }
  $month = isset($order->payment_details['cc_exp_month']) ? $order->payment_details['cc_exp_month'] : 1;
  $year = isset($order->payment_details['cc_exp_year']) ? $order->payment_details['cc_exp_year'] : date('Y');
  $form['cc_exp_month'] = uc_select_month(t('Expiration Month'), $month);
  $form['cc_exp_year'] = uc_select_year(t('Expiration Year'), $year);
  if (variable_get('uc_credit_issue_enabled', FALSE)) {

    // Set up the default Issue Number on the credit card form.
    if (!_valid_card_issue($order->payment_details['cc_issue'])) {

      // Display the Issue Number as is if it does not validate so it can be corrected.
      $default_card_issue = $order->payment_details['cc_issue'];
    }
    elseif (!empty($order->payment_details['cc_issue'])) {
      if (user_access('view cc numbers')) {

        // Display the full number to those with access.
        $default_card_issue = $order->payment_details['cc_issue'];
      }
      else {

        // Otherwise mask it with dashes.
        $default_card_issue = str_repeat('-', strlen($order->payment_details['cc_issue']));
      }
    }
    $form['cc_issue'] = array(
      '#type' => 'textfield',
      '#title' => t('Issue Number'),
      '#default_value' => $default_card_issue,
      '#attributes' => array(
        'autocomplete' => 'off',
      ),
      '#size' => 2,
      '#maxlength' => 2,
    );
  }
  if (variable_get('uc_credit_cvv_enabled', TRUE)) {

    // Set up the default CVV  on the credit card form.
    if (isset($_SESSION['clear_cc']) || empty($order->payment_details['cc_cvv'])) {
      $default_cvv = NULL;
    }
    elseif (!_valid_cvv($order->payment_details['cc_cvv'])) {

      // Display the CVV as is if it does not validate so it can be corrected.
      $default_cvv = $order->payment_details['cc_cvv'];
    }
    elseif (user_access('view cc numbers')) {

      // Display the full number to those with access.
      $default_cvv = $order->payment_details['cc_cvv'];
    }
    else {

      // Otherwise mask it with dashes.
      $default_cvv = str_repeat('-', strlen($order->payment_details['cc_cvv']));
    }
    $form['cc_cvv'] = array(
      '#type' => 'textfield',
      '#title' => t('CVV'),
      '#default_value' => $default_cvv,
      '#attributes' => array(
        'autocomplete' => 'off',
      ),
      '#size' => variable_get('uc_credit_amex', TRUE) ? 4 : 3,
      '#maxlength' => variable_get('uc_credit_amex', TRUE) ? 4 : 3,
    );
  }
  if (variable_get('uc_credit_bank_enabled', FALSE)) {
    $form['cc_bank'] = array(
      '#type' => 'textfield',
      '#title' => t('Issuing bank'),
      '#default_value' => $order->payment_details['cc_bank'],
      '#attributes' => array(
        'autocomplete' => 'off',
      ),
      '#size' => 32,
      '#maxlength' => 64,
    );
  }
  unset($_SESSION['clear_cc']);
  return $form;
}

/**
 * Themes the credit form to be in a compact table.
 */
function theme_uc_payment_method_credit_form($form) {

  // Comment out this function to just straight display the form.
  $form['cc_number']['#title'] = '';
  $form['cc_start_month']['#title'] = '';
  $form['cc_start_year']['#title'] = '';
  $form['cc_exp_month']['#title'] = '';
  $form['cc_exp_year']['#title'] = '';
  $form['cc_issue']['#title'] = '';
  if (arg(1) == 'checkout') {
    $path = base_path() . drupal_get_path('module', 'uc_credit');
    $output = '<table class="inline-pane-table" cellpadding="2">';
    if (strlen($form['cc_policy']['#value']) > 0) {
      $output .= '<tr><td colspan="2" class="description">' . $form['cc_policy']['#value'] . '</td></tr>';
    }
    if (variable_get('uc_credit_type_enabled', FALSE)) {
      $form['cc_type']['#title'] = '';
      $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_type']['#id'] . '">' . t('Card Type:') . '</label></td><td>' . drupal_render($form['cc_type']) . '</td></tr>';
    }
    if (variable_get('uc_credit_owner_enabled', FALSE)) {
      $form['cc_owner']['#title'] = '';
      $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_owner']['#id'] . '">' . t('Card Owner:') . '</label></td><td>' . drupal_render($form['cc_owner']) . '</td></tr>';
    }
    $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_number']['#id'] . '">' . t('Card Number:') . '</label></td><td>' . drupal_render($form['cc_number']) . '</td></tr>';
    if (variable_get('uc_credit_start_enabled', FALSE)) {
      $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_start_month']['#id'] . '">' . t('Start Date:') . '</label></td><td>' . drupal_render($form['cc_start_month']) . ' ' . drupal_render($form['cc_start_year']) . ' ' . t('(if present)') . '</td></tr>';
    }
    $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_exp_month']['#id'] . '">' . t('Expiration Date:') . '</label></td><td>' . drupal_render($form['cc_exp_month']) . ' ' . drupal_render($form['cc_exp_year']) . '</td></tr>';
    if (variable_get('uc_credit_issue_enabled', FALSE)) {
      $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_issue']['#id'] . '">' . t('Issue Number:') . '</label></td><td>' . drupal_render($form['cc_issue']) . ' ' . t('(if present)') . '</td></tr>';
    }
    if (variable_get('uc_credit_cvv_enabled', TRUE)) {
      $form['cc_cvv']['#title'] = '';
      $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_cvv']['#id'] . '">' . t('CVV:') . '</label></td><td>' . drupal_render($form['cc_cvv']) . theme('uc_credit_cvv_help') . '</td></tr>';
    }
    if (variable_get('uc_credit_bank_enabled', FALSE)) {
      $form['cc_bank']['#title'] = '';
      $output .= '<tr><td class="field-label">' . '<label for="' . $form['cc_bank']['#id'] . '">' . t('Issuing Bank:') . '</label></td><td>' . drupal_render($form['cc_bank']) . '</td></tr>';
    }
    $output .= '</table>';
  }
  else {
    $output = '<table class="order-edit-table"><tbody style="border-top: 0px;">';
    if (variable_get('uc_credit_type_enabled', FALSE)) {
      $form['cc_type']['#title'] = '';
      $output .= '<tr><td class="oet-label">' . t('Card Type:') . '</td><td>' . drupal_render($form['cc_type']) . '</td></tr>';
    }
    if (variable_get('uc_credit_owner_enabled', FALSE)) {
      $form['cc_owner']['#title'] = '';
      $output .= '<tr><td class="oet-label">' . t('Card Owner:') . '</td><td>' . drupal_render($form['cc_owner']) . '</td></tr>';
    }
    $output .= '<tr><td class="oet-label">' . t('Card Number:') . '</td><td>' . drupal_render($form['cc_number']) . '</td></tr>';
    if (variable_get('uc_credit_start_enabled', FALSE)) {
      $output .= '<tr><td class="oet-label">' . t('Start Date:') . '</td><td>' . drupal_render($form['cc_start_month']) . ' ' . drupal_render($form['cc_start_year']) . ' ' . t('(if present)') . '</td></tr>';
    }
    $output .= '<tr><td class="oet-label">' . t('Expiration Date:') . '</td><td>' . drupal_render($form['cc_exp_month']) . ' ' . drupal_render($form['cc_exp_year']) . '</td></tr>';
    if (variable_get('uc_credit_issue_enabled', FALSE)) {
      $output .= '<tr><td class="oet-label">' . t('Issue Number:') . '</td><td>' . drupal_render($form['cc_issue']) . ' ' . t('(if present)') . '</td></tr>';
    }
    if (variable_get('uc_credit_cvv_enabled', TRUE)) {
      $form['cc_cvv']['#title'] = '';
      $output .= '<tr><td class="oet-label">' . t('CVV:') . '</td><td>' . drupal_render($form['cc_cvv']) . '</td></tr>';
    }
    if (variable_get('uc_credit_bank_enabled', FALSE)) {
      $form['cc_bank']['#title'] = '';
      $output .= '<tr><td class="oet-label">' . t('Issuing Bank:') . '</td><td>' . drupal_render($form['cc_bank']) . '</td></tr>';
    }
    $output .= '</td></tr></tbody></table>';
  }
  return $output;
}

/**
 * Builds the "Process Card" button on the order view.
 */
function uc_credit_order_view_form($form_state, $order_id) {
  $form['order_id'] = array(
    '#type' => 'hidden',
    '#value' => $order_id,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Process card'),
  );
  return $form;
}

/**
 * Submit handler for order view form.
 */
function uc_credit_order_view_form_submit($form, &$form_state) {
  $form_state['redirect'] = 'admin/store/orders/' . $form_state['values']['order_id'] . '/credit';
}

/*******************************************************************************
 * Module and Helper Functions
 ******************************************************************************/

/**
 * Returns a credit card number with appropriate masking.
 */
function uc_credit_display_number($number, $masked = FALSE) {
  if (strlen($number) == 4) {
    return t('(Last 4) ') . $number;
  }
  if (user_access('view cc numbers') && !$masked) {
    return $number;
  }
  else {
    return str_repeat('-', 12) . substr($number, -4);
  }
}

/**
 * Caches CC details on a pageload for use in various functions.
 *
 * @param $op
 *   The cache operation to perform; either 'save', 'load', or 'clear'.
 * @param $data
 *   The encrypted, serialized string containing the CC data.
 * @return
 *   An array of credit card details.
 */
function uc_credit_cache($op, $data = NULL, $encrypted = TRUE) {

  // The CC data will be stored in this static variable.
  static $cc_cache = array();
  if ($op == 'save') {
    if ($encrypted) {

      // Initialize the encryption key and class.
      $key = uc_credit_encryption_key();
      $crypt = new uc_encryption_class();

      // Save the unencrypted CC details for the duration of this request.
      // In recent versions, we base64_encode() the payment details before
      // encrypting. We can detect encoded data by the lack of colons,
      // due to base64's limited character set.
      $data = $crypt
        ->decrypt($key, $data);
      if (strpos($data, ':') === FALSE) {
        $data = base64_decode($data);
      }
      $cc_cache = unserialize($data);
    }
    else {
      $cc_cache = $data;
    }
  }
  elseif ($op == 'clear') {
    $cc_cache = array();
  }
  return $cc_cache;
}

/**
 * Caches the encrypted CC data on the review order form for processing.
 */
function uc_credit_cart_review_back_submit($form, &$form_state) {
  $session_card_data = base64_decode($_POST['sescrd']);
  $_SESSION['sescrd'] = $session_card_data;
  uc_credit_cache('save', $session_card_data);
}

/**
 * Caches the encrypted CC data on the review order form for processing.
 */
function uc_credit_cart_review_pre_form_submit($form, &$form_state) {
  $session_card_data = base64_decode($_POST['sescrd']);
  $_SESSION['sescrd'] = $session_card_data;
  uc_credit_cache('save', $session_card_data);
}

/**
 * Clears the temporary CC data if the review order form submits.
 */
function uc_credit_cart_review_post_form_submit($form, &$form_state) {
  if (!empty($_SESSION['do_complete'])) {

    // Otherwise stuff it back in the session for the next pageload.
    unset($_SESSION['sescrd']);
  }
}

/**
 * Validates a CVV number during checkout.
 */
function _valid_cvv($cvv) {
  $digits = array();
  if (variable_get('uc_credit_visa', TRUE) || variable_get('uc_credit_mastercard', TRUE) || variable_get('uc_credit_discover', TRUE)) {
    $digits[] = 3;
  }
  if (variable_get('uc_credit_amex', TRUE)) {
    $digits[] = 4;
  }

  // Fail validation if it's non-numeric or an incorrect length.
  if (!is_numeric($cvv) || count($digits) > 0 && !in_array(strlen($cvv), $digits)) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Validates a credit card number during checkout.
 * See: http://www.merriampark.com/anatomycc.htm
 */
function _valid_card_number($number) {
  $id = substr($number, 0, 1);
  if ($id == 3 && !variable_get('uc_credit_amex', TRUE) || $id == 4 && !variable_get('uc_credit_visa', TRUE) || $id == 5 && !variable_get('uc_credit_mastercard', TRUE) || $id == 6 && !variable_get('uc_credit_discover', TRUE) || !ctype_digit($number)) {
    return FALSE;
  }
  $total = 0;
  for ($i = 0; $i < strlen($number); $i++) {
    $digit = substr($number, $i, 1);
    if ((strlen($number) - $i - 1) % 2) {
      $digit *= 2;
      if ($digit > 9) {
        $digit -= 9;
      }
    }
    $total += $digit;
  }
  if ($total % 10 != 0) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Validates a start date on a card.
 *
 * @param $month
 *   The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12.
 * @param $year
 *   The 4-digit numeric representation of the year, i.e. 2008.
 * @return
 *   TRUE for cards whose start date is blank (both month and year) or in the
 *   past, FALSE otherwise.
 */
function _valid_card_start($month, $year) {
  if (empty($month) && empty($year)) {
    return TRUE;
  }
  if (empty($month) || empty($year)) {
    return FALSE;
  }
  if ($year > date('Y')) {
    return FALSE;
  }
  elseif ($year == date('Y')) {
    if ($month > date('n')) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Validates an expiration date on a card.
 *
 * @param $month
 *   The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12.
 * @param $year
 *   The 4-digit numeric representation of the year, i.e. 2008.
 * @return
 *   TRUE for non-expired cards, FALSE for expired.
 */
function _valid_card_expiration($month, $year) {
  if ($year < date('Y')) {
    return FALSE;
  }
  elseif ($year == date('Y')) {
    if ($month < date('n')) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Validates an issue number on a card; returns TRUE or FALSE.
 */
function _valid_card_issue($issue) {
  if (empty($issue) || is_numeric($issue) && $issue > 0) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Loads the key for CC number encryption from a file.
 *
 * @return FALSE if no encryption key is found.
 */
function uc_credit_encryption_key() {
  static $key = FALSE;
  if (empty($key)) {
    $key_file = variable_get('uc_credit_encryption_path', '') . '/' . UC_CREDIT_KEYFILE_NAME;
    $contents = @file_get_contents($key_file);
    if (strlen($contents) == 32) {
      $key = $contents;
    }
  }
  return $key;
}

/**
 * Saves a CC data array to an order's data array.
 */
function _save_cc_data_to_order($cc_data, $order_id) {
  $crypt = new uc_encryption_class();

  // Load up the existing data array.
  $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $order_id));
  $data = unserialize($data);

  // Stuff the serialized and encrypted CC details into the array.
  $data['cc_data'] = $crypt
    ->encrypt(uc_credit_encryption_key(), base64_encode(serialize($cc_data)));
  uc_store_encryption_errors($crypt, 'uc_credit');

  // Save it again.
  db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order_id);
}

/**
 * Returns an array of credit card transaction types available to payment
 *   gateway modules that make sense for default usage.
 */
function uc_credit_transaction_types() {
  $types = array(
    UC_CREDIT_AUTH_ONLY => t('Authorization only'),
    UC_CREDIT_PRIOR_AUTH_CAPTURE => t('Prior authorization capture'),
    UC_CREDIT_AUTH_CAPTURE => t('Authorize and capture immediately'),
    UC_CREDIT_REFERENCE_TXN => t('Reference transaction'),
  );
  return $types;
}

/**
 * Retrieves the ID of the default credit card gateway.
 *
 * @return
 *   A string containing the ID of the default gateway or FALSE if none exists
 *     or none have valid credit callbacks.
 */
function uc_credit_default_gateway() {

  // Get an array of enabled payment gateways available for the payment method.
  $gateways = _payment_gateway_list('credit', TRUE);

  // Return FALSE if we found no gateways.
  if (empty($gateways)) {
    return FALSE;
  }

  // If we only found one gateway for this payment method...
  if (count($gateways) == 1) {

    // Get the payment gateway array and store its ID.
    $gateway = array_shift($gateways);
    $gateway_id = $gateway['id'];

    // Store the callback for this gateway.
    $callback = $gateway['credit'];
  }
  else {

    // Otherwise attempt to find the appropriate gateway function in the array.
    $callback = FALSE;

    // Loop through each gateway.
    foreach ($gateways as $gateway) {

      // Store the callback if this is the specified default.
      if ($gateway['id'] == variable_get('uc_payment_credit_gateway', '')) {
        $callback = $gateway['credit'];
        $gateway_id = $gateway['id'];
      }
    }

    // If we didn't find a default callback...
    if ($callback === FALSE) {

      // Get the key for the first payment gateway in the array.
      $gateway_id = array_shift(array_keys($gateways));

      // Store the callback for this gateway.
      $callback = $gateways[$gateway_id]['credit'];
    }
  }

  // Return FALSE if the specified callback does not exist.
  if (!function_exists($callback)) {
    return FALSE;
  }
  return $gateway_id;
}

/**
 * Stores a credit card authorization to an order's data array.
 *
 * @param $order_id
 *   The order associated with the credit card authorization.
 * @param $auth_id
 *   The payment service's ID for the authorization.
 * @param $amount
 *   The amount that was authorized on the card.
 * @return
 *   The entire updated data array for the order.
 */
function uc_credit_log_authorization($order_id, $auth_id, $amount) {

  // Load the existing order data array.
  $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $order_id));
  $data = unserialize($data);

  // Add the authorization to the cc_txns.
  $data['cc_txns']['authorizations'][$auth_id] = array(
    'amount' => $amount,
    'authorized' => time(),
  );

  // Save the updated data array to the database.
  db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order_id);
  return $data;
}

/**
 * Logs the capture of a prior authorization to an order's data array.
 *
 * @param $order_id
 *   The order associated with the credit card capture.
 * @param $auth_id
 *   The payment service's ID for the authorization that was captured.
 * @return
 *   The entire updated data array for the order or FALSE to indicate the
 *     specified authorization was not found.
 */
function uc_credit_log_prior_auth_capture($order_id, $auth_id) {

  // Load the existing order data array.
  $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $order_id));
  $data = unserialize($data);

  // Return FALSE if we can't find the authorization.
  if (empty($data['cc_txns']['authorizations'][$auth_id])) {
    return FALSE;
  }

  // Otherwise log the capture timestamp to the authorization.
  $data['cc_txns']['authorizations'][$auth_id]['captured'] = time();

  // Save the updated data array to the database.
  db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order_id);
  return $data;
}

/**
 * Logs a credit card reference to an order's data array.
 *
 * @param $order_id
 *   The order associated with the credit card details.
 * @param $ref_id
 *   The payment service's ID for the reference that may be used to charge the
 *     same credit card at a later date.
 * @param $cc_number
 *   The credit card number associated with this reference.  Only the last 4
 *     digits will be stored.
 * @return
 *   The entire updated data array for the order.
 */
function uc_credit_log_reference($order_id, $ref_id, $cc_number) {

  // Load the existing order data array.
  $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $order_id));
  $data = unserialize($data);
  $data['cc_txns']['references'][$ref_id] = array(
    'card' => substr($cc_number, -4),
    'created' => time(),
  );

  // Save the updated data array to the database.
  db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order_id);
  return $data;
}

/**
 * Returns the credit transaction types available for a payment gateway.
 */
function uc_credit_gateway_txn_types($gateway) {
  $types = array();

  // Get the transaction types associated with this gateway.
  $types = _payment_gateway_data($gateway, 'credit_txn_types');

  // Default to authorization plus capture if none are specified.
  if (empty($types)) {
    if (!is_null(_payment_gateway_data($gateway, 'credit'))) {
      $types = array(
        UC_CREDIT_AUTH_CAPTURE,
      );
    }
    else {

      // Or an empty array if the gateway doesn't even handle credit payments.
      $types = array();
    }
  }
  return $types;
}

/**
 * Title callback for admin/store/orders/%uc_order/credit.
 */
function uc_credit_terminal_title($order) {
  return t('Credit card terminal: Order @order_id', array(
    '@order_id' => $order->order_id,
  ));
}

Functions

Namesort descending Description
theme_uc_payment_method_credit_form Themes the credit form to be in a compact table.
uc_credit_cache Caches CC details on a pageload for use in various functions.
uc_credit_cart_review_back_submit Caches the encrypted CC data on the review order form for processing.
uc_credit_cart_review_post_form_submit Clears the temporary CC data if the review order form submits.
uc_credit_cart_review_pre_form_submit Caches the encrypted CC data on the review order form for processing.
uc_credit_cron Implements hook_cron().
uc_credit_default_gateway Retrieves the ID of the default credit card gateway.
uc_credit_display_number Returns a credit card number with appropriate masking.
uc_credit_encryption_key Loads the key for CC number encryption from a file.
uc_credit_exit Implements hook_exit().
uc_credit_form_alter Implements hook_form_alter().
uc_credit_gateway_txn_types Returns the credit transaction types available for a payment gateway.
uc_credit_init Implements hook_init().
uc_credit_log_authorization Stores a credit card authorization to an order's data array.
uc_credit_log_prior_auth_capture Logs the capture of a prior authorization to an order's data array.
uc_credit_log_reference Logs a credit card reference to an order's data array.
uc_credit_menu Implements hook_menu().
uc_credit_order Implements hook_order().
uc_credit_order_view_form Builds the "Process Card" button on the order view.
uc_credit_order_view_form_submit Submit handler for order view form.
uc_credit_payment_method Implements hook_payment_method().
uc_credit_perm Implements hook_perm().
uc_credit_settings_form_submit Creates the encryption key file if it doesn't already exist.
uc_credit_settings_form_validate Validates the encryption key directory and key file.
uc_credit_store_status Implements hook_store_status().
uc_credit_terminal_title Title callback for admin/store/orders/%uc_order/credit.
uc_credit_theme Implements hook_theme().
uc_credit_transaction_types Returns an array of credit card transaction types available to payment gateway modules that make sense for default usage.
uc_payment_method_credit Callback function for the Credit Card payment method.
uc_payment_method_credit_form Displays the credit card details form on the checkout screen.
_save_cc_data_to_order Saves a CC data array to an order's data array.
_valid_card_expiration Validates an expiration date on a card.
_valid_card_issue Validates an issue number on a card; returns TRUE or FALSE.
_valid_card_number Validates a credit card number during checkout. See: http://www.merriampark.com/anatomycc.htm
_valid_card_start Validates a start date on a card.
_valid_cvv Validates a CVV number during checkout.

Constants

Namesort descending Description
UC_CREDIT_AUTH_CAPTURE Authorize and capture money all at once.
UC_CREDIT_AUTH_ONLY Just authorize an amount on a credit card account.
UC_CREDIT_CREDIT Credit funds to a credit card account.
UC_CREDIT_KEYFILE_NAME Name of encryption key file.
UC_CREDIT_PRIOR_AUTH_CAPTURE Capture funds from a prior authorization.
UC_CREDIT_REFERENCE_CREDIT Credit funds to a reference at the payment gateway.
UC_CREDIT_REFERENCE_REMOVE Remove a reference from the payment gateway.
UC_CREDIT_REFERENCE_SET Set up a credit card reference through the payment gateway.
UC_CREDIT_REFERENCE_TXN Capture funds using a credit card reference.
UC_CREDIT_VOID Void a transaction before the transaction clears.