? uc_linkpoint_api-971128.patch
Index: uc_linkpoint_api.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uc_linkpoint_api/uc_linkpoint_api.module,v
retrieving revision 1.10
diff -u -p -r1.10 uc_linkpoint_api.module
--- uc_linkpoint_api.module	30 Nov 2010 05:39:13 -0000	1.10
+++ uc_linkpoint_api.module	7 Dec 2010 00:54:41 -0000
@@ -1,404 +1,662 @@
-<?php
-// $Id: uc_linkpoint_api.module,v 1.10 2010/11/30 05:39:13 jrust Exp $
-/**
- * @file
- * A module used for Linkpint API payment gateway
- *
- * Developed by Elvis McNeely (http://www.lafayetteweb.com | ubercart@mcneelycorp.com | customizations possible).
- * Revision and further integration by Nicolas Soro | nicksoro@gmail.com.
- * "Porting" to Ubercart 2.x/Drupal 6.x by Sean Corrales | sean.corrales@interworksinc.com
- * 6.x Version released by Jason Rust | jason@rustedcode.com
- */
-
-define('UC_LINKPOINT_LIVE_URL', 'https://secure.linkpt.net:1129/');
-define('UC_LINKPOINT_TEST_URL', 'https://staging.linkpt.net:1129/');
-
-/*******************************************************************************
- * Hook Functions (Ubercart)
- ******************************************************************************/
-
-function uc_linkpoint_api_payment_gateway() {
-  $gateways[] = array(
-    'id' => 'linkpointapi',
-    'title' => t('Linkpoint API'),
-    'description' => t('Process credit card payments using the service of Linkpint API.'),
-    'settings' => 'uc_linkpoint_api_settings_form',
-    'credit' => 'uc_linkpoint_api_charge',
-  );
-
-  return $gateways;
-}
-
-/*******************************************************************************
- * Callback Functions, Forms, and Tables
- ******************************************************************************/
-
-/**
- * Callback for payment gateway settings.
- */
-function uc_linkpoint_api_settings_form() {
-  $form['linkpoint_api_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Linkpoint API settings'),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_login_id'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Store Number'),
-    '#default_value' => variable_get('linkpoint_api_login_id', ''),
-    '#description' => t("Enter your 10 digit Store Number assigned to you by Linkpoint."),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_transaction_key'] = array(
-    '#type' => 'textfield',
-    '#title' => t('PEM File'),
-    '#default_value' => variable_get('linkpoint_api_transaction_key', ''),
-    '#description' => t('Enter the absolute path to your PEM file (from your server root). This is required for Linkpoint API to work. Download yours from <a target="_blank" href="http://www.linkpointcentral.com">Linkpoint Central</a> > Support > Download Center using the API button. The file should be named "####.pem", where #### represents your store number.'),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_transaction_mode'] = array(
-    '#type' => 'select',
-    '#title' => t('Transaction mode'),
-    '#description' => t('Transaction mode used for processing orders.  You can use these options to test certain responses from the live server.  If you need to do extensive testing you should create a test account at Linkpoint and select the Test server below.'),
-    '#options' => array(
-      'LIVE' => t('Production Mode'),
-      'GOOD' => t('Test Approved Response'),
-      'DECLINE' => t('Test Decline Response'),
-      'DUPLICATE' => t('Test Duplicate Response'),
-    ),
-    '#default_value' => variable_get('linkpoint_api_transaction_mode', 'LIVE'),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_transaction_server'] = array(
-    '#type' => 'select',
-    '#title' => t('Transaction server'),
-    '#description' => t('Unless you have set up a test account at Linkpoint
-    and are doing extensive testing this should be Live.'),
-    '#options' => array(
-      'live' => t('Live Server'),
-      'test' => t('Test Server'),
-    ),
-    '#default_value' => variable_get('linkpoint_api_transaction_server', 'live'),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_transaction_method'] = array(
-    '#type' => 'radios',
-    '#title' => t('Transaction Type'),
-    '#default_value' => variable_get('linkpoint_api_transaction_method', 'PREAUTH'),
-    '#options' => array(
-      'PREAUTH' => t('PREAUTH'),
-      'SALE' => t('SALE'),
-    ),
-    '#description' => t('Linkpoint states for tangible items or anything that will be shipped to always use PREAUTH. If you are selling a download item etc, use SALE. PREAUTH authorizes a credit card but does not charge it. Go to <a target= "_blank" href= "http://www.linkpointcentral.com">Linkpoint Central</a> > Reports > Transctions to complete the transaction. SALE automatically charges the card.'),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_avs'] = array(
-    '#type' => 'select',
-    '#title' => t('Address Verification Service (AVS)'),
-    '#default_value' => variable_get('linkpoint_api_avs', array()),
-    '#multiple' => TRUE,
-    '#options' => array(
-      ''   => t('- Disable AVS Check -'),
-      'YY' => t('Address & zip match (YY)'),
-      'NY' => t('Only zip matches (NY)'),
-      'YN' => t('Only street matches (YN)'),
-      'XX' => t('AVS requested, but not received (XX)'),
-    ),
-    '#description' => t('Linkpoint returns an AVS response for all transactions which you may use to prevent fraud.  Select which AVS responses you will accept.'),
-  );
-  $form['linkpoint_api_settings']['linkpoint_api_cvv'] = array(
-    '#type' => 'select',
-    '#title' => t('Card Code Verification Service (CVV)'),
-    '#default_value' => variable_get('linkpoint_api_cvv', array()),
-    '#multiple' => TRUE,
-    '#options' => array(
-      ''  => t('- Disable CVV Check -'),
-      'M' => t('Card code matches (M)'),
-      'N' => t('Card code does not match (N)'),
-      'P' => t('Not processed (P)'),
-      'U' => t('Issuer is not certified (U)'),
-      'X' => t('No response from the credit card association was received (X)'),
-    ),
-    '#description' => t('Linkpoint returns the CVV response for all transactions which you may use to prevent fraud.  Select which CVV responses you will accept.  You will also want to make sure you enable the CVV code for the credit card checkout pane.'),
-  );
-
-  return $form;
-}
-
-function uc_linkpoint_api_charge($order_id, $amount, $data) {
-  global $user;
-  if (!function_exists('curl_init')) {
-    drupal_set_message(t('The Linkpint API service requires curl.  Please talk to your system administrator to get this configured.'));
-    return array('success' => FALSE);
-  }
-
-  // This handles the case where a user cancels their order on the checkout screen
-  if ($_SESSION['uc_linkpoint_order'] != $order_id) {
-    $_SESSION['uc_linkpoint_order'] = $order_id;
-    unset($_SESSION['uc_linkpoint_attempt']);
-  }
-  // If users attempts to submit order more than once then modify Order ID so
-  // as to avoid a duplicate transaction error.
-  $_SESSION['uc_linkpoint_attempt'] = empty($_SESSION['uc_linkpoint_attempt']) ? 1 : ($_SESSION['uc_linkpoint_attempt'] + 1);
-  $oid = $_SESSION['uc_linkpoint_attempt'] > 1 ? "$order_id-{$_SESSION['uc_linkpoint_attempt']}" : $order_id;
-  $order = uc_order_load($order_id);
-
-  // Calculate the shipping / tax / subtotal amounts.
-  $shipping = $tax = $subtotal = 0;
-
-  if (is_array($order->line_items)) {
-    foreach ($order->line_items as $item) {
-      if ($item['type'] == 'shipping') {
-        $shipping += $item['amount'];
-      }
-    }
-  }
-
-  if (module_exists('uc_taxes')) {
-    foreach (uc_taxes_calculate($order) as $tax_item) {
-      $tax += $tax_item->amount;
-    }
-  }
-
-  $subtotal = $order->order_total - $tax - $shipping;
-
-  // Prepare a description of the order.
-  $description = '';
-
-  if (is_array($order->products)) {
-    foreach ($order->products as $product) {
-      if (!empty($description)) {
-        $description .= ' // ';
-      }
-      $description .= $product->title .' x'. $product->qty;
-      if ($product->data['attributes']) {
-        foreach ($product->data['attributes'] as $key => $value) {
-          $description .= ', '. $key .': '. $value;
-        }
-      }
-    }
-  }
-
-  $description = substr($description, 0, 255);
-
-
-/*******************************************************************************
- * Convert some data before sending it to Linkpoint
- ******************************************************************************/
-  $cardexpyr = substr($order->payment_details['cc_exp_year'], 2, 2);
-
-  $submit_data = array(
-    'x_country' => uc_get_country_data(array('country_id' => $order->billing_country)),
-    'x_ship_to_country' => uc_get_country_data(array('country_id' => $order->delivery_country)),
-  );
-
-/*******************************************************************************
- * Build XML string
- ******************************************************************************/
-
-  $xml = "<order>";
-
-  $xml .= _uc_linkpoint_api_required_xml_entities(variable_get('linkpoint_api_transaction_method', 'PREAUTH'), $oid, $order_id);
-
-  $xml .= "<payment>";
-
-  // Only break down the payment information if it totals up to the chargetotal.
-  if ($subtotal + $tax + $shipping == $amount) {
-    if (!empty($subtotal)) $xml .= "<subtotal>" . $subtotal . "</subtotal>";
-    if (!empty($tax)) $xml .= "<tax>" . $tax . "</tax>";
-    if (!empty($shipping)) $xml .= "<shipping>" . $shipping . "</shipping>";
-  }
-
-  $xml .= "<chargetotal>" . $amount . "</chargetotal>";
-  $xml .= "</payment>";
-
-  $xml .= "<billing>";
-  if (!empty($user->uid)) $xml .= "<userid>" . $user->uid . "</userid>";
-  $xml .= "<name>" . $order->billing_first_name. " " . $order->billing_last_name . "</name>";
-  $xml .= "<company>" . $order->billing_company . "</company>";
-  $xml .= "<address1>" . $order->billing_street1 . "</address1>";
-  $xml .= "<address2>" . $order->billing_street2 . "</address2>";
-  $xml .= "<addrnum>" . $order->billing_street1 . "</addrnum>";
-  $xml .= "<city>" . $order->billing_city . "</city>";
-  $xml .= "<state>" . uc_get_zone_code($order->billing_zone) . "</state>";
-  $xml .= "<zip>" . $order->billing_postal_code . "</zip>";
-  $xml .= "<country>" . $submit_data['x_country'][0]['country_iso_code_2'] . "</country>";
-  $xml .= "<phone>" . $order->billing_phone ."</phone>";
-  $xml .= "<email>" . $order->primary_email . "</email>";
-  $xml .= "</billing>";
-
-  $xml .= "<shipping>";
-  $xml .= "<name>" . $order->delivery_first_name . " " . $order->delivery_last_name . "</name>";
-  $xml .= "<address1>" . $order->delivery_street1 . "</address1>";
-  $xml .= "<address2>" . $order->delivery_street2 . "</address2>";
-  $xml .= "<city>" . $order->delivery_city . "</city>";
-  $xml .= "<state>" . uc_get_zone_code($order->delivery_zone) . "</state>";
-  $xml .= "<zip>" . $order->delivery_postal_code . "</zip>";
-  $xml .= "<country>" . $submit_data['x_ship_to_country'][0]['country_iso_code_2'] . "</country>";
-  $xml .= "</shipping>";
-
-  $xml .= "<creditcard>";
-  $xml .= "<cardnumber>" . $order->payment_details['cc_number'] . "</cardnumber>";
-  $xml .= "<cardexpmonth>" . $order->payment_details['cc_exp_month'] . "</cardexpmonth>";
-  $xml .= "<cardexpyear>" . $cardexpyr . "</cardexpyear> ";
-  $xml .= "<cvmvalue>" . $order->payment_details['cc_cvv'] . "</cvmvalue>";
-  $xml .= "<cvmindicator>" . $cvmindicator . "</cvmindicator>";
-  $xml .= "</creditcard>";
-
-  $xml .= "<notes>";
-  $xml .= "<comments>" . $description . "</comments>";
-  $xml .= "</notes>";
-
-  $xml .= "</order>";
-
-  $retarr = _uc_linkpoint_api_execute_txn($xml);
-
-  $x_response_code = $retarr['r_approved'];
-  $x_response_text = $retarr['r_error'];
-  $x_approval_code = $retarr['r_code'];
-  $x_avs_code = substr($retarr['r_avs'], 0, 2);
-  $x_cvv_code = substr($retarr['r_avs'], -1);
-  $approved_text = $retarr['r_approved'] . ' ' .  $retarr['r_avs'] . ' ' .  $retarr['r_code'];
-
-  $allowed_cvv = array_filter(variable_get('linkpoint_api_cvv', array()));
-  if ($x_response_code == 'APPROVED' && !empty($allowed_cvv) && !in_array($x_cvv_code, $allowed_cvv) && _uc_linkpoint_api_void_order($oid, $order_id)) {
-    $x_response_code = 'DECLINED';
-    $x_response_text = t('CVV verification failed because the code was "!code"', array('!code' => $x_cvv_code));
-  }
-
-  $allowed_avs = array_filter(variable_get('linkpoint_api_avs', array()));
-  if ($x_response_code == 'APPROVED' && !empty($allowed_avs) && !in_array($x_avs_code, $allowed_avs) && _uc_linkpoint_api_void_order($oid, $order_id)) {
-    $x_response_code = 'DECLINED';
-    $x_response_text = t('Adress Verification System (AVS) failed because the code was "!code"', array('!code' => $x_avs_code));
-  }
-
-  if ($x_response_code != 'APPROVED') {
-    $message = t('Credit card declined for !amount with error: !error', array('!amount' => uc_currency_format($amount), '!error' => $x_response_text));
-    $result = array(
-      'success' => FALSE,
-      'comment' => $message,
-      'message' => $message,
-      'uid' => $user->uid,
-    );
-  }
-  else {
-    $message = t('Credit card processed successfully for !amount.  Approval code: !code. CVV response: !cvv. AVS response: !avs',
-                 array('!amount' => uc_currency_format($amount), '!code' => $approved_text, '!cvv' => $x_cvv_code, '!avs' => $x_avs_code));
-    $result = array(
-      'success' => TRUE,
-      'comment' => $message,
-      'message' => $message,
-      'uid' => $user->uid,
-    );
-    unset($_SESSION['uc_linkpoint_attempt']);
-  }
-  uc_order_comment_save($order_id, $user->uid, $message, 'admin');
-  return $result;
-}
-
-/**
- * Voids an order
- * @return bool True if successful
- */
-function _uc_linkpoint_api_void_order($oid, $order_id) {
-  global $user;
-  // Orders can't be voided in PREAUTH state, they have to be moved to POSTAUTH first
-  if (variable_get('linkpoint_api_transaction_method', 'PREAUTH') == 'PREAUTH') {
-    $xml = "<order>";
-    $xml .= _uc_linkpoint_api_required_xml_entities('POSTAUTH', $oid, $order_id);
-    $xml .= "</order>";
-    $retarr = _uc_linkpoint_api_execute_txn($xml);
-    if (!empty($retarr['r_error'])) {
-      uc_order_comment_save($order_id, $user->uid, t('Error when trying to POSTAUTH transaction: !error', array('!error' => $retarr['r_error'])), 'admin');
-    }
-  }
-
-  $xml = "<order>";
-  $xml .= _uc_linkpoint_api_required_xml_entities('VOID', $oid);
-  $xml .= "</order>";
-
-  $retarr = _uc_linkpoint_api_execute_txn($xml);
-  if ($retarr['r_approved'] == 'APPROVED') {
-    return TRUE;
-  }
-  else {
-    uc_order_comment_save($order_id, $user->uid, t('Error when trying to VOID transaction: !error', array('!error' => $retarr['r_error'])), 'admin');
-    return FALSE;
-  }
-}
-
-/**
- * Returns the orderoptions, merchantinfo, and transactiondetails entities
- * which are required for all transactions
- */
-function _uc_linkpoint_api_required_xml_entities($ordertype, $oid, $invoice_number = NULL) {
-  $xml =  "<orderoptions>";
-  $xml .= "<result>" . variable_get('linkpoint_api_transaction_mode', 'LIVE') . "</result>";
-  $xml .= "<ordertype>" . $ordertype . "</ordertype>";
-  $xml .= "</orderoptions>";
-
-  $xml .= "<merchantinfo>";
-  $xml .= "<configfile>" . variable_get('linkpoint_api_login_id', '') . "</configfile>";
-  $xml .= "<keyfile>" . variable_get('linkpoint_api_transaction_key', '') . "</keyfile>";
-  $xml .= "</merchantinfo>";
-
-  $xml .= "<transactiondetails>";
-  $xml .= "<transactionorigin>ECI</transactionorigin>";
-  $xml .= "<oid>" . $oid . "</oid>";
-  if (!is_null($invoice_number)) {
-    $xml .= "<invoice_number>" . $invoice_number . "</invoice_number>";
-  }
-  $xml .= "<ip>" . ip_address() . "</ip>";
-  $xml .= "</transactiondetails>";
-
-  return $xml;
-}
-
-/**
- * Executes the transaction against the Linkpoint server
- * @return array An array based on the XML that linkpoint returns
- */
-function _uc_linkpoint_api_execute_txn($xml) {
-  $ch = curl_init();
-  if (variable_get('linkpoint_api_transaction_server', 'live') == 'test') {
-    $url = UC_LINKPOINT_TEST_URL;
-    curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
-    curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
-  }
-  else {
-    $url = UC_LINKPOINT_LIVE_URL;
-  }
-  curl_setopt($ch, CURLOPT_URL, $url);
-  curl_setopt($ch, CURLOPT_VERBOSE, 1);
-  curl_setopt($ch, CURLOPT_POST, 1);
-  curl_setopt($ch, CURLOPT_POSTFIELDS, _uc_linkpoint_api_scrub_xml($xml));
-  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-  curl_setopt($ch, CURLOPT_SSLCERT, variable_get('linkpoint_api_transaction_key', ''));
-  $buffer = curl_exec($ch);
-
-  if ($error = curl_error($ch)) {
-    $error .= '<br />The file: %file ';
-    if (file_exists(variable_get('linkpoint_api_transaction_key', ''))) {
-      $error .= 'exists: check your pass phrase or verify that you have selected the right "transaction server" for your certificate (PEM file).';
-    }
-    else {
-      $error .= 'does not exist: check your path.';
-    }
-    watchdog('uc_linkpoint_api', $error, array('%file' => variable_get('linkpoint_api_transaction_key', '')), WATCHDOG_ERROR);
-  }
-  curl_close($ch);
-
-  if (strlen($buffer) < 2) {
-    $buffer = "<r_error>Sorry - Could not connect to payment gateway.</r_error>";
-  }
-
-  preg_match_all ("/<(.*?)>(.*?)\</", $buffer, $outarr, PREG_SET_ORDER);
-
-  $n = 0;
-  while (isset($outarr[$n])) {
-    $retarr[$outarr[$n][1]] = strip_tags($outarr[$n][0]);
-    $n++;
-  }
-
-  return $retarr;
-}
-
-function _uc_linkpoint_api_scrub_xml($xml) {
-  $xml = str_replace("&", "and", $xml);
-  $xml = str_replace("'", " ", $xml);
-  return $xml;
-}
+<?php
+// $Id: uc_linkpoint_api.module,v 1.9 2010/11/30 05:36:48 jrust Exp $
+/**
+ * @file
+ * A module used for Linkpint API payment gateway
+ *
+ * Developed by Elvis McNeely (http://www.lafayetteweb.com | ubercart@mcneelycorp.com | customizations possible).
+ * Revision and further integration by Nicolas Soro | nicksoro@gmail.com.
+ * "Porting" to Ubercart 2.x/Drupal 6.x by Sean Corrales | sean.corrales@interworksinc.com
+ * 6.x Version released by Jason Rust | jason@rustedcode.com
+ */
+
+define('UC_LINKPOINT_LIVE_URL', 'https://secure.linkpt.net:1129/');
+define('UC_LINKPOINT_TEST_URL', 'https://staging.linkpt.net:1129/');
+
+/*******************************************************************************
+ * Hook Functions (Ubercart)
+ ******************************************************************************/
+
+function uc_linkpoint_api_payment_gateway() {
+  $gateways[] = array(
+    'id' => 'linkpointapi',
+    'title' => t('Linkpoint API'),
+    'description' => t('Process credit card payments using the service of Linkpint API.'),
+    'settings' => 'uc_linkpoint_api_settings_form',
+    'credit' => 'uc_linkpoint_api_charge',
+    'credit_txn_types' => array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_PRIOR_AUTH_CAPTURE, UC_CREDIT_AUTH_CAPTURE),
+  );
+
+  return $gateways;
+}
+
+/*******************************************************************************
+ * Callback Functions, Forms, and Tables
+ ******************************************************************************/
+
+/**
+ * Callback for payment gateway settings.
+ */
+function uc_linkpoint_api_settings_form() {
+  $form['linkpoint_api_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Linkpoint API settings'),
+  );
+  $form['linkpoint_api_settings']['linkpoint_api_login_id'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Store Number'),
+    '#default_value' => variable_get('linkpoint_api_login_id', ''),
+    '#description' => t("Enter your 10 digit Store Number assigned to you by Linkpoint."),
+  );
+  $form['linkpoint_api_settings']['linkpoint_api_transaction_key'] = array(
+    '#type' => 'textfield',
+    '#title' => t('PEM File'),
+    '#default_value' => variable_get('linkpoint_api_transaction_key', ''),
+    '#description' => t('Enter the absolute path to your PEM file (from your server root). This is required for Linkpoint API to work. Download yours from <a target="_blank" href="http://www.linkpointcentral.com">Linkpoint Central</a> > Support > Download Center using the API button. The file should be named "####.pem", where #### represents your store number.'),
+  );
+  $form['linkpoint_api_settings']['linkpoint_api_transaction_mode'] = array(
+    '#type' => 'select',
+    '#title' => t('Transaction mode'),
+    '#description' => t('Transaction mode used for processing orders. You can use these options to test certain responses from the live server.  If you need to do extensive testing you should create a test account at Linkpoint and select the Test server below.'),
+    '#options' => array(
+      'LIVE' => t('Production Mode'),
+      'GOOD' => t('Test Approved Response'),
+      'DECLINE' => t('Test Decline Response'),
+      'DUPLICATE' => t('Test Duplicate Response'),
+    ),
+    '#default_value' => variable_get('linkpoint_api_transaction_mode', 'LIVE'),
+  );
+  $form['linkpoint_api_settings']['linkpoint_api_order_prefix'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Order Prefix'),
+    '#default_value' => variable_get('linkpoint_api_order_prefix', ''),
+    '#description' => t('Enter a prefix to prepend to the ordernumber before sending to Linkpoint. Useful if you have multiple websites using the same merchant account, and need to pass in a unique order id to this store.'),
+  );
+  $form['linkpoint_api_settings']['linkpoint_api_transaction_server'] = array(
+    '#type' => 'select',
+    '#title' => t('Transaction server'),
+    '#description' => t('Unless you have set up a test account at Linkpoint
+    and are doing extensive testing this should be Live.'),
+    '#options' => array(
+      'live' => t('Live Server'),
+      'test' => t('Test Server'),
+    ),
+    '#default_value' => variable_get('linkpoint_api_transaction_server', 'live'),
+  );
+  $form['fraud'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Fraud prevention'),
+    '#description' => t('When a credit card transaction occurs, AVS and CVV response codes are returned. These can be used to determine fraud. Unfortunately, this information is received after a transaction has already occured. If you chose to use the fraud detection, the transaction will be voided after the card has already been charged. This in unavoidable.'),
+  );
+  $form['fraud']['linkpoint_api_avs'] = array(
+    '#type' => 'select',
+    '#title' => t('Address Verification Service (AVS)'),
+    '#default_value' => variable_get('linkpoint_api_avs', array()),
+    '#multiple' => TRUE,
+    '#options' => array(
+      ''   => t('- Disable AVS Check -'),
+      'YY' => t('Address & zip match (YY)'),
+      'NY' => t('Only zip matches (NY)'),
+      'YN' => t('Only street matches (YN)'),
+      'XX' => t('AVS requested, but not received (XX)'),
+    ),
+    '#description' => t('Linkpoint returns an AVS response for all transactions which you may use to prevent fraud. Select which AVS responses you will accept.'),
+  );
+  $form['fraud']['linkpoint_api_cvv'] = array(
+    '#type' => 'select',
+    '#title' => t('Card Code Verification Service (CVV)'),
+    '#default_value' => variable_get('linkpoint_api_cvv', array()),
+    '#multiple' => TRUE,
+    '#options' => array(
+      ''  => t('- Disable CVV Check -'),
+      'M' => t('Card code matches (M)'),
+      'N' => t('Card code does not match (N)'),
+      'P' => t('Not processed (P)'),
+      'U' => t('Issuer is not certified (U)'),
+      'X' => t('No response from the credit card association was received (X)'),
+    ),
+    '#description' => t('Linkpoint returns the CVV response for all transactions which you may use to prevent fraud.  Select which CVV responses you will accept.  You will also want to make sure you enable the CVV code for the credit card checkout pane.'),
+  );
+
+  return $form;
+}
+
+/**
+ * Validate function for our settings. Keep in mind that the settings form above is combined with a larger form,
+ * So we need to tell that larger form about it in a form_alter (below)
+ **/
+function uc_linkpoint_api_settings_form_validate($form, &$form_state) {
+  // Verify that the given filename and path exists and is accessible
+  if (!empty($form_state['values']['linkpoint_api_transaction_key']) && !is_file($form_state['values']['linkpoint_api_transaction_key'])) {
+    form_set_error('linkpoint_api_transaction_key', t('%dir is not a valid file or directory', array('%dir' => $form_state['values']['linkpoint_api_transaction_key'])));
+  }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ * Let the payment gateway settings form know that we are adding another validate function
+ */
+function uc_linkpoint_api_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'uc_payment_gateways_form') {
+    $form['#validate'][] = 'uc_linkpoint_api_settings_form_validate';
+  }
+}
+
+/*******************************************************************************
+ * Main credit card charge function
+ ******************************************************************************/
+function uc_linkpoint_api_charge($order_id, $amount, $data) {
+  global $user;
+  if (!function_exists('curl_init')) {
+    drupal_set_message(t('The Linkpint API service requires curl.  Please talk to your system administrator to get this configured.'));
+    return array('success' => FALSE);
+  }
+
+  // This handles the case where a user cancels their order on the checkout screen
+  if ($_SESSION['uc_linkpoint_order'] != $order_id) {
+    $_SESSION['uc_linkpoint_order'] = $order_id;
+    unset($_SESSION['uc_linkpoint_attempt']);
+  }
+
+  $order = uc_order_load($order_id);
+  
+  // Determine the unique order ID for this transaction
+  if ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
+    $unique_oid = $data['auth_id'];
+  } else {
+    // Order numbers must be unique in Linkpoint, so if someone attempted a transaction (then failed),
+    // then went back and changed details, we must alter the order ID to avoid an error from Linkpoint
+    $order_id = $order->order_id;
+    $_SESSION['uc_linkpoint_attempt'] = empty($_SESSION['uc_linkpoint_attempt']) ? 1 : ($_SESSION['uc_linkpoint_attempt'] + 1);
+    $unique_oid = $_SESSION['uc_linkpoint_attempt'] > 1 ? "$order_id-{$_SESSION['uc_linkpoint_attempt']}" : $order_id;
+    $unique_oid = variable_get('linkpoint_api_order_prefix', '').$unique_oid;
+  }
+  
+  // Build transaction and send to Linkpoint
+  $transaction_xml = _uc_linkpt_construct_transaction_xml($order, $unique_oid, $data, _uc_linkpt_txn_map($data['txn_type']), $amount);
+  $transaction_result = _uc_linkpt_send_transaction($transaction_xml);
+  
+  // transaction did not pass due to error
+  if ($transaction_result['approved'] == 'ERROR') {
+    $message = t('Could not connect to payment gateway. Error: @error',array('@error' => $transaction_result['error']));
+    $result = array(
+      'success' => FALSE,
+      'comment' => $message,
+      'message' => $message,
+      'uid' => $user->uid,
+    );
+    return $result;
+  }
+
+  // Credit card was declined for whatever reason
+  if ($transaction_result['approved'] != 'APPROVED') {
+    $result = array(
+      'success' => FALSE,
+      'comment' => t('Credit card payment declined: @text', array('@text' => $transaction_result['error'])),
+      'message' => t('Credit card payment declined: @text', array('@text' => $transaction_result['error'])),
+      'uid' => $user->uid,
+    );
+
+  // Credit card was accepted, continue on but check if it passes fraud detection
+  } else {
+    // log the transaction that just occured
+    _uc_linkpt_save_order_comment($order_id, $data, $transaction_result, _uc_linkpt_txn_map($data['txn_type']));
+        
+    // Should this order be voided?
+    if (($fraud_reason = _uc_linkpt_is_fraud($transaction_result)) != null) {
+      // Need to void order... indicate such in the log
+      uc_order_comment_save($order->order_id, $user->uid, t('Unaccepted order: !voidreason', array('!voidreason' => $fraud_reason)), 'admin');
+
+      // Move order to POSTAUTH if it was a PREAUTH. Needs to happen before we can void
+      if ($data['txn_type'] == UC_CREDIT_AUTH_ONLY) {
+      
+        // Build transaction and send to Linkpoint
+        $transaction_xml = _uc_linkpt_construct_transaction_xml($order, $unique_oid, $data, _uc_linkpt_txn_map(UC_CREDIT_PRIOR_AUTH_CAPTURE));
+        $transaction_result = _uc_linkpt_send_transaction($transaction_xml);
+        
+        if ($transaction_result['approved'] != 'APPROVED') {
+          uc_order_comment_save($order->order_id, $user->uid, t('Voiding error while trying move transaction to !ordertype: @error', array('!ordertype' => 'POSTAUTH', '@error' => $transaction_result['error'])), 'admin');
+        // No error, so save the result in the logs
+        } else {
+          _uc_linkpt_save_order_comment($order_id, $data, $transaction_result, _uc_linkpt_txn_map(UC_CREDIT_PRIOR_AUTH_CAPTURE));
+        }
+      }
+      // move order to VOID
+      $transaction_xml = _uc_linkpt_construct_transaction_xml($order, $unique_oid, $data, _uc_linkpt_txn_map(UC_CREDIT_VOID));
+      $transaction_result = _uc_linkpt_send_transaction($transaction_xml);
+      
+      if ($transaction_result['approved'] != 'APPROVED') {
+        uc_order_comment_save($order->order_id, $user->uid, t('Voiding error while trying move transaction to !ordertype: @error', array('!ordertype' => 'VOID', '@error' => $transaction_result['error'])), 'admin');
+      // No error, so save the result in the logs
+      } else {
+        _uc_linkpt_save_order_comment($order_id, $data, $transaction_result, _uc_linkpt_txn_map(UC_CREDIT_VOID));
+      }
+
+      $result = array(
+        'success' => FALSE,
+        'comment' => t('Credit card did not pass fraud detection: @reason',array('@reason' => $fraud_reason)),
+        'message' => t('Credit card did not pass fraud detection: @reason',array('@reason' => $fraud_reason)),
+        'uid' => $user->uid,
+      );
+    
+    // No, continue to process it
+    } else {
+  
+      $result = array(
+        'success' => TRUE,
+        'comment' => t('Credit card payment processed successfully.<br/>Code: @code', array('@code' => $response['approvalcode'])),
+        'message' => t('Credit card payment processed successfully.<br/>Code: @code', array('@code' => $response['approvalcode'])),
+        'uid' => $user->uid,
+      );
+      unset($_SESSION['uc_linkpoint_attempt']);
+      
+      // Took these next lines from Authorize.net's implementation
+      // If this was an authorization only transaction...
+      if ($data['txn_type'] == UC_CREDIT_AUTH_ONLY) {
+        // Log the authorization to the order.
+        uc_credit_log_authorization($order->order_id, $transaction_result['order_num'], $amount);
+      }
+      elseif ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
+        uc_credit_log_prior_auth_capture($order->order_id, $data['auth_id']);
+      }
+      
+      // Don't log this as a payment money wasn't actually captured.
+      if (in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY))) {
+        $result['log_payment'] = FALSE;
+      }
+    
+    }
+  }
+    
+  return $result;
+}
+
+/*******************************************************************************
+ * Internal helper functions
+ ******************************************************************************/
+
+/**
+ * Determines if the transaction should be voided for fraud. Compares the user
+ * defined accepted values for CVV and AVS and returns why the transaction should be voided
+ * or null if it shouldn't
+ */
+function _uc_linkpt_is_fraud($response) {
+  $allowed_cvv = array_filter(variable_get('linkpoint_api_cvv', array()));
+  $allowed_avs = array_filter(variable_get('linkpoint_api_avs', array()));
+  if (empty($allowed_cvv) && empty($allowed_avs)) {
+    return null;
+  }
+  
+  if (!in_array($response['avs'],$allowed_avs)) {
+    return 'Address on card does not match billing address.';
+  }
+  
+  if (!in_array($response['cvv'],$allowed_cvv)) {
+    return 'CVV code on card does not match provided code.';  
+  }
+}
+
+/**
+ * Builds the XML needed for a particular transaction with Linkpoint, as determined by
+ * the value of ordertype. Could be PREAUTH, SALE, etc...
+ **/
+function _uc_linkpt_construct_transaction_xml($order, $unique_oid, $data, $ordertype, $amount = 0) {
+  global $user;
+  $xml = "<order>";
+  $xml .= _uc_linkpt_xml_merchant_info($order);
+  $xml .= _uc_linkpt_xml_transaction_details($order->order_num, $unique_oid);
+  $xml .= _uc_linkpt_xml_order_options($order, $ordertype);
+  if ($ordertype == _uc_linkpt_txn_map(UC_CREDIT_AUTH_ONLY) || $ordertype == _uc_linkpt_txn_map(UC_CREDIT_AUTH_CAPTURE)) {
+    $xml .= _uc_linkpt_xml_billing($order);
+    $xml .= _uc_linkpt_xml_shipping($order);
+    $xml .= _uc_linkpt_xml_credit_card($order);
+    $xml .= _uc_linkpt_xml_payment($order, $amount);
+    $xml .= _uc_linkpt_xml_notes($order);
+  }
+  $xml .= "</order>";
+  $xml = _uc_linkpt_xml_clean($xml);
+
+  return $xml;
+}
+
+/**
+ * Initiate a cURL session and send over the pre-built XML to First Data for processing
+ **/
+function _uc_linkpt_send_transaction($xml) {
+  // Open the cURL session
+  $ch = curl_init();
+  
+  // Set the cURL options
+  // Determine the posting URL
+  if (variable_get('linkpoint_api_transaction_server', 'live') == "test") {
+    curl_setopt($ch, CURLOPT_URL, UC_LINKPOINT_TEST_URL);
+    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // test server does not support
+    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // these two params
+  } else {
+    curl_setopt($ch, CURLOPT_URL, UC_LINKPOINT_LIVE_URL);
+  }
+  curl_setopt($ch, CURLOPT_VERBOSE, 1);         // Present verbose error output, to standard error
+  curl_setopt($ch, CURLOPT_POST, 1);            // POST Request
+  curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);   // Data to send in the POST request
+  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  // Puts output to a returned string
+  curl_setopt($ch, CURLOPT_SSLCERT, variable_get('linkpoint_api_transaction_key', ''));     // The public cert key generated by First Data
+  
+  // Execute the cURL session
+  $result = curl_exec($ch);
+  
+  // There was a cURL error
+  if ($result === false) {
+    $response['approved'] = 'ERROR';
+    $response['error'] = curl_error($ch);
+    return $response;
+  }
+  
+  // Close cURL session
+  curl_close($ch);
+  
+  // No cURL error, format the output from XML into an array
+  preg_match_all ("/<(.*?)>(.*?)\</", $result, $outarr, PREG_SET_ORDER);
+  $n = 0;
+  while (isset($outarr[$n])) {
+    $retarr[$outarr[$n][1]] = strip_tags($outarr[$n][0]);
+    $n++;
+  }
+
+  $response = array(
+    'approved' => $retarr['r_approved'],
+    'error' => $retarr['r_error'],
+    'approvalcode' => $retarr['r_code'],
+    'avs' => substr($retarr['r_avs'], 0, 2),
+    'cvv' => substr($retarr['r_avs'], -1),
+    'response_message' => $retarr['r_message'],
+    'order_num' => $retarr['r_ordernum'],
+  );
+  
+  return $response;
+}
+
+/**
+ * Construct and post a detailed admin order log that prints all relevant information
+ * returned from Linkpoint regarding the transaction
+ */
+function _uc_linkpt_save_order_comment($order_id, $data, $response, $ordertype) {
+  // Get human readable interpretations of the AVS and CVV codes
+  $avs_text = _uc_linkpt_avs_to_text($response['avs']);
+  $cvv_text = _uc_linkpt_cvv_to_text($response['cvv']);
+
+  // Build log text and add to logs
+  $comment = '<b>Linkpoint Transaction Attempt:</b> (Order Num: '. $response['order_num'] .')<br />';
+  if (!empty($response['approved'])) $comment .= $ordertype . ": <b>" .$response['approved']."</b><br/>";
+  if (!empty($response['message'])) $comment .= "Message: ".$response['message'];
+  if (!empty($response['approvalcode'])) $comment .= "Approval Code: ".$response['approvalcode']."<br/>";
+  if (!empty($response['error'])) $comment .= "Error Code: ".$response['error']."<br/>";
+  if ($ordertype == _uc_linkpt_txn_map(UC_CREDIT_AUTH_ONLY) || $ordertype == _uc_linkpt_txn_map(UC_CREDIT_AUTH_CAPTURE)) {
+    if (!empty($response['avs'])) $comment .= "AVS: ".$avs_text."<br/>";
+    if (!empty($response['cvv'])) $comment .= "CVV: ".$cvv_text;
+  }
+  uc_order_comment_save($order_id, $user->uid, $comment, 'admin');
+}
+
+/**
+ * Construct XML billing entity
+ **/
+function _uc_linkpt_xml_billing($order) {
+  global $user;
+  $billing_country = _uc_linkpt_get_country_iso($order->billing_country);
+  $xml = '';
+  $xml .="<billing>";
+    if (!empty($user->uid)) $xml .="<userid>" . $user->uid . "</userid>";
+    $xml .="<name>" . $order->billing_first_name. " " . $order->billing_last_name . "</name>";
+    $xml .="<company>" . $order->billing_company . "</company>";
+    $xml .="<address1>" . $order->billing_street1 . "</address1>";
+    $xml .="<address2>" . $order->billing_street2 . "</address2>";
+    $xml .="<addrnum>" . $order->billing_street1 . "</addrnum>";
+    $xml .="<city>" . $order->billing_city . "</city>";
+    $xml .="<state>" . uc_get_zone_code($order->billing_zone) . "</state>";
+    $xml .="<zip>" . $order->billing_postal_code . "</zip>";
+    $xml .="<country>" . $billing_country . "</country>";
+    $xml .="<phone>" . $order->billing_phone ."</phone>";
+    $xml .="<email>" . $order->primary_email . "</email>";
+  $xml .="</billing>";
+  return $xml;
+}
+
+/**
+ * Construct XML shipping entity
+ **/
+function _uc_linkpt_xml_shipping($order) {
+  $delivery_country = _uc_linkpt_get_country_iso($order->delivery_country);
+  $xml = '';
+  $xml .="<shipping>";
+    $xml .="<name>" . $order->delivery_first_name . " " . $order->delivery_last_name . "</name>";
+    $xml .="<address1>" . $order->delivery_street1 . "</address1>";
+    $xml .="<address2>" . $order->delivery_street2 . "</address2>";
+    $xml .="<city>" . $order->delivery_city . "</city>";
+    $xml .="<state>" . uc_get_zone_code($order->delivery_zone) . "</state>";
+    $xml .="<zip>" . $order->delivery_postal_code . "</zip>";
+    $xml .="<country>" . $delivery_country . "</country>";
+//  $xml .="<phone>" . $order->delivery_phone ."</phone>"; // breaks XML with First Data
+//  $xml .="<email>" . $order->primary_email . "</email>"; // this as well
+  $xml .="</shipping>";
+  return $xml;
+}
+
+/**
+ * Construct XML credit card entity
+ **/
+function _uc_linkpt_xml_credit_card($order) {
+  $xml = '';
+  $xml .="<creditcard>";
+    $xml .="<cardnumber>" . $order->payment_details['cc_number'] . "</cardnumber>";
+    $xml .="<cardexpmonth>" . $order->payment_details['cc_exp_month'] . "</cardexpmonth>";
+    $xml .="<cardexpyear>" . substr($order->payment_details['cc_exp_year'], 2, 2) . "</cardexpyear> ";
+    if (!empty($order->payment_details['cc_cvv'])) {
+      $xml .="<cvmvalue>" . $order->payment_details['cc_cvv'] . "</cvmvalue>";
+      $xml .="<cvmindicator>provided</cvmindicator>";
+    }
+  $xml .="</creditcard>";
+  return $xml;
+}
+
+/**
+ * Construct XML payment entity
+ **/
+function _uc_linkpt_xml_payment($order, $amount) {
+  // Calculate shipping, tax, and subtotal
+  $shipping = 0;
+  if (is_array($order->line_items)) {
+    foreach ($order->line_items as $item) {
+      if ($item['type'] == 'shipping') {
+        $shipping += $item['amount'];
+      }
+    }
+  }
+  $tax = 0;
+  if (module_exists('uc_taxes')) {
+    foreach (uc_taxes_calculate($order) as $tax_item) {
+      $tax += $tax_item->amount;
+    }
+  }
+  $subtotal = $order->order_total - $tax - $shipping;
+  
+  $xml = '';
+  $xml .="<payment>";
+    // Only break down the payment information if it totals up to the chargetotal.
+    if ($subtotal + $tax + $shipping == $amount) {
+      if (!empty($subtotal)) $xml .="<subtotal>" . $subtotal . "</subtotal>";
+      if (!empty($tax)) $xml .="<tax>" . $tax . "</tax>";
+      if (!empty($shipping)) $xml .="<shipping>" . $shipping . "</shipping>";
+    }
+    $xml .="<chargetotal>".$amount."</chargetotal>";
+  $xml .="</payment>";
+  return $xml;
+}
+
+/**
+ * Construct XML transaction details entity
+ **/
+function _uc_linkpt_xml_transaction_details($ordernum, $unique_oid) {
+  $xml = '';
+  $xml .="<transactiondetails>";
+    $xml .="<transactionorigin>ECI</transactionorigin>";
+    $xml .="<oid>" . $unique_oid . "</oid>";
+    $xml .="<invoice_number>" . $ordernum . "</invoice_number>";
+    $xml .="<ip>" . ip_address() . "</ip>";
+  $xml .="</transactiondetails>";
+  return $xml;
+}
+
+/**
+ * Construct XML order options entity
+ **/
+function _uc_linkpt_xml_order_options($order, $ordertype) {
+  $xml = '';
+  $xml .="<orderoptions>";
+    $xml .="<result>" . variable_get('linkpoint_api_transaction_mode', 'LIVE') . "</result>";
+    $xml .="<ordertype>" . $ordertype . "</ordertype>";
+  $xml .="</orderoptions>";
+  return $xml;
+}
+
+/**
+ * Construct XML merchant information entity
+ **/
+function _uc_linkpt_xml_merchant_info($order) {
+  if (variable_get('linkpoint_api_transaction_server', 'live') == "test") {
+    $host = "staging.linkpt.net";
+  } else {
+    $host = "secure.linkpt.net";
+  }
+  
+  $xml = '';
+  $xml .="<merchantinfo>";
+    $xml .="<configfile>" . variable_get('linkpoint_api_login_id', '') . "</configfile>";
+    $xml .="<keyfile>" . variable_get('linkpoint_api_transaction_key', '') . "</keyfile>";
+    $xml .="<host>". $host ."</host>";
+    $xml .="<port>1129</port>";
+  $xml .="</merchantinfo>";
+  return $xml;
+}
+
+/**
+ * Construct XML notes entity
+ **/
+function _uc_linkpt_xml_notes($order) {
+  $xml = '';
+  // Build description
+  $description = '';
+  if (is_array($order->products)) {
+    foreach ($order->products as $product) {
+      if (!empty($description)) {
+        $description .= ' // ';
+      }
+      $description .= $product->title .' x'. $product->qty;
+      if ($product->data['attributes']) {
+        foreach ($product->data['attributes'] as $attrname => $options) {
+          $description .= ', '. $attrname .': ';
+          foreach ($options as $option) {
+            $description .= $option.', ';
+          }
+        }
+      }
+    }
+  }
+  $description = substr($description, 0, 1023);
+  
+  $xml .="<notes>";
+    $xml .="<comments>" . $description . "</comments>";
+  $xml .="</notes>";
+  return $xml;
+}
+
+/**
+ * Associate Ubercarts transaction types with First Data's
+ **/
+function _uc_linkpt_txn_map($type) {
+  switch ($type) {
+    case UC_CREDIT_AUTH_ONLY:
+      $ordertype = 'PREAUTH';
+      break;
+    case UC_CREDIT_AUTH_CAPTURE:
+      $ordertype = 'SALE';
+      break;
+    case UC_CREDIT_PRIOR_AUTH_CAPTURE:
+      $ordertype = 'POSTAUTH';
+      break;
+    case UC_CREDIT_VOID:
+      $ordertype = 'VOID';
+      break;
+  }
+  return $ordertype;
+}
+
+/**
+ * Get the two character country code from the database
+ **/
+function _uc_linkpt_get_country_iso($country) {
+  $countries = uc_get_country_data(
+    array(
+      'country_id' => $country,
+    )
+  );
+  return $countries[0]['country_iso_code_2'];
+}
+
+/**
+ * Clean up the XML
+ **/
+function _uc_linkpt_xml_clean($xml) {
+  /* remove ampersand and appostrophes from text */
+  $xml = str_replace("&", "and", $xml);
+  $xml = str_replace("'", " ", $xml);
+  return $xml;
+}
+
+function _uc_linkpt_avs_to_text($code) {
+  $text = '';
+  switch($code) {
+    case 'YY':
+      $text = t('Address and ZIP Match (YY)');
+      break;
+    case 'NY':
+      $text = t('Only zip matches (NY)');
+      break;
+    case 'YN':
+      $text = t('Only address matches (YN)');
+      break;
+    case 'XX':
+      $text = t('AVS requested, but not received (XX)');
+      break;
+    default:
+      $text = t('Unknown');
+  }
+  return $text;
+}
+
+function _uc_linkpt_cvv_to_text($code) {
+  $text = '';
+  switch ($code) {
+    case 'M':
+      $text = t('Card code matches (M)');
+      break;
+    case 'N':
+      $text = t('Card code does not match (N)');
+      break;
+    case 'P':
+      $text = t('Not Processed (P)');
+      break;
+    case 'S':
+      $text = t('Not present on card (S)');
+      break;
+    case 'U':
+      $text = t('Issuer unable to process request (U)');
+      break;
+    case 'X':
+      $text = t('No response received from CC association (X)');
+      break;
+    default:
+      $text = t('Unknown');
+  }
+  return $text;
+}
+
