<?php
// $Id: uc_credit.module,v 1.5.2.26 2010/07/09 19:51:32 islandusurper Exp $

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

// Used to just authorize an amount on a credit card account.
define('UC_CREDIT_AUTH_ONLY', 'authorize');

// Used to capture funds from a prior authorization.
define('UC_CREDIT_PRIOR_AUTH_CAPTURE', 'prior_auth_capture');

// Used to authorize and capture money all at once.
define('UC_CREDIT_AUTH_CAPTURE', 'auth_capture');

// Used to set up a credit card reference through the payment gateway.
define('UC_CREDIT_REFERENCE_SET', 'reference_set');

// Used to capture funds using a credit card reference.
define('UC_CREDIT_REFERENCE_TXN', 'reference_txn');

// Used to remove a reference from the payment gateway.
define('UC_CREDIT_REFERENCE_REMOVE', 'reference_remove');

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

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

// Used to void a transaction before the transaction clears.
define('UC_CREDIT_VOID', 'void');

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

/**
 * Implementation of 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' => 'Credit card terminal: Order @order_id',
    'title arguments' => array('@order_id' => arg(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;
}

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

/**
 * Implementation of hook_theme().
 */
function uc_credit_theme() {
  return array(
    'uc_payment_method_credit_form' => array(
      'arguments' => array('form' => NULL),
    ),
  );
}

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

/**
 * Implementation of 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() === FALSE) {
          // 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');
      }

      // 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']),
      );

      // 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;

      // Clear the session of the details.
      unset($_SESSION['sescrd']);
      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'),
        );

        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,
        );
      }
  }
}

/**
 * Implementation of hook_cron().
 */
function uc_credit_cron() {
  // Truncate stored data when in debug per the credt 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 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']);

      $cc_data = array(
        'cc_number' => substr($data['cc_data']['cc_number'], -4),
        'cc_type' => $data['cc_data']['cc_type'],
      );

      // Stuff the serialized and encrypted CC details into the array.
      $data['cc_data'] = $crypt->encrypt(uc_credit_encryption_key(), 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['order_id']);
    }
  }
}

/**
 * Implementation of hook_exit().
 */
function uc_credit_exit() {
  // Separate the args ourself since the arg() function may not be loaded.
  $args = explode('/', $_GET['q']);

  // Make sure sensitive checkout session data doesn't persist on other pages.
  if (isset($_SESSION['sescrd']) && (!isset($args[1]) || $args[1] != 'checkout')) {
    unset($_SESSION['sescrd']);
  }
}


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

/**
 * Implementation of 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;
}

/**
 * Implementation of hook_order().
 */
function uc_credit_order($op, &$arg1, $arg2) {
  // Set up the encryption key and object for saving and loading.
  if ($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 card 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 {
            // Otherwise, save only some limited, PCI compliant data.
            $cc_data = array(
              'cc_number' => substr($arg1->payment_details['cc_number'], -4),
              'cc_exp_month' => $arg1->payment_details['cc_exp_month'],
              'cc_exp_year' => $arg1->payment_details['cc_exp_year'],
              'cc_type' => $arg1->payment_details['cc_type'],
            );
          }

          _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;
  }
}

