diff --git a/commerce_sagepay_server.module b/commerce_sagepay_server.module
index fae1fa5..5038514 100644
--- a/commerce_sagepay_server.module
+++ b/commerce_sagepay_server.module
@@ -30,6 +30,7 @@ function commerce_sagepay_server_commerce_payment_method_info() {
     'description' => t('Integration with SagePay using Server method.'),
     'active' => TRUE,
     'offsite' => TRUE,
+    //'offsite_autoredirect' => TRUE,
     'callbacks' => array(),
   );
 
@@ -37,6 +38,23 @@ function commerce_sagepay_server_commerce_payment_method_info() {
 }
 
 /**
+ * Implements hook_menu
+ */
+function commerce_sagepay_server_menu() {
+  $items = array();
+
+  // Define a path to receive VPS callback.
+  $items['commerce-sagepay-server/vps-callback/%commerce_order'] = array(
+    'page callback' => 'commerce_sagepay_server_handle_callback',
+    'page arguments' => array(2),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  
+  return $items;
+}
+
+/**
  * Settings form for SagePay Server payment method. Used to set vendor name
  * within Rules settings.
  */
@@ -49,7 +67,7 @@ function commerce_sagepay_server_settings_form($settings = NULL) {
     'enc_key' => '',
     'order_description' => 'Your order from sitename.com',
     'profile' => '',
-    'txn_mode' => SAGEPAY_TXN_MODE_TEST,
+    'txn_mode' => SAGEPAY_SERVER_TXN_MODE_TEST,
     'txn_type' => COMMERCE_CREDIT_AUTH_CAPTURE,
     'apply_avs_cv2' => '0',
     'apply_3d_secure' => '0',
@@ -65,21 +83,6 @@ function commerce_sagepay_server_settings_form($settings = NULL) {
     '#required' => TRUE,
   );
 
-  $form['enc_key'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Encryption Key'),
-    '#description' => t('If you have requested server based integration, you will have received an encryption key from SagePay in a separate email.'),
-    '#default_value' => $settings['enc_key'],
-    '#required' => TRUE,
-  );
-
-  $form['vendor_email'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Vendor Email',
-    '#description' => 'An e-mail address on which you can be contacted when a transaction completes.',
-    '#default_value' => $settings['vendor_email'],
-  );
-
   $form['order_description'] = array(
     '#type' => 'textfield',
     '#title' => t('Order Description'),
@@ -93,9 +96,9 @@ function commerce_sagepay_server_settings_form($settings = NULL) {
     '#title' => t('Transaction mode'),
     '#description' => t('Adjust to live transactions when you are ready to start processing actual payments.'),
     '#options' => array(
-      SAGEPAY_TXN_MODE_LIVE => t('Live transactions in a live account'),
-      SAGEPAY_TXN_MODE_TEST => t('Test transactions in a test account'),
-      SAGEPAY_TXN_MODE_SIMULATION => t('Simulation Account'),
+      SAGEPAY_SERVER_TXN_MODE_LIVE => t('Live transactions in a live account'),
+      SAGEPAY_SERVER_TXN_MODE_TEST => t('Test transactions in a test account'),
+      SAGEPAY_SERVER_TXN_MODE_SIMULATION => t('Simulation Account'),
     ),
     '#default_value' => $settings['txn_mode'],
   );
@@ -158,268 +161,29 @@ function commerce_sagepay_server_redirect_form($form, &$form_state, $order, $pay
     return array();
   }
 
-  if (empty($payment_method['settings']['enc_key'])) {
-    drupal_set_message(t('SagePay Server Integration is not configured for use. Encryption key has not been specified.'), 'error');
-    return array();
-  }
-
-  $settings = array(
-    // Return to the previous page when payment is canceled
-    'cancel_return' => url('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
-
-    // Return to the payment redirect page for processing successful payments
-    'return' => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
-
-    // Specify the current payment method instance ID in the notify_url
-    'payment_method' => $payment_method['instance_id'],
-  );
-
-  return commerce_sagepay_server_order_form($form, $form_state, $order, $payment_method['settings'] + $settings);
-
-}
-
-function commerce_sagepay_server_order_form($form, &$form_state, $order, $settings) {
   $wrapper = entity_metadata_wrapper('commerce_order', $order);
   $total = commerce_line_items_total($wrapper->commerce_line_items);
 
-  // Ensure a default value for the payment_method setting.
-  $settings += array('payment_method' => '');
-
   // Load customer profile.
   $profile = commerce_customer_profile_load($order->commerce_customer_billing[LANGUAGE_NONE][0]['profile_id']);
-
   // Get user billing address.
   $address = $profile->commerce_customer_address[LANGUAGE_NONE][0];
 
-  // Encrypt the order details (address and amount) ready to send to SagePay.
-  $encrypted_order = _commerce_sagepay_server_encrypted_order($settings, $order, $total, $address);
-
-  // Determine the correct transaction type based on the payment gateway settings.
-  switch($settings['txn_type']){
-    case COMMERCE_CREDIT_AUTH_CAPTURE:
-      $txType = 'PAYMENT';
-      break;
-    case COMMERCE_CREDIT_AUTH_ONLY:
-      $txType = 'DEFERRED';
-      break;
-    default:
-      // Set to deferred by default if there is no setting for the payment gateway.
-      $txType = 'DEFERRED';
+  if (isset($order->data['profiles']['customer_profile_shipping'])) {
+    // Load customer delivery profile.
+    $profile = commerce_customer_profile_load($order->data['profiles']['customer_profile_shipping']);
+    // Get user delivery address.
+    $delivery_address = $profile->commerce_customer_address[LANGUAGE_NONE][0];
   }
-
-  // Build the data array that will be translated into hidden form values.
-  $data = array(
-    'VPSProtocol' => '2.23',
-    'TxType' => $txType,
-    'Vendor' => $settings['vendor_name'],
-    'Crypt' => $encrypted_order,
-  );
-
-  // determine the correct url based on the transaction mode.
-  switch ($settings['txn_mode']) {
-    case SAGEPAY_TXN_MODE_LIVE:
-      $server_url = SAGEPAY_SERVER_LIVE;
-   break;
-   case SAGEPAY_TXN_MODE_TEST:
-      $server_url = SAGEPAY_SERVER_TEST;
-   break;
-
-   case SAGEPAY_TXN_MODE_SIMULATION:
-    $server_url = SAGEPAY_SERVER_SIMULATION;
-   break;
-
+  elseif (isset($order->field_customer_shipping[LANGUAGE_NONE][0]['profile_id'])) {
+    $profile = commerce_customer_profile_load($order->field_customer_shipping[LANGUAGE_NONE][0]['profile_id']);
+    // Get user delivery address.
+    $delivery_address = $profile->commerce_customer_address[LANGUAGE_NONE][0];
   }
-
-  $form['#action'] = $server_url;
-
-  foreach ($data as $name => $value) {
-    if (!empty($value)) {
-      $form[$name] = array('#type' => 'hidden', '#value' => $value);
-    }
-  }
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Proceed to SagePay'),
-  );
-
-  return $form;
-}
-
-/**
- * Implements hook_redirect_form_validate
- */
-function commerce_sagepay_server_redirect_form_validate($order, $payment_method){
-  // SagePay sends a callback to the site as a single encrypted string called Crypt
-  // this is appened to the success or failure URL.
-  $encrypted_response = $_GET['crypt'];
-
-  // Now we have the encrypted response, we need to decrypt this using the same
-  // secret key that we used to send the request in the first place.
-  // The secret key is stored in the payment method.
-  $enc_key = $payment_method['settings']['enc_key'];
-  if (!isset($enc_key)) {
-  	watchdog('commerce_sagepay_server', t('Cannot load SagePay key from payment method in order to decrypt response'), array(), WATCHDOG_ERROR);
-  	return FALSE;
+  else{
+    $delivery_address = $address;
   }
-
-  // Decrypt the response received from SagePay.
-  $unencrypted_response = _commerce_sagepay_server_simple_xor(_commerce_sagepay_form_base64Decode($encrypted_response), $enc_key);
-
-  // The response we get back will have the following tokens available:
-  // Status = a String containing the status of the transaction from the possible values:
-    // ABORT, NOTAUTHED, REJECTED, MALFORMED, INVALID, ERROR, OK, AUTHENTICATED, REGISTERED
-
-  // StatusDetail = Human-readable text providing extra detail for the Status message
-
-  // VendorTxCode = The order ID we send with the original request. This can be used to apply this transaction back to the order.
-
-  // VPSTxId = The unique transaction ID created by SagePay
-
-  // TxAuthNo = The transaction authorisation number
-
-  // Amount = The amount that was taken in the payment (this should match the amount requested and can be used to verify the call back)
-
-  // AVSCV2 = The result of the card AVSCV2 security check
-  // This will be one of ALL MATCH, SECURITY CODE MATCH ONLY, ADDRESS MATCH ONLY, NO DATA MATCHES or DATA NOT CHECKED
-
-  // AddressResult = The result of the address check (comparing the card address entered by the user to the one on the credit card record at the provider)
-  // This will be one of: NOTPROVIDED, NOTCHECKED, MATCHED, NOTMATCHED
-
-  // PostCodeResult = The result of the postcode check (comparing the card address postcode entered by the user to the one on the credit card record at the provider)
-  // This will be one of NOTPROVIDED, NOTCHECKED, MATCHED, NOTMATCHED
-
-  // CV2Result = The results of the CV2 security check
-  // This will be one off NOTPROVIDED, NOTCHECKED, MATCHED, NOTMATCHED
-
-  // GiftAid = Whether or not the user specified Gift Aid during the transaction (charity payments only)
-  // This will be either: 0 = The Gift Aid box was not checked this transaction. 1 = The user checked the Gift Aid box on the payment page
-
-  // 3DSecureStatus: This will be one of:
-  // OK - 3D Secure checks carried out and user authenticated correctly.
-  // NOTCHECKED ‚ 3D-Secure checks were not performed. NOTAVAILABLE ‚Äì The card used was either not part of the
-  // 3D Secure Scheme, or the authorisation was not possible.
-  // NOTAUTHED ‚3D-Secure authentication checked, but the user failed the authentication.
-  // INCOMPLETE ‚3D-Secure authentication was unable to complete. No authentication occurred.
-  // ERROR - Authentication could not be attempted due to data errors or service unavailability in one of the parties involved in the check.
-
-  // CAVV = The encoded result code from the 3D-Secure checks (CAVV or UCAF)."CAVV" - Only present if the 3DSecureStatus is OK
-
-  // CardType =  The card type used to complete the transaction. This will be one of:
-  // VISA, MC, DELTA, SOLO, MAESTRO, UKE, AMEX, DC, JCB, LASER, PAYPAL
-
-  // Last4Digits = The last 4 digits of the card number used in this transaction. PayPal transactions have 0000
-
-  // Split the decrypted string into an array of tokens.
-  $tokens = _commerce_sagepay_server_get_tokens($unencrypted_response);
-
-  // Split the vendor code to get rid of the random number that was added to the end before sending.
-  $arrtmp = explode('_', $tokens['VendorTxCode']);
-  $order_id = $arrtmp[0];
-
-
-  // Validate the returned decrypted data
-  if ($order_id != $order->order_id) {
-  	watchdog('commerce_sagepay_server', t('Returned order id does not match order for this session'), array(), WATCHDOG_ERROR);
-  	return FALSE;
-  }
-
-  // Check for a valid status callback.
-  switch ($tokens['Status']) {
-      case 'ABORT' :
-        watchdog('commerce_sagepay', 'ABORT error from SagePay for order %order_id', array('%order_id'=> $order_id), WATCHDOG_ALERT);
-        commerce_sagepay_server_transaction($payment_method, $order, array(), $tokens, COMMERCE_PAYMENT_STATUS_FAILURE);
-        return FALSE;
-      case 'NOTAUTHED' :
-        watchdog('commerce_sagepay', 'NOTAUTHED error from SagePay for order %order_id', array('%order_id'=> $order_id), WATCHDOG_ALERT);
-        commerce_sagepay_server_transaction($payment_method, $order, array(), $tokens, COMMERCE_PAYMENT_STATUS_FAILURE);
-        return FALSE;
-      case 'REJECTED' :
-        watchdog('commerce_sagepay', 'REJECTED error from SagePay for order %order_id', array('%order_id'=> $order_id), WATCHDOG_ALERT);
-        commerce_sagepay_server_transaction($payment_method, $order, array(), $tokens, COMMERCE_PAYMENT_STATUS_FAILURE);
-        return FALSE;
-      case 'MALFORMED' :
-        watchdog('commerce_sagepay', 'MALFORMED error from SagePay for order %order_id', array('%order_id'=> $order_id), WATCHDOG_ALERT);
-        commerce_sagepay_server_transaction($payment_method, $order, array(), $tokens, COMMERCE_PAYMENT_STATUS_FAILURE);
-        return FALSE;
-      case 'INVALID' :
-        watchdog('commerce_sagepay', 'INVALID error from SagePay for order %order_id', array('%order_id'=> $order_id), WATCHDOG_ERROR);
-        commerce_sagepay_server_transaction($payment_method, $order, array(), $tokens, COMMERCE_PAYMENT_STATUS_FAILURE);
-        return FALSE;
-      case 'ERROR' :
-        watchdog('commerce_sagepay', 'System ERROR from SagePay for order %order_id', array('%order_id'=> $order_id), WATCHDOG_ERROR);
-        commerce_sagepay_server_transaction($payment_method, $order, array(), $tokens, COMMERCE_PAYMENT_STATUS_FAILURE);
-        return FALSE;
-      case 'OK' :
-        watchdog('commerce_sagepay', 'OK Payment callback received from SagePay for order %order_id with status code %status', array('%order_id'=> $order_id, '%status'=>$tokens['Status']));
-        break;
-      case 'AUTHENTICATED' :
-        watchdog('commerce_sagepay', 'AUTHENTICATED Payment callback received from SagePay for order %order_id with status code %status', array('%order_id'=> $order_id, '%status'=>$tokens['Status']));
-        break;
-      case 'REGISTERED' :
-        watchdog('commerce_sagepay', 'REGISTERED Payment callback received from SagePay for order %order_id with status code %status', array('%order_id'=> $order_id, '%status'=>$tokens['Status']));
-        break;
-
-      default:
-        // If the status code is anything other than those above, something has gone wrong so log an error and fail.
-        watchdog('commerce_sagepay', 'Unrecognised Status response from SagePay for order %order_id (%response_code)', array('%order_id' => $order_id, '%response_code' => $tokens['Status']), WATCHDOG_ERROR);
-        return FALSE;
-
-    }
-
-  // Validation successful.
-  // Create a transaction and associate it with the order.
-   $arr_charge = array();
-   $arr_charge['amount'] = $tokens['Amount'] * 100; // convert back to commerce int
-   $arr_charge['currency_code'] = $order->commerce_order_total['und'][0]['currency_code'];
-
-   switch ($tokens['Status']) {
-     case 'OK':
-       commerce_sagepay_server_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_SUCCESS);
-       break;
-     default:
-       commerce_sagepay_server_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_PENDING);
-   }
-
-  return TRUE;
-}
-
-
-
-/**
- * Create a transaction and associate it with an order
- */
-function commerce_sagepay_server_transaction($payment_method, $order, $charge = array('amount' => 0, 'currency_code' => ''), $tokens, $transaction_status) {
-
-  $transaction = commerce_payment_transaction_new('commerce_sagepay', $order->order_id);
-  $transaction->instance_id = $payment_method['instance_id'];
-  $transaction->amount = $charge['amount'];
-  $transaction->currency_code = $charge['currency_code'];
-
-  // set a status for the payment - one of COMMERCE_PAYMENT_STATUS_SUCCESS, COMMERCE_PAYMENT_STATUS_PENDING or COMMERCE_PAYMENT_STATUS_FAILURE
-  $transaction->status = $transaction_status;
-  $transaction->message = 'Status @status, @statusdetail. VPSTxId=@vpstxid. Auth Code=@authcode. Address Check: @address. Postcode Check: @postcode. AVSCV2 Result: @avs. 3D Secure: @tds';
-  $transaction->message_variables =
-    array(
-      '@status' => $tokens['Status'],
-      '@statusdetail' => $tokens['StatusDetail'],
-      '@vpstxid' => $tokens['VPSTxId'],
-      '@authcode' => $tokens['TxAuthNo'],
-      '@address' => $tokens['AddressResult'],
-      '@postcode' => $tokens['PostCodeResult'],
-      '@avs' => $tokens['AVSCV2'],
-      '@tds' => $tokens['3DSecureStatus'],
-    );
-  commerce_payment_transaction_save($transaction);
-
-}
-
-
-/**
- * Encrypt the order details ready to send to SagePay Server
- **/
-function _commerce_sagepay_server_encrypted_order($settings, $order, $total, $address) {
-
+  
   // add a random number to the transaction ID so that the order can be resubmitted to SagePage in the
   // event the user clicks back to modify the order before completing. (otherwise we SagePay rejects this
   // as a duplicate)
@@ -432,159 +196,164 @@ function _commerce_sagepay_server_encrypted_order($settings, $order, $total, $ad
   $redirect_key = $order->data['payment_redirect_key'];
 
   // convert commerce int to decimal
-  $order_amount = $total['amount'] / 100;
-
+  $amount = commerce_currency_amount_to_decimal($total['amount'], $total['currency_code']);
+  $order_amount = number_format($amount, 2);
+  
   // Check if we need to encoded cart.
   $encoded_cart = '';
-  if ($settings['send_basket_contents'] == '1'){
+  if ($payment_method['settings']['send_basket_contents'] == '1') {
     $encoded_cart = _commerce_sagepay_server_cart_to_string($order);
   }
+  
+  if ((empty($address['first_name']) || empty($address['last_name'])) && !empty($address['name_line'])) {
+    $names = explode(' ', $address['name_line']);
+    $address['first_name'] = implode(' ', array_slice($names, 0, count($names)-1));
+    $address['last_name'] = $names[count($names)-1];
+  }
+  if ((empty($delivery_address['first_name']) || empty($delivery_address['last_name'])) && !empty($delivery_address['name_line'])) {
+    $names = explode(' ', $delivery_address['name_line']);
+    $delivery_address['first_name'] = implode(' ', array_slice($names, 0, count($names)-1));
+    $delivery_address['last_name'] = $names[count($names)-1];
+  }
 
-
-  $query = array(
+    // Determine the correct transaction type based on the payment gateway settings.
+  switch ($payment_method['settings']['txn_type']) {
+    case COMMERCE_CREDIT_AUTH_CAPTURE:
+      $txType = 'PAYMENT';
+      break;
+    case COMMERCE_CREDIT_AUTH_ONLY:
+      $txType = 'DEFERRED';
+      break;
+    default:
+      // Set to deferred by default if there is no setting for the payment gateway.
+      $txType = 'DEFERRED';
+  }
+  
+  // Build the data array that will be translated into hidden form values.
+  $data = array(
+    'VPSProtocol' => '2.23',
+    'TxType' => $txType,
+    'Vendor' => $payment_method['settings']['vendor_name'],
     'VendorTxCode' => $strVendorTxCode,
     'Amount' => $order_amount,
     'Currency' => $total['currency_code'],
-    'Description' => $settings['order_description'],
-    'SuccessURL' => $settings['return'],
-    'FailureURL' => $settings['cancel_return'],
-    'CustomerName' => $address['first_name'] . " " . $address['last_name'],
-    'CustomerEmail' => $order->mail,
-    'VendorEmail' => $settings['vendor_email'],
-    // 'SendEmail' => '',
+    'Description' => $payment_method['settings']['order_description'],
+    'NotificationURL' => url('commerce-sagepay-server/vps-callback/' . $order->order_id, array('absolute' => TRUE)),  
     'eMailMessage' => '',
-    'BillingSurname' => $address['last_name'],
-    'BillingFirstnames' => $address['first_name'],
-    'BillingAddress1' => $address['thoroughfare'],
-    'BillingAddress2' => $address['premise'],
-    'BillingCity' => $address['locality'],
-    'BillingPostcode' => $address['postal_code'],
+    'BillingSurname' => substr(commerce_sagepay_valid_chars($address['last_name'], 'name'), 0, 20),
+    'BillingFirstnames' => substr(commerce_sagepay_valid_chars($address['first_name'], 'name'), 0, 20),
+    'BillingAddress1' => substr(commerce_sagepay_valid_chars($address['thoroughfare'], 'add'), 0, 100),
+    'BillingAddress2' => substr(commerce_sagepay_valid_chars($address['premise'], 'add'), 0, 100),
+    'BillingCity' => substr(commerce_sagepay_valid_chars($address['locality'], 'add'), 0, 40),
+    'BillingPostcode' => substr(commerce_sagepay_valid_chars($address['postal_code'], 'pc'), 0, 10),
     'BillingCountry' => $address['country'],
     // 'BillingState' => $address[''],
     // 'BillingPhone' => $address['postal_code'],
-    'DeliverySurname' => $address['last_name'],
-    'DeliveryFirstnames' => $address['first_name'],
-    'DeliveryAddress1' => $address['thoroughfare'],
-    'DeliveryAddress2' => $address['premise'],
-    'DeliveryCity' => $address['locality'],
-    'DeliveryPostcode' => $address['postal_code'],
-    'DeliveryCountry' => $address['country'],
+    'DeliverySurname' => substr(commerce_sagepay_valid_chars($delivery_address['last_name'], 'name'), 0, 20),
+    'DeliveryFirstnames' => substr(commerce_sagepay_valid_chars($delivery_address['first_name'], 'name'), 0, 20),
+    'DeliveryAddress1' => substr(commerce_sagepay_valid_chars($delivery_address['thoroughfare'], 'add'), 0, 100),
+    'DeliveryAddress2' => substr(commerce_sagepay_valid_chars($delivery_address['premise'], 'add'), 0, 100),
+    'DeliveryCity' => substr(commerce_sagepay_valid_chars($delivery_address['locality'], 'add'), 0, 40),
+    'DeliveryPostcode' => substr(commerce_sagepay_valid_chars($delivery_address['postal_code'], 'pc'), 0, 10),
+    'DeliveryCountry' => $delivery_address['country'],
     // 'DeliveryState' => $address[''],
     // 'DeliveryPhone' => $address[''],
     'Basket' => $encoded_cart,
     // 'AllowGiftAid' => '',
-    'ApplyAVSCV2' => $settings['apply_avs_cv2'],
-    'Apply3DSecure' => $settings['apply_3d_secure'],
+    'ApplyAVSCV2' => $payment_method['settings']['apply_avs_cv2'],
+    'Apply3DSecure' => $payment_method['settings']['apply_3d_secure'],
     // 'BillingAgreement' => '',
-
   );
 
-
-  $keys = array_keys($query);
-  $query_string = '';
-  foreach($keys as $key){
-    $query_string .= $key . '=' . $query[$key] . '&';
-  }
-  $query_string = substr($query_string, 0, strlen($query_string) -1);
-watchdog('debug', $query_string);
-
-  // Encrypt the order details using base64 and the secret key from the settings.
-  return base64_encode(_commerce_sagepay_server_simple_xor($query_string, $settings['enc_key']));
-}
-
-function _commerce_sagepay_server_base64Decode($scrambled) {
-   $output = "";
-   $corrected = str_replace(" ", "+", $scrambled);
-   $output = base64_decode($corrected);
-   return $output;
- }
-
-function _commerce_sagepay_server_simple_xor($InString, $Key) {
-  // Initialise key array.
-  $KeyList = array();
-  // Initialise out variable.
-  $output = "";
-
-  // Convert $Key into array of ASCII values.
-  for ($i = 0; $i < strlen($Key); $i++) {
-    $KeyList[$i] = ord(substr($Key, $i, 1));
+  // create a POST to send to SagePay
+  $post = '';
+  foreach ($data as $name => $value) {
+    $post .= urlencode($name) . '=' . urlencode($value) . '&';  
   }
-
-  // Step through string a character at a time.
-  for ($i = 0; $i < strlen($InString); $i++) {
-    // Get ASCII code from string, get ASCII code from key (loop through with MOD), XOR the two, get the character from the result
-    // % is MOD (modulus), ^ is XOR.
-    $output .= chr(ord(substr($InString, $i, 1)) ^ ($KeyList[$i % strlen($Key)]));
+  // chop off the last &
+  $post = substr($post, 0, -1);
+  
+  // determine the correct url based on the transaction mode.
+  switch ($payment_method['settings']['txn_mode']) {
+    case SAGEPAY_SERVER_TXN_MODE_LIVE:
+     $server_url = SAGEPAY_SERVER_SERVER_LIVE;
+     break;
+   case SAGEPAY_SERVER_TXN_MODE_TEST:
+     $server_url = SAGEPAY_SERVER_SERVER_TEST;
+     break;
+   case SAGEPAY_SERVER_TXN_MODE_SIMULATION:
+     $server_url = SAGEPAY_SERVER_SERVER_SIMULATION;
+     break;
   }
-  return $output;
-}
-
-function _commerce_sagepay_server_get_tokens($tokenizedstring) {
-
-  // List the possible tokens.
-  $tokens = array(
-    'Status',
-    'StatusDetail',
-    'VendorTxCode',
-    'VPSTxId',
-    'TxAuthNo',
-    'Amount',
-    'AVSCV2',
-    'AddressResult',
-    'PostCodeResult',
-    'CV2Result',
-    'GiftAid',
-    '3DSecureStatus',
-    'CAVV',
-    'AddressStatus',
-    'PayerStatus',
-    'CardType',
-    'Last4Digits',
+  //dpm($post);
+  $response = _commerce_sagepay_server_request_post($server_url, $post);
+  //dpm($response);
+  // Create a new payment transaction and setup the amount
+  $transaction = commerce_payment_transaction_new('commerce_sagepay_server', $order->order_id);
+  $transaction->amount = $total['amount'];
+  $transaction->currency_code = $total['currency_code'];
+  $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
+  $transaction->instance_id = $payment_method['instance_id'];
+  
+  $response_data = array(
+    'response_status' => isset($response['Status']) ? $response['Status'] : 'No Status',
+    'response_detail' => isset($response['StatusDetail']) ? $response['StatusDetail'] : 'No Status',
+    'vendor' => $payment_method['settings']['vendor_name'],
+    'vendor_txcode' => $strVendorTxCode,
+    'payment_mode' => $payment_method['settings']['txn_mode'],
+    'request_data' => $post,   
   );
-
-  // Initialise arrays.
-  $output = array();
-  $result = array();
-
-  // Get the next token in the sequence.
-  for ($i = count($tokens)-1; $i >= 0 ; $i--) {
-    // Find the position in the string.
-    $start = strpos($tokenizedstring, $tokens[$i]);
-  // If it's present
-    if ($start !== FALSE) {
-      // Record position and token name.
-      $result[$i]->start = $start;
-      $result[$i]->token = $tokens[$i];
-    }
+  if (isset($response['VPSTxId'])) {
+    $response_data['response_vpstxid'] = $response['VPSTxId'];
+    $transaction->remote_id = $response['VPSTxId']; 
   }
-
-  // Sort in order of position.
-  sort($result);
-
-  // Go through the result array, getting the token values.
-  for ($i = 0; $i<count($result); $i++) {
-    // Get the start point of the value.
-    $valueStart = $result[$i]->start + strlen($result[$i]->token) + 1;
-  // Get the length of the value.
-    if ($i==(count($result)-1)) {
-      $output[$result[$i]->token] = substr($tokenizedstring, $valueStart);
-    }
-    else {
-      $valueLength = $result[$i+1]->start - $result[$i]->start - strlen($result[$i]->token) - 2;
-      $output[$result[$i]->token] = substr($tokenizedstring, $valueStart, $valueLength);
-    }
-
+  if (isset($response['SecurityKey'])) {
+    $response_data['response_securitykey'] = $response['SecurityKey'];
+  }
+  $transaction->data = $response_data;
+  
+  //Process the response - default status is failure during processing
+  if (!isset($response['Status'])) {
+    $transaction->data['vps_status'] = 'TIMEOUT';
+    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+    $transaction->message = 'No valid response from Sagepay';
+    commerce_payment_transaction_save($transaction);
+    watchdog('custom_sagepay_server', 'No Status code received in SagePay callback.', array(), WATCHDOG_ERROR);
+    drupal_goto('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key']);
+  }
+  $transaction_status = explode(" ", $response["Status"]);  
+  switch ($transaction_status[0]) {
+    case 'OK':
+      $transaction->remote_status = 'Started';
+      commerce_payment_transaction_save($transaction);
+      header("Location: " . $response["NextURL"]);
+      exit;
+    case 'FAIL':
+      $transaction->data['vps_status'] = 'FAIL';
+      $transaction->remote_status = 'Failed';
+      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+      $transaction->message = 'FAIL Response from Sagepay';
+      commerce_payment_transaction_save($transaction);
+      watchdog('commerce_sagepay', 'FAIL Status response from SagePay for order %order_id', array('%order_id' => $order->order_id), WATCHDOG_ERROR);
+      drupal_goto('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key']);
+      break;
+    default :
+      $transaction->data['vps_status'] = 'UNKNOWN';
+      $transaction->remote_status = 'Unknown';
+      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+      $transaction->message = 'Unknown or invalid response from Sagepay';
+      commerce_payment_transaction_save($transaction);
+      watchdog('commerce_sagepay', 'Unrecognised Status response from SagePay for order %order_id (%response_code)', array('%order_id' => $order->order_id, '%response_code' => $transaction_status[0]), WATCHDOG_ERROR);
+      drupal_goto('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key']);
+      break;
   }
-
-  // Return the ouput array.
-  return $output;
 }
 
 /**
  * Convert a commerce order to a string in a format suitable to
  * send to SagePay.
  */
-function _commerce_sagepay_server_cart_to_string($order){
+function _commerce_sagepay_server_cart_to_string($order) {
   $order_string = '';
 
   // Load an array of line items from the order.
@@ -595,7 +364,7 @@ function _commerce_sagepay_server_cart_to_string($order){
   $order_string = $total_lines . ':';
 
   // Encode each order line and add to the string.
-  for ($line = 0; $line < $total_lines; $line++){
+  for ($line = 0; $line < $total_lines; $line++) {
     $order_string .= _commerce_sagepay_server_line_item_to_string($order_lines[$line]['line_item_id']);
   }
 
@@ -609,13 +378,19 @@ function _commerce_sagepay_server_cart_to_string($order){
  * Convert a commerce line item to a string in a format suitable to
  * send to SagePay.
  */
-function _commerce_sagepay_server_line_item_to_string($line_item_id){
+function _commerce_sagepay_server_line_item_to_string($line_item_id) {
 
   // Load the line item ID
   $line_item = commerce_line_item_load($line_item_id);
 
   $description = $line_item->line_item_label;
-
+  
+  if (!empty($line_item->commerce_product[LANGUAGE_NONE][0]['product_id'])) {
+    $product_id = $line_item->commerce_product[LANGUAGE_NONE][0]['product_id'];
+    $product = commerce_product_load($product_id);
+    $description = $product->title . ' (' . $description . ')';
+  }
+  
   // remove any colons from the line description as we need this as a delimiter.
   $description = str_ireplace(':', ' ', $description);
 
@@ -625,11 +400,322 @@ function _commerce_sagepay_server_line_item_to_string($line_item_id){
   $item_value = $line_item->commerce_unit_price['und'][0]['data']['components'][0]['price']['amount'] / 100;
   if (isset($line_item->commerce_unit_price['und'][0]['data']['components'][1])) {
     $item_tax = $line_item->commerce_unit_price['und'][0]['data']['components'][1]['price']['amount'] / 100;
-  } else {
+  } 
+  else {
     $item_tax = '---';
   }
   $item_total = $line_item->commerce_unit_price['und'][0]['amount'] / 100;
   $line_total = $line_item->commerce_total['und'][0]['amount'] / 100;
 
   return $description . ':' . $quantity . ':' . $item_value . ':' . $item_tax . ':' . $item_total . ':' . $line_total . ':';
+}
+
+function _commerce_sagepay_server_request_post($url, $data) {
+  // Set a one-minute timeout for this script
+  set_time_limit(60);
+
+  // Initialise output variable
+  $output = array();
+
+  // Open the cURL session 
+  $curlSession = curl_init();
+
+  // Set the URL
+  curl_setopt($curlSession, CURLOPT_URL, $url);
+  // No headers, please
+  curl_setopt($curlSession, CURLOPT_HEADER, 0);
+  // It's a POST request
+  curl_setopt($curlSession, CURLOPT_POST, 1);
+  // Set the fields for the POST
+  curl_setopt($curlSession, CURLOPT_POSTFIELDS, $data);
+  // Return it direct, don't print it out
+  curl_setopt($curlSession, CURLOPT_RETURNTRANSFER, 1); 
+  // This connection will timeout in 30 seconds
+  curl_setopt($curlSession, CURLOPT_TIMEOUT, 30); 
+  //The next two lines must be present for the kit to work with newer version of cURL
+  //You should remove them if you have any problems in earlier versions of cURL
+  curl_setopt($curlSession, CURLOPT_SSL_VERIFYPEER, FALSE);
+  curl_setopt($curlSession, CURLOPT_SSL_VERIFYHOST, 1);
+
+  //Send the request and store the result in an array
+  
+  $rawresponse = curl_exec($curlSession);
+  //Store the raw response for later as it's useful to see for integration and understanding 
+  //Split response into name=value pairs
+  $response = explode(chr(10), $rawresponse);
+  // Check that a connection was made
+  if (curl_error($curlSession)) {
+    // If it wasn't...
+    $output['Status'] = "FAIL";
+    $output['StatusDetail'] = curl_error($curlSession);
+  }
+
+  // Close the cURL session
+  curl_close($curlSession);
+  
+  // Tokenise the response
+  for ($i=0; $i<count($response); $i++) {
+    // Find position of first "=" character
+    $splitAt = strpos($response[$i], "=");
+    // Create an associative (hash) array with key/value pairs ('trim' strips excess whitespace)
+    $output[trim(substr($response[$i], 0, $splitAt))] = trim(substr($response[$i], ($splitAt+1)));
+  } // END for ($i=0; $i<count($response); $i++)
+
+  // Return the output
+  return $output;
+}
+
+/**
+ * Handle response from VPS server to initial request
+ */
+function commerce_sagepay_server_handle_callback($order) {
+  $transactions = commerce_payment_transaction_load_multiple(array(), array('order_id' => $order->order_id));
+  foreach ($transactions as $transaction) {                                                                   
+    if ($transaction->data['vendor_txcode'] == $_POST["VendorTxCode"]) {     
+      // Create an array of values for checking against security key
+      $data = array();
+      $data['VPSTxId'] = $transaction->data['response_vpstxid'];   // VPS transaction ID (from database)
+      $data['VendorTxCode'] = $transaction->data['vendor_txcode']; // Vendor's transaction code (from database)
+      $data['Status'] = $transaction->data['response_status'];     // Status of order (from database)
+      if (isset($_POST['TxAuthNo'])) $data['TxAuthNo'] = $_POST['TxAuthNo'];             // Transaction authorisation number (POSTed)
+      $data['Vendor'] = $transaction->data['vendor'];   // Vendor name 
+      $data['AVSCV2'] = $_POST['AVSCV2'];                 // Address verficiation response (POSTed)
+      $data['SecurityKey'] = $transaction->data['response_securitykey'];  // Security key (from database)
+      // new for 2.22
+      $data['AddressResult'] = $_POST['AddressResult'];   // AVS result (POSTed)
+      $data['PostCodeResult'] = $_POST['PostCodeResult']; // PostCode check result (POSTed)
+      $data['CV2Result'] = $_POST['CV2Result'];           // CV2 check result (POSTed)
+      $data['GiftAid'] = $_POST['GiftAid'];               // GiftAid flag (POSTed)
+      $data['3DSecureStatus'] = $_POST['3DSecureStatus']; // 3D Secure Status (POSTed)
+      if (isset($_POST['CAVV'])) $data['CAVV'] = $_POST['CAVV']; // CAVV result (POSTed)
+      // new for 2.23
+      if (isset($_POST['AddressStatus'])) $data['AddressStatus'] = $_POST['AddressStatus'];
+      if (isset($_POST['PayerStatus'])) $data['PayerStatus'] = $_POST['PayerStatus'];
+      $data['CardType'] = $_POST['CardType'];
+      $data['Last4Digits'] = $_POST['Last4Digits'];
+      
+      // Get the first word of the status -- in case it has appended values (eg. REPEATED)
+      $sp = explode(" ", $_POST['Status']);
+      $baseStatus = array_shift($sp);
+      $eoln = chr(13) . chr(10);
+      $transaction->remote_status = 'Processed';
+      
+      $status = '';
+      $reason = '';
+      $status_detail = '';
+      $redirectURL = url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array('absolute' => TRUE));
+      
+      // Reply according to the value of $Status
+      switch ($baseStatus) {
+      // If the transaction was authorised ok
+      case 'OK':    
+        // Check the MD5 Hash Value sent back in the signature, to confirm the validity of the post
+        // Compare the incoming signature to the calculated signature sent with the post
+        if (strtolower($_POST['VPSSignature']) == md5(join("", $data))) {
+          /** 
+           * The Hash Value and VPS Signature match, so reply to the VPS with a redirect URL 
+           * for the completion page.  You will need to add code here to store the Auth Code
+           * in your database.
+           * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+           * to reflect that it has been Authorised and store the Auth COde sent back in $_POST['TxAuthNo']
+           * You may also wish to e-mail the customer here to confirm the order 
+           */
+          $transaction->data += array(
+            'TxAuthNo' => $_POST['TxAuthNo'],
+            'AVSCV2' => $_POST["AVSCV2"],
+            'AddressResult' => $_POST["AddressResult"],
+            'PostCodeResult' => $_POST["PostCodeResult"],
+            'CV2Result' => $_POST["CV2Result"],
+            'VBVSecureStatus' => $_POST["3DSecureStatus"],
+            'CAVV' => $_POST["CAVV"], 
+          );
+          $transaction->data['vps_status'] = 'OK';
+          //$transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+          $transaction->message = 'OK';
+          $status = "OK";
+          $reason = '';
+        }
+        else{
+          /**
+           * The Hash Value and VPS Signature DO not match, the order may have been tampered with 
+           * so redirect the user to a page explaining this.  
+           * You may wish to add code here to flag this in your database
+           *
+           * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+           * to reflect that it has been Tampered with.
+           */
+          $transaction->data['vps_status'] = 'TAMPER';
+          //$transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+          $transaction->message = 'Tampered';
+          $status = "INVALID";
+          $reason = "tamper";
+          $status_detail = "MD5 codes did not match " . $transaction->data['vendor_txcode'];
+          watchdog('commerce_sagepay', 'MALFORMED error from SagePay for order %order_id', array('%order_id' => $order->order_id), WATCHDOG_ALERT);
+        }
+        break;
+      case 'NOTAUTHED':
+        /**
+         * The bank has not Authorised this request.  Inform the user of this.
+         * It is a good idea to add code here to update your database to reflect this.
+         * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+         * to reflect that it has not been Authorised
+         */
+        $transaction->data['vps_status'] = 'NOTAUTHED';
+        //$transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        $transaction->message = 'Not Authorised';
+        $status = "OK";
+        $reason = "notauth";
+        watchdog('commerce_sagepay', 'NOTAUTHED error from SagePay for order %order_id', array('%order_id' => $order->order_id), WATCHDOG_ALERT);
+        break;
+      case 'REJECTED':
+        /**
+         * SagePay rejected the transaction based on AVS or 3DSecure rules.  
+        * Inform the user of this.
+        * It is a good idea to add code here to update your database to reflect this.
+        * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+        * to reflect that it has not been Rejected
+        */
+        $transaction->data += array(
+          'TxAuthNo' => $_POST['TxAuthNo'],
+          'AVSCV2' => $_POST["AVSCV2"],
+          'AddressResult' => $_POST["AddressResult"],
+          'PostCodeResult' => $_POST["PostCodeResult"],
+          'CV2Result' => $_POST["CV2Result"],
+          'VBVSecureStatus' => $_POST["3DSecureStatus"],
+          'CAVV' => $_POST["CAVV"], 
+        );
+        $transaction->data['vps_status'] = 'REJECTED';
+        //$transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        $transaction->message = 'Rejected';
+        $status = "OK";
+        $reason = "reject";
+        watchdog('commerce_sagepay', 'REJECTED error from SagePay for order %order_id', array('%order_id' => $order->order_id), WATCHDOG_ALERT);
+        break;
+      case 'ABORT':
+        /**
+         * The process either timed out, or more likely, the user clicked cancel
+         * It is a good idea to add code here to update your database to reflect this.
+         * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+         * to reflect that the user aborted the transaction 
+         */
+        $transaction->data['vps_status'] = 'ABORT';
+        //$transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        $transaction->message = 'Aborted';
+        $status = "OK";
+        $reason = 'abort';
+        watchdog('commerce_sagepay', 'ABORT error from SagePay for order %order_id', array('%order_id' => $order->order_id), WATCHDOG_ALERT);
+        $redirectURL = url('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key'], array('absolute' => TRUE));
+        break;
+      case 'ERROR':
+        /**
+         * Something has gone very wrong at the PROXT VPS.  You should never receive this message
+         * but trap for it anyway and update your database with an error flag on this order.
+         * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+         * to reflect that an error occurred at the SagePay site
+         */
+        $transaction->data['vps_status'] = 'ERROR';
+        //$transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        $transaction->message = 'Error';
+        $status = "ERROR";
+        $reason = "error";
+        watchdog('commerce_sagepay', 'System ERROR from SagePay for order %order_id', array('%order_id' => $order->order_id), WATCHDOG_ERROR);
+        break;
+      case 'FAIL':
+      default:
+        /**
+        * Connection to SagePay could not be made (timed out) or other problem
+        * Update the transaction record referenced by $_POST['VendorTxCode'] in your database
+        * to reflect that an error occurred
+        */
+        //$transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        $transaction->data['vps_status'] = 'FAIL2';
+        $transaction->message = 'Failed';
+        // There's no point in sending error notification since it won't go anywhere
+        watchdog('commerce_sagepay', 'Unknown response from SagePay for order %order_id (%response_code)', array('%order_id' => $order->order_id, '%response_code' => $baseStatus), WATCHDOG_ERROR);
+        break;
+      }
+      if (!empty($status)) {
+        if (!empty($reason)) $reason = '/' . $reason;
+        $response = "Status=" . $status . $eoln . "RedirectURL=" . $redirectURL . "?VPSTxId=" . $transaction->data['response_vpstxid'] . "&VendorTxCode=" . $transaction->data['vendor_txcode'] . $eoln . "StatusDetail=" . $status_detail;
+        // Send the response back to the VPS, which will then redirect to the URL given above
+        echo $response;
+        $transaction->data['response_to_sagepay'] = $response;
+        //commerce_checkout_complete($order);
+      }
+      $transaction->message = 'Status @status, @statusdetail. VPSTxId=@vpstxid. Auth Code=@authcode. Address Check: @address. Postcode Check: @postcode. AVSCV2 Result: @avs. 3D Secure: @tds';
+      $transaction->message_variables = array(
+        '@status' => $baseStatus,
+        '@statusdetail' => $transaction->data['response_detail'],
+        '@vpstxid' => $data['VPSTxId'],
+        '@authcode' => $data['TxAuthNo'],
+        '@address' => $data['AddressResult'],
+        '@postcode' => $data['PostCodeResult'],
+        '@avs' => $data['AVSCV2'],
+        '@tds' => $data['3DSecureStatus'],
+      );
+      // Save all the changes to the payment transaction
+      // This will trigger payment update transaction rules and
+      // order first paid in full rules
+      commerce_payment_transaction_save($transaction);
+      break; // for loop - transaction has been found
+    }
+  }
+  //End of callback
+  exit;
+} 
+
+/*
+ * Validate payment by checking status of last transaction
+ */
+function commerce_sagepay_server_redirect_form_validate($order, $payment_method) {
+  $return = FALSE;
+  $transactions = commerce_payment_transaction_load_multiple(array(), array('order_id' => $order->order_id));
+  if (count($transactions) > 0) {
+    $transaction = end($transactions);
+    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+    switch ($transaction->data['vps_status']) {
+      case 'OK':
+        $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+        $return = TRUE;
+        //drupal_set_message(t('Transaction was processed succesfully'));
+        break;
+      case 'TAMPER':
+        drupal_set_message(t('There was a problem processing your transaction. Your credit/debit card was not charged. Please try again later.'), 'error');
+        break;
+      case 'NOTAUTHED';
+        drupal_set_message(t('There was a problem processing your transaction. Your credit/debit card was not charged. Please try again later.'), 'error');
+        break;
+      case 'REJECTED':
+        drupal_set_message(t('There was a problem processing your transaction. Your credit/debit card was not charged. Please try again later.'), 'error');
+        break;
+      case 'ABORT':
+        break;
+      case 'ERROR':
+      case 'FAIL':
+      case 'FAIL2':
+      case 'UNKNOWN':
+        drupal_set_message(t('There was a problem processing your transaction. Your credit/debit card was not charged. Please try again later.'), 'error');
+        break;
+    }
+    commerce_payment_transaction_save($transaction);
+  }
+  return $return;
+}
+
+function commerce_sagepay_valid_chars($text, $type = 'add') {
+  $accents = '/&([A-Za-z]{1,2})(grave|acute|circ|cedil|uml|lig);/';
+  $text_encoded = htmlentities($text, ENT_NOQUOTES, 'UTF-8');
+  $text = preg_replace($accents, '$1', $text_encoded);
+  switch ($type) {
+  case 'name':
+    $pat = '/[^(A-Za-z& \/\.\'\-)]*/';
+    break;
+  case 'add':
+    $pat = '/[^(A-Za-z0-9&:, \/\.\'\+\-\{\})]*/';
+    break;
+  case 'pc':
+    $pat = '/[^(A-Za-z0-9 \-)]*/';
+    break;
+  }
+  return preg_replace($pat, '', $text);
 }
\ No newline at end of file
