'cart/checkout/credit/cvv_info',
'title' => t('CVV information'),
'callback' => 'uc_credit_cvv_info',
'access' => user_access('access content'),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/store/settings/payment/cc_encrypt',
'title' => t('Encrypt existing credit card data'),
'callback' => 'uc_credit_encrypt_existing',
'access' => user_access('administer credit cards'),
'type' => MENU_CALLBACK,
);
}
else {
if (is_numeric(arg(3))) {
$items[] = array(
'path' => 'admin/store/orders/'. arg(3) .'/credit',
'title' => t('Credit card terminal: Order @order_id', array('@order_id' => arg(3))),
'description' => t('Process a credit card payment for order @order_id.', array('@order_id' => arg(3))),
'callback' => 'uc_credit_terminal',
'callback arguments' => array(arg(3)),
'access' => user_access('process credit cards'),
'type' => MENU_CALLBACK
);
}
}
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_form_alter().
*/
function uc_credit_form_alter($form_id, &$form) {
switch ($form_id) {
case 'uc_payment_methods_form':
if (empty($_POST) && uc_credit_encryption_key() === FALSE) {
drupal_set_message(t('Credit card encryption must be configured to accept credit card payments.'), 'error');
}
$form['#validate']['uc_credit_settings_form_validate'] = array();
$form['#submit']['uc_credit_settings_form_submit'] = array();
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' => $_SESSION['sescrd'],
);
$form['#submit'] = array('uc_credit_cart_review_pre_form_submit' => array()) + $form['#submit'] + array('uc_credit_cart_review_post_form_submit' => array());
// 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 ((array) $form['gateways'] as $key => $value) {
// Get the transaction types associated with this gateway.
$gateway_types = uc_credit_gateway_txn_types($key);
// Default to authorization plus capture if none are specified.
if (empty($types) && !is_null(_payment_gateway_data($key, 'credit'))) {
$types = array(UC_CREDIT_AUTH_CAPTURE);
}
// 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() {
// Clear the data
$time = strtotime(variable_get('uc_cart_anon_duration', '4') .' '. variable_get('uc_cart_anon_unit', 'hours') .' ago');
switch ($GLOBALS['db_type']) {
case 'mysql':
case 'mysqli':
db_query("DELETE upc.* FROM {uc_orders} AS uo LEFT JOIN {uc_payment_credit} AS upc ON uo.order_id = upc.order_id WHERE uo.modified <= %d AND uo.order_status = '%s'",
$time, uc_order_state_default('in_checkout'));
break;
case 'pgsql':
db_query("DELETE FROM {uc_payment_credit} WHERE order_id IN (SELECT order_id FROM {uc_orders} WHERE modified <= %d AND order_status = '%s')",
$time, uc_order_state_default('in_checkout'));
break;
}
// Mask old stored credit card numbers.
$time = strtotime(variable_get('uc_credit_number_duration', '1') .' '. variable_get('uc_credit_number_unit', 'years') .' ago');
switch ($GLOBALS['db_type']) {
case 'mysql':
case 'mysqli':
db_query("DELETE upc.* FROM {uc_orders} AS uo LEFT JOIN {uc_payment_credit} AS upc ON uo.order_id = upc.order_id WHERE uo.modified <= %d AND uo.order_status = '%s'",
$time, variable_get('uc_credit_clear_status', uc_order_state_default('completed')));
break;
case 'pgsql':
db_query("DELETE FROM {uc_payment_credit} WHERE order_id IN (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')));
break;
}
if (variable_get('uc_credit_debug', FALSE)) {
$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),
);
// 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[0] != 'cart' || $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 setup 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 credit card security settings 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', t('Credit card encryption must be setup 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_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;
case 'delete':
db_query("DELETE FROM {uc_payment_credit} WHERE order_id = %d", $arg1->order_id);
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 .= ' ';
}
}
$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')) {
uc_add_js(drupal_get_path('module', 'uc_credit') .'/uc_credit.js');
$output .= ''
.''. t('View card details.') .'';
$output .= '
';
if (variable_get('uc_credit_type_enabled', TRUE)) {
$type = check_plain($arg1->payment_details['cc_type']);
if (strlen($type) > 0) {
$output .= '
'. t('Card Type:') .'
'. $type .'
';
}
}
if (variable_get('uc_credit_owner_enabled', FALSE)) {
$owner = check_plain($arg1->payment_details['cc_owner']);
if (strlen($owner) > 0) {
$output .= '
';
}
if (variable_get('uc_credit_issue_enabled', FALSE)) {
$issue = $arg1->payment_details['cc_issue'];
if (strlen($issue) > 0) {
$output .= '
'. t('Issue Number:') .'
'. $issue .'
';
}
}
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 .= '
'. t('CVV:') .'
'. $cvv .'
';
}
}
if (variable_get('uc_credit_bank_enabled', TRUE)) {
$bank = check_plain($arg1->payment_details['cc_bank']);
if (strlen($bank) > 0) {
$output .= '
'. t('Issuing Bank:') .'
'. $bank .'
';
}
}
}
$output .= '
';
// 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:') .' '
. 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 %button button on the View tab to 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' => '
'. t('You must have access to administer credit cards to adjust these settings.') .'
',
);
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('You must enable encryption by following the encryption instructions in order to accept credit card payments. In short, you must specify a path outside of your document root where the encryption key may be stored. Relative paths will be resolved relative to the Drupal installation directory. 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. 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. 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. 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.', 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' => '
',
);
// 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_id, $form_values) {
$dir = variable_get('uc_credit_encryption_path', t('Not configured, see below.'));
$filename = rtrim($dir, '/\\') .'/uc_credit.key';
if ($form_values['uc_credit_encryption_path'] !== variable_get('uc_credit_encryption_path', t('Not configured, see below.')) || !file_exists($filename)) {
$dir = rtrim($form_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_id, $form_values) {
if ($_SESSION['update_cc_encrypt_dir'] === TRUE) {
$dir = rtrim($form_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')) {
$message = t('Credit card encryption key file creation failed. Check your filepath settings and directory permissions.');
drupal_set_message($message, 'error');
watchdog('uc_credit', $message, 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);
$message = t('Credit card encryption key file generated. Card data will now be encrypted.');
drupal_set_message($message);
drupal_set_message(t('If you are updating from a previous version and have any pre-existing credit card data, you should click here to encrypt it before receiving any more credit card orders or the data may be lost.', array('!url' => url('admin/store/settings/payment/cc_encrypt'))));
watchdog('uc_credit', t('Credit card encryption key file generated.'));
}
}
}
}
if (!$form_values['uc_credit_checkout_process'] && !$form_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($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'],
);
}
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,
);
}
// Setup 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)) {
// Setup 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)) {
// Setup 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 = '
';
if (strlen($form['cc_policy']) > 0) {
$output .= '
';
}
return $output;
}
// Builds the "Process Card" button on the order view.
function uc_credit_order_view_form($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_id, $form_values) {
return 'admin/store/orders/'. $form_values['order_id'] .'/credit';
}
// Prints the contents of the CVV information popup window.
function uc_credit_cvv_info() {
$output = ''. t('What is the CVV?') .'
'. t('CVV stands for Card Verification Value. This number is used as a security feature to protect you from credit card fraud. Finding the number on your card is a very simple process. Just follow the directions below.') .'
'
. t('The CVV for these cards is found on the back side of the card. It is only the last three digits on the far right of the signature panel box.') .'
';
}
if (variable_get('uc_credit_amex', TRUE)) {
$output .= '
'. t('American Express') .':
'
. t('The CVV on American Express cards is found on the front of the card. It is a four digit number printed in smaller text on the right side above the credit card number.') .'
';
}
$output .= '';
print $output;
exit();
}
// Displays the credit card terminal page.
function uc_credit_terminal($order_id) {
$order = uc_order_load($order_id);
if ($order === FALSE) {
drupal_set_message(t('Order @order_id does not exist.', array('@order_id' => $order_id)));
drupal_goto('admin/store/orders');
}
$output = l(t('Return to order view screen.'), 'admin/store/orders/'. $order_id);
$output .= '
'. t('Use this terminal to process credit card payments through your default gateway.') .'
'. t('Be warned that credit card data will automatically be converted to the last 4 digits of the card once a transaction has occurred. As such, subsequent charges after a partial payment will not have any stored credit card information to use.') .'
';
$output .= drupal_get_form('uc_credit_terminal_form', $order, $balance);
return $output;
}
// Displays the credit card terminal form for administrators.
function uc_credit_terminal_form($order, $lock_amount = FALSE) {
// Get the transaction types available to our default gateway.
$types = uc_credit_gateway_txn_types(uc_credit_default_gateway());
// Put the order ID in the form.
$form['order_id'] = array(
'#type' => 'hidden',
'#value' => $order->order_id,
);
$balance = uc_payment_balance($order);
// Let the administrator set the amount to charge.
$form['amount'] = array(
'#type' => 'textfield',
'#title' => t('Charge Amount'),
'#default_value' => $balance > 0 ? uc_currency_format($balance, FALSE, FALSE) : 0,
'#size' => 10,
'#disabled' => $lock_amount,
'#field_prefix' => variable_get('uc_sign_after_amount', FALSE) ? '' : variable_get('uc_currency_sign', '$'),
'#field_suffix' => variable_get('uc_sign_after_amount', FALSE) ? variable_get('uc_currency_sign', '$') : '',
);
// Build a credit card form.
$form['specify_card'] = array(
'#type' => 'fieldset',
'#title' => t('Credit card details'),
'#description' => t('Use the available buttons in this fieldset to process with the specified card details.'),
);
$form['specify_card']['cc_data'] = array(
'#theme' => 'uc_payment_method_credit_form',
'#tree' => TRUE,
);
$form['specify_card']['cc_data'] += uc_payment_method_credit_form($order);
unset($form['specify_card']['cc_data']['cc_policy']);
// If available, let the card be charged now.
if (in_array(UC_CREDIT_AUTH_CAPTURE, $types)) {
$form['specify_card']['charge_card'] = array(
'#type' => 'submit',
'#value' => t('Charge amount'),
);
}
// If available, let the amount be authorized.
if (in_array(UC_CREDIT_AUTH_ONLY, $types)) {
$form['specify_card']['authorize_card'] = array(
'#type' => 'submit',
'#value' => t('Authorize amount only'),
);
}
// If available, create a reference at the gateway.
if (in_array(UC_CREDIT_REFERENCE_SET, $types)) {
$form['specify_card']['reference_set'] = array(
'#type' => 'submit',
'#value' => t('Set a reference only'),
);
}
// If available, create a reference at the gateway.
if (in_array(UC_CREDIT_CREDIT, $types)) {
$form['specify_card']['credit_card'] = array(
'#type' => 'submit',
'#value' => t('Credit amount to this card'),
);
}
// Find any uncaptured authorizations.
$options = array();
foreach ((array) $order->data['cc_txns']['authorizations'] as $auth_id => $data) {
if (empty($data['captured'])) {
$options[$auth_id] = t('@auth_id - @date - @amount authorized', array('@auth_id' => strtoupper($auth_id), '@date' => format_date($data['authorized'], 'small'), '@amount' => uc_currency_format($data['amount'])));
}
}
// If any authorizations existed...
if (!empty($options)) {
// Display a fieldset with the authorizations and available action buttons.
$form['authorizations'] = array(
'#type' => 'fieldset',
'#title' => t('Prior authorizations'),
'#description' => t('Use the available buttons in this fieldset to select and act on a prior authorization. The charge amount specified above will be captured against the authorization listed below. Only one capture is possible per authorization, and a capture for more than the amount of the authorization may result in additional fees to you.'),
);
$form['authorizations']['select_auth'] = array(
'#type' => 'radios',
'#title' => t('Select authorization'),
'#options' => $options,
);
// If available, capture a prior authorization.
if (in_array(UC_CREDIT_PRIOR_AUTH_CAPTURE, $types)) {
$form['authorizations']['auth_capture'] = array(
'#type' => 'submit',
'#value' => t('Capture amount to this authorization'),
);
}
// If available, void a prior authorization.
if (in_array(UC_CREDIT_VOID, $types)) {
$form['authorizations']['auth_void'] = array(
'#type' => 'submit',
'#value' => t('Void authorization'),
);
}
// Collapse this fieldset if no actions are available.
if (!isset($form['authorizations']['auth_capture']) && !isset($form['authorizations']['auth_void'])) {
$form['authorizations']['#collapsible'] = TRUE;
$form['authorizations']['#collapsed'] = TRUE;
}
}
// Find any uncaptured authorizations.
$options = array();
// Log a reference to the order for testing.
// $order->data = uc_credit_log_reference($order->order_id, substr(md5(time()), 0, 16), '4111111111111111');
foreach ((array) $order->data['cc_txns']['references'] as $ref_id => $data) {
$options[$ref_id] = t('@ref_id - @date - (Last 4) @card', array('@ref_id' => strtoupper($ref_id), '@date' => format_date($data['created'], 'small'), '@card' => $data['card']));
}
// If any references existed...
if (!empty($options)) {
// Display a fieldset with the authorizations and available action buttons.
$form['references'] = array(
'#type' => 'fieldset',
'#title' => t('Customer references'),
'#description' => t('Use the available buttons in this fieldset to select and act on a customer reference.'),
);
$form['references']['select_ref'] = array(
'#type' => 'radios',
'#title' => t('Select references'),
'#options' => $options,
);
// If available, capture a prior references.
if (in_array(UC_CREDIT_REFERENCE_TXN, $types)) {
$form['references']['ref_capture'] = array(
'#type' => 'submit',
'#value' => t('Charge amount to this reference'),
);
}
// If available, remove a previously stored reference.
if (in_array(UC_CREDIT_REFERENCE_REMOVE, $types)) {
$form['references']['ref_remove'] = array(
'#type' => 'submit',
'#value' => t('Remove reference'),
);
}
// If available, remove a previously stored reference.
if (in_array(UC_CREDIT_REFERENCE_CREDIT, $types)) {
$form['references']['ref_credit'] = array(
'#type' => 'submit',
'#value' => t('Credit amount to this reference'),
);
}
// Collapse this fieldset if no actions are available.
if (!isset($form['references']['ref_capture']) && !isset($form['references']['ref_remove']) && !isset($form['references']['ref_credit'])) {
$form['references']['#collapsible'] = TRUE;
$form['references']['#collapsed'] = TRUE;
}
}
return $form;
}
function uc_credit_terminal_form_validate($form_id, $form_values) {
switch ($form_values['op']) {
case t('Charge amount'):
case t('Authorize amount only'):
case t('Capture amount to this authorization'):
case t('Charge amount to this reference'):
if (!is_numeric($form_values['amount']) || $form_values['amount'] <= 0) {
form_set_error('amount', t('You must enter a positive number for the amount.'));
}
}
if (uc_order_load($form_values['order_id']) === FALSE) {
form_set_error('', t('Invalid order ID. Unable to process payment.'));
}
}
function uc_credit_terminal_form_submit($form_id, $form_values) {
// Load the order.
$order = uc_order_load($form_values['order_id']);
// Get the data from the form and replace masked data from the order.
$cc_data = $form_values['cc_data'];
if (strpos($cc_data['cc_number'], t('(Last 4) ')) === 0) {
$cc_data['cc_number'] = $order->payment_details['cc_number'];
}
if ($cc_data['cc_cvv'] == str_repeat('-', strlen($cc_data['cc_cvv']))) {
$cc_data['cc_cvv'] = $order->payment_details['cc_cvv'];
}
// Cache the values for use during processing.
uc_credit_cache('save', $cc_data, FALSE);
// Build the data array passed on to the payment gateway.
$data = array();
switch ($form_values['op']) {
case t('Charge amount'):
$data['txn_type'] = UC_CREDIT_AUTH_CAPTURE;
break;
case t('Authorize amount only'):
$data['txn_type'] = UC_CREDIT_AUTH_ONLY;
break;
case t('Set a reference only'):
$data['txn_type'] = UC_CREDIT_REFERENCE_SET;
break;
case t('Credit amount to this card'):
$data['txn_type'] = UC_CREDIT_CREDIT;
break;
case t('Capture amount to this authorization'):
$data['txn_type'] = UC_CREDIT_PRIOR_AUTH_CAPTURE;
$data['auth_id'] = $form_values['select_auth'];
break;
case t('Void authorization'):
$data['txn_type'] = UC_CREDIT_VOID;
$data['auth_id'] = $form_values['select_auth'];
break;
case t('Charge amount to this reference'):
$data['txn_type'] = UC_CREDIT_REFERENCE_TXN;
$data['ref_id'] = $form_values['select_ref'];
break;
case t('Remove reference'):
$data['txn_type'] = UC_CREDIT_REFERENCE_REMOVE;
$data['ref_id'] = $form_values['select_ref'];
break;
case t('Credit amount to this reference'):
$data['txn_type'] = UC_CREDIT_REFERENCE_CREDIT;
$data['ref_id'] = $form_values['select_ref'];
}
$result = uc_payment_process('credit', $form_values['order_id'], $form_values['amount'], $data, TRUE, NULL, FALSE);
if ($result) {
$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", $form_values['order_id']));
$data = unserialize($data);
$cache = uc_credit_cache('load');
if (variable_get('uc_credit_debug', FALSE) && !variable_get('uc_credit_checkout_process', TRUE)) {
$cc_data = $cache;
}
else {
$cc_data = array(
'cc_number' => substr($cache['cc_number'], -4),
);
}
// 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), $form_values['order_id']);
drupal_set_message(t('The credit card was processed successfully. See the admin comments for more details.'));
}
else {
if (variable_get('uc_credit_debug', FALSE)) {
_save_cc_data_to_order(uc_credit_cache('load'), $form_values['order_id']);
}
drupal_set_message(t('There was an error processing the credit card. See the admin comments for details.'), 'error');
}
return 'admin/store/orders/'. $form_values['order_id'];
}
/*******************************************************************************
* 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);
}
}
// Gives the option to site admin to encrypt existing CC data.
function uc_credit_encrypt_existing() {
// Check to make sure encryption has been setup first.
if (($key = uc_credit_encryption_key()) === FALSE) {
return t('You must first setup your credit card encryption key filepath.');
}
// Check to make sure this action hasn't already been performed.
if (variable_get('uc_credit_existing_cc_encrypted', FALSE)) {
return t('This action may not be repeated.');
}
// Display a help messaage to make sure the admin wants to do this now.
if (arg(5) !== 'process') {
return t('Existing credit card numbers should be encrypted for future usage. The encryption process may take a few moments if you have a lot of data that needs to be encrypted. Click this link to update them now.', array('!url' => url('admin/store/settings/payment/cc_encrypt/process')));
}
// Setup our encryption object.
$crypt = new uc_encryption_class;
// Query the database for existing CC details.
$result = db_query("SELECT * FROM {uc_payment_credit} WHERE cc_number REGEXP ('[0-9]')");
while ($row = db_fetch_array($result)) {
// Save the data to the order.
_save_cc_data_to_order($row, $row['order_id']);
// Delete the completed row.
db_query("DELETE FROM {uc_payment_credit} WHERE credit_id = %d", $row['credit_id']);
}
// Log any errors to the watchdog.
uc_store_encryption_errors($crypt, 'uc_credit');
// Mark the process as having been completed.
variable_set('uc_credit_existing_cc_encrypted', TRUE);
drupal_set_message(t('Your existing credit card data has been encrypted.'));
drupal_goto('admin/store/settings/payment/cc_encrypt');
}
/**
* 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_pre_form_submit($form_id, $form_values) {
$_SESSION['sescrd'] = $_POST['sescrd'];
uc_credit_cache('save', $_POST['sescrd']);
}
// Clears the temporary CC data if the review order form submits.
function uc_credit_cart_review_post_form_submit($form_id, $form_values) {
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;
}
else if ($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;
}
else if ($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;
}
/**
* Return any transaction references on a given user's orders.
*
* @param $account (optional)
* The user account whose orders are to be searched for CC references.
* Default: The current user account context.
* @return
* A structured array. Each element's key is the transaction reference, and
* the value is an array containing the billing information and the
* truncated credit card number.
*/
function uc_credit_references($account = NULL) {
static $references = array();
// If account is NULL, we'll use the current user context.
if (is_null($account)) {
global $user;
$account = drupal_clone($user);
}
if (!isset($references[$account->uid])) {
$result = db_query("SELECT billing_first_name, billing_last_name, billing_phone, billing_company, billing_street1, billing_street2, billing_city, billing_zone, billing_postal_code, billing_country, data FROM {uc_orders} WHERE uid = %d", $account->uid);
while ($order = db_fetch_array($result)) {
// Extract data and remove it from the result; we won't need it later and
// that'll help keep our code clean.
$data = unserialize($order['data']);
unset($order['data']);
// For each reference, store the reference ID as the key and the billing info
// and truncated CC data as the value.
if (is_array($data['cc_txns']) && is_array($data['cc_txns']['references'])) {
foreach ($data['cc_txns']['references'] as $id => $reference) {
$references[$account->uid][$id] = $order + array('cc_number' => $reference['card']);
}
}
}
}
return $references[$account->uid];
}
/**
* 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;
}