/**
 * Implementation of 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', 'mastercard', 'discover', 'amex');
  foreach ($cc_types as $type) {
    if (variable_get('uc_credit_'. $type, TRUE)) {
      $title .= ' <img src="'. $path .'/images/'. $type .'.gif" style="position: relative; top: 5px;" />';
    }
  }
  $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' => check_plain($_POST['cc_type']),
        'cc_owner' => check_plain($_POST['cc_owner']),
        'cc_number' => check_plain(str_replace(' ', '',  $_POST['cc_number'])),
        'cc_start_month' => check_plain($_POST['cc_start_month']),
        'cc_start_year' => check_plain($_POST['cc_start_year']),
        'cc_exp_month' => check_plain($_POST['cc_exp_month']),
        'cc_exp_year' => check_plain($_POST['cc_exp_year']),
        'cc_issue' => check_plain($_POST['cc_issue']),
        'cc_cvv' => check_plain($_POST['cc_cvv']),
        'cc_bank' => check_plain($_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.
      $_SESSION['sescrd'] = $crypt->encrypt($key, 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 = $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' => $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') ? $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') ? $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':
      // 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)) {
          $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 = $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)) {
            $start = $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)) {
            $issue = $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)) {
            $cvv = user_access('view cc numbers') ? $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)) {
            $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':
      $cache = uc_credit_cache('load');

      $changes['payment_details']['cc_type'] = check_plain($_POST['cc_type']);
      $changes['payment_details']['cc_owner'] = check_plain($_POST['cc_owner']);
      if (strpos($_POST['cc_number'], t('(Last 4) ')) !== 0) {
        $changes['payment_details']['cc_number'] = check_plain($_POST['cc_number']);
      }
      else {
        $changes['payment_details']['cc_number'] = $cache['cc_number'];
      }
      $changes['payment_details']['cc_exp_month'] = check_plain($_POST['cc_exp_month']);
      $changes['payment_details']['cc_exp_year'] = check_plain($_POST['cc_exp_year']);
      if ($_POST['cc_cvv'] !== str_repeat('-', strlen($_POST['cc_cvv']))) {
        $changes['payment_details']['cc_cvv'] = check_plain($_POST['cc_cvv']);
      }
      else {
        $changes['payment_details']['cc_cvv'] = $cache['cc_cvv'];
      }
      $changes['payment_details']['cc_bank'] = check_plain($_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('Card number encryption key filepath'),
        '#description' => t('<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 specify a path 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 is set, you should not change it.', array('!url' => 'http://www.ubercart.org/docs/user/2731/credit_card_settings#security')),
        '#default_value' => variable_get('uc_credit_encryption_path', t('Not configured, see below.')),
      );
      $form['cc_security']['uc_credit_debug'] = array(
        '#type' => 'checkbox',
        '#title' => t('Operate in credit card debug mode.'),
        '#description' => t('In debug mode, credit card details may be stored 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.  When not in debug mode, no credit card information except the last 4 digits of the card number will be stored. <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')))),
      );

      // 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 card details and try again.  If the problem persists, contact us to complete your order.')),
      );

      return $form;
  }
}

// Makes sure the encryption key directory in the credit card settings is valid.
function uc_credit_settings_form_validate($form, &$form_state) {
  $dir = variable_get('uc_credit_encryption_path', t('Not configured, see below.'));
  $filename = rtrim($dir, '/\\') .'/uc_credit.key';

  if ($form_state['values']['uc_credit_encryption_path'] !== variable_get('uc_credit_encryption_path', t('Not configured, see below.'))) {
    $dir = rtrim($form_state['values']['uc_credit_encryption_path'], '/\\');

    $_SESSION['update_cc_encrypt_dir'] = TRUE;
    if (!empty($dir) && $dir !== t('Not configured, see below.')) {
      if (!is_dir($dir)) {
        form_set_error('uc_credit_encryption_path', t('You have specified a non-existent directory.'));
        unset($_SESSION['update_cc_encrypt_dir']);
      }
      else {
        $file = fopen($dir .'/encrypt.test', 'w');
        if ($file === FALSE) {
          form_set_error('uc_credit_encryption_path', t('Cannot write to directory, please verify the directory permissions.'));
          unset($_SESSION['update_cc_encrypt_dir']);
        }
        else {
          if (fwrite($file, '0123456789') === FALSE) {
            form_set_error('uc_credit_encryption_path', t('Cannot write to directory, please verify the directory permissions.'));
            unset($_SESSION['update_cc_encrypt_dir']);
            fclose($file);
          }
          else {
            fclose($file);
            $file = fopen($dir .'/encrypt.test', 'r');
            if ($file === FALSE) {
              form_set_error('uc_credit_encryption_path', t('Cannot read from directory, please verify the directory permissions.'));
              unset($_SESSION['update_cc_encrypt_dir']);
            }
            fclose($file);
          }
          unlink($dir .'/encrypt.test');
        }
      }
    }
  }
}

// Creates the encryption key file if it doesn't already exist.
function uc_credit_settings_form_submit($form, &$form_state) {
  if ($_SESSION['update_cc_encrypt_dir'] === TRUE) {
    $dir = rtrim($form_state['values']['uc_credit_encryption_path'], '/\\');

    if (!empty($dir) && $dir !== t('Not configured, see below.')) {
      if (!file_exists($dir .'/uc_credit.key')) {
        if (!$file = fopen($dir .'/uc_credit.key', 'wb')) {
          drupal_set_message(t('Credit card encryption key file creation failed. Check your filepath settings and directory permissions.'), 'error');
          watchdog('uc_credit', 'Credit card encryption key file creation failed. Check your filepath settings and directory permissions.', array(), 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.');
        }
      }
    }
  }

  if (!$form_state['values']['uc_credit_checkout_process'] && !$form_state['values']['uc_credit_debug']) {
    drupal_set_message(t('You have chosen to not process credit card payments during checkout, but customer credit card data is not being stored once checkout is completed. This configuration is not recommended for a live site, as you will not be able to collect payment for your orders.'), 'error');
  }
}

// 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;
    }
    $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(array_keys($options)),
    );
  }

  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 (variable_get('uc_credit_validate_numbers', TRUE) && (strlen($order->payment_details['cc_number']) > 4 && !_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 (!empty($order->payment_details['cc_number'])) {
    if (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' => $_SESSION['clear_cc'] ? '' : $default_num,
    '#attributes' => array('autocomplete' => 'off'),
    '#size' => 20,
    '#maxlength' => 19,
  );

  if (variable_get('uc_credit_start_enabled', FALSE)) {
    $form['cc_start_month'] = uc_select_month(t('Start Month'), $order->payment_details['cc_start_month'], TRUE);
    $form['cc_start_year'] = uc_select_year(t('Start Year'), $order->payment_details['cc_start_year'], date('Y') - 10, date('Y'), TRUE);
  }

  $form['cc_exp_month'] = uc_select_month(t('Expiration Month'), $order->payment_details['cc_exp_month']);
  $form['cc_exp_year'] = uc_select_year(t('Expiration Year'), $order->payment_details['cc_exp_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 (!_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 (!empty($order->payment_details['cc_cvv'])) {
      if (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' => $_SESSION['clear_cc'] ? '' : $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 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">'. 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="field-label">'. t('Card Owner:') .'</td><td>'
               . drupal_render($form['cc_owner']) .'</td></tr>';
    }
    $output .= '<tr><td class="field-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="field-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="field-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="field-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="field-label">'. t('CVV:') .'</td><td>'. drupal_render($form['cc_cvv'])
                .' <img src="'. $path .'/images/info.png" onclick="cvv_info_popup();" style="cursor: pointer; position: relative; top: 3px;" /> <a style="cursor: pointer; font-weight: normal;" onclick="cvv_info_popup();">'
                . t("What's the CVV?") .'</a></td></tr>';
    }
    if (variable_get('uc_credit_bank_enabled', FALSE)) {
      $form['cc_bank']['#title'] = '';
      $output .= '<tr><td class="field-label">'. t('Issuing Bank:') .'</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;
}

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.
      $cc_cache = unserialize($crypt->decrypt($key, $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 ($_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;
  }

  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;

  if (!empty($key)) {
    return $key;
  }

  $dir = variable_get('uc_credit_encryption_path', t('Not configured, see below.'));

  if (!empty($dir) && $dir !== t('Not configured, see below.')) {
    $filename = rtrim($dir, '/\\') .'/uc_credit.key';

    if (file_exists($filename)) {
      if (!$file = fopen($filename, 'r')) {
        return FALSE;
      }

      $key = fread($file, filesize($filename));
      fclose($file);
    }
  }
  else {
    return FALSE;
  }

  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(), 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;
}
