? 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 01:22:03 -0000
@@ -1,5 +1,5 @@
 <?php
-// $Id: uc_linkpoint_api.module,v 1.10 2010/11/30 05:39:13 jrust Exp $
+// $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
@@ -24,6 +24,7 @@ function uc_linkpoint_api_payment_gatewa
     '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;
@@ -56,7 +57,7 @@ function uc_linkpoint_api_settings_form(
   $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.'),
+    '#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'),
@@ -65,6 +66,12 @@ function uc_linkpoint_api_settings_form(
     ),
     '#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'),
@@ -76,17 +83,12 @@ function uc_linkpoint_api_settings_form(
     ),
     '#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['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['linkpoint_api_settings']['linkpoint_api_avs'] = array(
+  $form['fraud']['linkpoint_api_avs'] = array(
     '#type' => 'select',
     '#title' => t('Address Verification Service (AVS)'),
     '#default_value' => variable_get('linkpoint_api_avs', array()),
@@ -98,9 +100,9 @@ function uc_linkpoint_api_settings_form(
       '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.'),
+    '#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(
+  $form['fraud']['linkpoint_api_cvv'] = array(
     '#type' => 'select',
     '#title' => t('Card Code Verification Service (CVV)'),
     '#default_value' => variable_get('linkpoint_api_cvv', array()),
@@ -119,6 +121,30 @@ function uc_linkpoint_api_settings_form(
   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')) {
@@ -131,274 +157,506 @@ function uc_linkpoint_api_charge($order_
     $_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;
-    }
+  $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;
   }
 
-  $subtotal = $order->order_total - $tax - $shipping;
+  // 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,
+    );
 
-  // Prepare a description of the order.
-  $description = '';
+  // 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));
+      }
 
-  if (is_array($order->products)) {
-    foreach ($order->products as $product) {
-      if (!empty($description)) {
-        $description .= ' // ';
+      $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);
       }
-      $description .= $product->title .' x'. $product->qty;
-      if ($product->data['attributes']) {
-        foreach ($product->data['attributes'] as $key => $value) {
-          $description .= ', '. $key .': '. $value;
-        }
+      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;
       }
+    
     }
   }
-
-  $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)),
-  );
+    
+  return $result;
+}
 
 /*******************************************************************************
- * Build XML string
+ * Internal helper functions
  ******************************************************************************/
 
-  $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'];
-
+/**
+ * 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()));
-  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,
-    );
+  if (empty($allowed_cvv) && empty($allowed_avs)) {
+    return null;
   }
-  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']);
+  
+  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.';  
   }
-  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) {
+ * 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;
-  // 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 .= _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);
 
-  $retarr = _uc_linkpoint_api_execute_txn($xml);
-  if ($retarr['r_approved'] == 'APPROVED') {
-    return TRUE;
+  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;
   }
-  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;
+  
+  // 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;
 }
 
 /**
- * Returns the orderoptions, merchantinfo, and transactiondetails entities
- * which are required for all transactions
+ * Construct and post a detailed admin order log that prints all relevant information
+ * returned from Linkpoint regarding the transaction
  */
-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>";
+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;
   }
-  $xml .= "<ip>" . ip_address() . "</ip>";
-  $xml .= "</transactiondetails>";
+  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;
 }
 
 /**
- * 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).';
+ * 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>";
     }
-    else {
-      $error .= 'does not exist: check your path.';
+  $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'];
+      }
     }
-    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>";
+  $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;
+}
 
-  preg_match_all ("/<(.*?)>(.*?)\</", $buffer, $outarr, PREG_SET_ORDER);
+/**
+ * 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;
+}
 
-  $n = 0;
-  while (isset($outarr[$n])) {
-    $retarr[$outarr[$n][1]] = strip_tags($outarr[$n][0]);
-    $n++;
+/**
+ * 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;
+}
 
-  return $retarr;
+/**
+ * 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'];
 }
 
-function _uc_linkpoint_api_scrub_xml($xml) {
+/**
+ * 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;
+}
+
