1

I've been trying (without success) to upgrade the Cartthrob_sage_s.php gateway on Cartthrob for SagePay Europe Offsite, in order to utilise the 3.00 protocol (upgrading from 2.23. I'm running latest editions of EE (2.9.2) and Cartthrob (2.61).

On just editing the VPSprotocol field to 3.00 in the 3 locations in file, the gateway progresses to processing of payment, then returns a VPSsignature error (which I expected, since other fields are listed as mandatory in the gneeric upgrade guidelines from Sage).

On attempting to add BankAuthCode, DeclineCode and ExpiryDate to my response as part of the generated MD5, I still get a mismatch between that and the VPS signature generated by Sage (VPS signature error as above). I've established I must be missing something, but I'm not sure what - I double checked and removing the new fields and restoring protocol to 2.23 makes the gateway function again.

I've attached file contents for clarity (apologies for length):

<?php class Cartthrob_sage_s extends Cartthrob_payment_gateway {
public $title = 'sage_server_title';
public $affiliate = 'sage_affiliate'; 
public $overview = 'sage_overview';
public $language_file = TRUE;
public $settings = array(
    array(
        'name' => 'sage_payment_page_style', 
        'short_name' => 'profile', 
        'type' => 'radio',  
        'default' => 'NORMAL', 
        'options' => array(
            'NORMAL' => 'sage_normal',
            'LOW' => 'sage_minimal_formatting'
            ),
    ),
    array(
        'name' =>  'mode',
        'short_name' => 'mode', 
        'type' => 'radio',  
        'default' => 'test', 
        'options' => array(
            'simulator' => 'simulator',
            'test' => 'test',
            'live' => 'live'
            ),
    ),
    array(
        'name' => 'sage_vendor_name',
        'short_name' => 'vendor_name', 
        'type' => 'text',
    ),
);

public $required_fields = array(
    'first_name',
    'last_name',
    'address',
    'city',
    'zip',
    'country_code'
);


public $fields = array(
    'first_name'           ,
    'last_name'            ,
    'address'              ,
    'address2'             ,
    'city'                 ,
    'state'                ,
    'zip'                  ,
    'country_code'         ,
    'shipping_first_name'  ,
    'shipping_last_name'   ,
    'shipping_address'     ,
    'shipping_address2'    ,
    'shipping_city'        ,
    'shipping_state'       ,
    'shipping_zip'         ,
    'shipping_country_code',
    'phone'                ,
    'email_address'        ,
    ); 

public $hidden = array('description','currency_code');

/**
 * process_payment
 *
 * @param string $credit_card_number 
 * @return mixed | array | bool An array of error / success messages  is returned, or FALSE if all fails.
 * @author Chris Newton
 * @access public
 * @since 1.0.0
 */
public function charge($credit_card_number)
{

    if ($this->plugin_settings('mode') == "test")
    {
        $this->_host = "https://test.sagepay.com/gateway/service/vspserver-register.vsp";
        $this->tokenrebillhost = "https://test.sagepay.com/gateway/service/vspdirect-register.vsp"; 
        $this->tokenhost = "https://test.sagepay.com/gateway/service/token.vsp"; 

    }
    elseif ($this->plugin_settings('mode') == "simulator")
    {
        $this->_host = "https://test.sagepay.com/Simulator/VSPServerGateway.asp?Service=VendorRegisterTx";
    }
    else
    {
        $this->tokenrebillhost = "https://live.sagepay.com/gateway/service/vspdirect-register.vsp"; 
        $this->tokenhost = "https://live.sagepay.com/gateway/service/token.vsp"; 

        $this->_host = "https://live.sagepay.com/gateway/service/vspserver-register.vsp";
    }

    $auth['authorized']     =   FALSE; 
    $auth['declined']       =   FALSE; 
    $auth['failed']         =   TRUE; 
    $auth['error_message']  =   NULL; 
    $auth['transaction_id'] =   NULL;

    /*
    // find out if this needs SSL 
    if (!function_exists('openssl_open'))
    {
        $auth['error_message']  = $this->lang('gateway_requires_ssl'); 
        return $auth;
    }
    */

    $basket=""; 

    if ($this->order('items'))
    {
        $basket = (count($this->order('items'))+2).":"; 

        foreach ($this->order('items') as $row_id => $item)
        {
            $basket .= str_replace(":","",$item['title']) .":";
            $basket .= $item['quantity'] .":";
            $basket .= number_format($item['price'],2,'.','').":";
            $basket .= ":";
            $basket .= number_format($item['price'],2,'.','').":";
            $basket .= number_format(($item['price']*$item['quantity']),2,'.','').":";

        }
        $basket .= 'Shipping:----:----:----:----:';
        $basket .= number_format($this->order('shipping'), 2, '.', '').":";
        $basket .= 'VAT/Tax:----:----:----:----:';
        $basket .= number_format($this->order('tax'), 2, '.', '');
    }

    if (strlen($basket) > 7499)
    {
        // the basket can't be over 7500, and has to be formatted a specific way. We'll remove it if it's too long.
        $basket = ""; 
    }

    $country_code = $this->order('country_code') ? $this->alpha2_country_code($this->order('country_code')) : "GB"; 
    $shipping_country_code = $this->order('shipping_country_code') ? $this->alpha2_country_code($this->order('shipping_country_code')) : $country_code; 

    $post_array = array(
        'VPSProtocol'       => "3.00",
        'TxType'                => "PAYMENT",
        'Website'           => "eqvvs.co.uk",
        'Vendor'                => $this->plugin_settings('vendor_name'),
        'VendorTXCode'      => $this->order('entry_id')."_".time(), // needs a unique ID for this transaction. 
        'Amount'                => number_format($this->total(),2,'.',''),
        'Currency'          => ($this->order('currency_code') ? $this->order('currency_code') : "GBP"),
        'Description'       => substr(($this->order('description') ? $this->order('description') : "Purchase from ".$this->order('site_name')), 0, 40),
        'NotificationURL'   => $this->response_script(ucfirst(get_class($this))),
        'CustomerEMail'     => $this->order('email_address'),
        'BillingFirstnames' => substr($this->order('first_name'), 0, 20),
        'BillingSurname'        => substr($this->order('last_name'),0,20),
        'BillingAddress1'   => substr($this->order('address'), 0, 100),
        'BillingAddress2'   => substr($this->order('address2'), 0, 100),
        'BillingCity'       => substr($this->order('city'), 0, 40),
        'BillingPostCode'   => substr($this->order('zip'), 0, 10),
        'BillingCountry'        => $country_code,
        'BillingState'      => "",
        'BillingPhone'      => preg_replace('/[^0-9-]/', '', $this->order('phone')),
        'DeliveryFirstnames'    => substr(($this->order('shipping_first_name') ? $this->order('shipping_first_name') : $this->order('first_name')), 0, 20),
        'DeliverySurname'   => substr(($this->order('shipping_last_name') ? $this->order('shipping_last_name') : $this->order('last_name')),0,20),
        'DeliveryAddress1'  => substr(($this->order('shipping_address') ? $this->order('shipping_address') : $this->order('address')), 0, 100),
        'DeliveryAddress2'  => substr(($this->order('shipping_address2') ? $this->order('shipping_address2') : $this->order('address2')), 0, 100),
        'DeliveryCity'      => substr(($this->order('shipping_city') ? $this->order('shipping_city') : $this->order('city')), 0, 40),
        'DeliveryPostCode'  => substr(($this->order('shipping_zip') ? $this->order('shipping_zip') : $this->order('zip')), 0, 10),
        'DeliveryCountry'   => $shipping_country_code,
        'DeliveryState'     => "",
        'DeliveryPhone'     => preg_replace('/[^0-9-]/', '', $this->order('phone')),
        'Basket'                => $basket  
    ); 

    if (strlen($post_array['NotificationURL']) > 250)
    {
        $resp = array(
             'authorized'       =>  FALSE,
             'error_message'    =>  $this->lang('sage_s_notification_url_too_long'),
             'failed'           =>  TRUE,
             'declined'         =>  FALSE,
             'transaction_id'   =>  NULL,
            );
        return $resp; 
    }

    // We don't want to pass the state data to eWay unless it has 2 characters and is a us state. They don't accept any non-us state values
    if ("US" != $post_array['DeliveryCountry'])
    {
        $post_array['DeliveryState']  = "";
    }
    else
    {
        $post_array['DeliveryState'] = strtoupper($this->order('shipping_state') ? $this->order('shipping_state') : $this->order('state'));
    }
    if ("US" != $post_array['BillingCountry'])
    {
        $post_array['BillingState']  = ""; 
    }
    else
    {
        $post_array['BillingState'] = strtoupper($this->order('state')); 
    }

    $post_array['Profile']  = $this->plugin_settings('profile'); 

    $data =     $this->data_array_to_string($post_array);

    $connect =  $this->curl_transaction($this->_host,$data); 


    if (!$connect)
    {
        $auth['failed']         = TRUE;
        $auth['authorized']     = FALSE;
        $auth['declined']       = FALSE;
        $auth['error_message']  = $this->lang('curl_gateway_failure'); 
        return $auth; 
    }

    $transaction =  $this->split_url_string($connect, "\r\n");
    $next_url = explode("NextURL=", $connect);

    if (!empty($next_url[1]))
    {
        $next_url = str_replace("\r\n","",$next_url[1]);
    }

    if (!is_array($transaction))
    {
        $auth['failed']         = TRUE;
        $auth['authorized']     = FALSE;
        $auth['declined']       = FALSE;
        $auth['error_message']  = $this->lang('sage_failed');

        return $auth;
    }

    if ("OK" != strtoupper($transaction['Status']))
    {
        switch(strtoupper($transaction['Status']))
        {
            case "MALFORMED":
                $auth['error_message'] = $this->lang('sage_malformed'); 
                $auth['error_message'] .= $transaction['StatusDetail']; 
                break; 
            case "INVALID":
                $auth['error_message'] = $this->lang('sage_invalid');
                $auth['error_message'] .= $transaction['StatusDetail']; 
                break;
            case "ERROR":
                $auth['error_message'] = $this->lang('sage_error');
                break;
            default:
                $auth['error_message'] = $this->lang('sage_default');
        }

        $auth['failed']         = TRUE;
        $auth['authorized']     = FALSE;
        $auth['declined']       = FALSE;
        $auth['transaction_id'] = NULL; 
        return $auth; 
    }

    $this->update_order(array('sage_key' => $transaction['SecurityKey']));
    $this->gateway_exit_offsite(NULL, $next_url); exit;
}
public function charge_token($token, $customer_id)
{
    $resp = array(
         'authorized'       =>  FALSE,
         'error_message'    =>  NULL,
         'failed'           =>  TRUE,
         'declined'         =>  FALSE,
         'transaction_id'   =>  NULL,
        );

    switch ($this->order('card_type'))
    {
        case "mc": 
            $card_type="MC"; 
            break;
        case 'visa': 
            $card_type="VISA"; 
            break;
        case 'amex': 
            $card_type="AMEX"; 
            break;
        case 'maestro': 
            $card_type="MAESTRO"; 
            break;
        case 'solo': 
            $card_type="SOLO"; 
            break;
        case 'delta': 
            $card_type="DELTA"; 
            break;
        default: $card_type="VISA";  
    }
    $basket=""; 

    if ($this->order('items'))
    {
        $basket = (count($this->order('items'))+2).":"; 

        foreach ($this->order('items') as $row_id => $item)
        {

            $basket .= $item['title'] .":";
            $basket .= $item['quantity'] .":";
            $basket .= number_format($item['price'],2,'.','').":";
            $basket .= ":";
            $basket .= number_format($item['price'],2,'.','').":";
            $basket .= number_format(($item['price']*$item['quantity']),2,'.','').":";

        }
        $basket .= 'Shipping:----:----:----:----:';
        $basket .= number_format($this->order('shipping'), 2, '.', '').":";
        $basket .= 'VAT/Tax:----:----:----:----:';
        $basket .= number_format($this->order('tax'), 2, '.', '');
    }

    $post_array = array(
        'VPSProtocol'               => "3.00",
        'TxType'                    => 'PAYMENT',
        'Vendor'                    => $this->plugin_settings('vendor_name'),
        'VendorTxCode'              => $this->order('entry_id')."_".time(),
        'Amount'                    => number_format($this->order('total'),2,'.',''),
        'Currency'                  => ( $this->order('currency_code')?  $this->order('currency_code') : "GBP"),  
        'Description'               =>  ($this->order('description') ? $this->order('description') : "Purchase from ".$this->order('site_name')),
        'Token'                     => $token,
        'StoreToken'                => 1,
        'BillingSurname'            => $this->order('last_name'),
        'BillingFirstnames'         => $this->order('first_name'),
        'BillingAddress1'           => $this->order('address'),
        'BillingAddress2'           => $this->order('address2'),
        'BillingCity'               => $this->order('city'),
        'BillingPostCode'           => $this->order('zip'),
        'BillingCountry'            => $this->alpha2_country_code($this->order('country_code')),
        'BillingPhone'              => preg_replace('/[^0-9-]/', '', $this->order('phone')),
        'DeliverySurname'           => ($this->order('shipping_last_name') ? $this->order('shipping_last_name') : $this->order('last_name')),
        'DeliveryFirstnames'        => ($this->order('shipping_first_name') ? $this->order('shipping_first_name') : $this->order('first_name')),
        'DeliveryAddress1'          => ($this->order('shipping_address') ? $this->order('shipping_address') : $this->order('address')),
        'DeliveryAddress2'          => ($this->order('shipping_address2') ? $this->order('shipping_address2') : $this->order('address2')),
        'DeliveryCity'              => ($this->order('shipping_city') ? $this->order('shipping_city') : $this->order('city')),
        'DeliveryPostCode'          => ($this->order('shipping_zip') ? $this->order('shipping_zip') : $this->order('zip')),
        'DeliveryCountry'           => ($this->order('shipping_country_code') ? $this->alpha2_country_code($this->order('shipping_country_code')) : $this->alpha2_country_code($this->order('country_code'))),
        'CustomerEMail'             => $this->order('email_address'),
        'Basket'                    => $basket,
        'Apply3DSecure'             => 0,
    );  
    // We don't want to pass the state data to Sage unless it has 2 characters and is a us state. They don't accept any non-us state values
    if ("US" != $post_array['DeliveryCountry'])
    {
        $post_array['DeliveryState']  = "";
    }
    else
    {
        $post_array['DeliveryState'] = strtoupper($this->order('shipping_state'));
    }
    if ("US" != $post_array['BillingCountry'])
    {
        $post_array['BillingState']  = ""; 
    }
    else
    {
        $post_array['BillingState'] = strtoupper($this->order('state')); 
    }

    $data =     $this->data_array_to_string($post_array);
    $connect =  $this->curl_transaction($this->tokenrebillhost,$data); 

    if (!$connect)
    {
        $resp['failed']         = TRUE;
        $resp['authorized']     = FALSE;
        $resp['declined']       = FALSE;
        $resp['error_message']  = $this->lang('curl_gateway_failure'); 

        return $resp; 
    }   

    $transaction = $this->sage_string_split($connect); 

    if (!empty($transaction['Status']))
    {
        if ("OK" == strtoupper($transaction['Status']))
        {
            $resp = array(
                 'authorized'       =>  TRUE,
                 'error_message'    =>  NULL,
                 'failed'           =>  FALSE,
                 'declined'         =>  FALSE,
                 'transaction_id'   =>  trim($transaction['VPSTxId'], "{}")
                );
        }
        else
        {
            switch(strtoupper($transaction['Status']))
            {
                case "MALFORMED":
                $error_message = $this->lang('sage_malformed'); 
                $error_message .= $transaction['StatusDetail']; 
                break; 
                case "INVALID":
                $error_message = $this->lang('sage_invalid');
                $error_message .= $transaction['StatusDetail']; 
                break;
                case "ABORT":
                $error_message = $this->lang('transaction_cancelled');
                break;
                case "NOTAUTHED":
                $error_message = $this->lang('sage_notauthed');
                break; 
                case "REJECTED": 
                $error_message = $this->lang('sage_rejected'); 
                break; 
                case "PPREDIRECT":
                $error_message = $this->lang('sage_ppredirect');
                break;
                case "AUTHENTICATED":
                $error_message = $this->lang('sage_authenticated');
                break;
                case "REGISTERED":
                $error_message = $this->lang('sage_registered');
                break;
                case "ERROR":
                $error_message = $this->lang('sage_error');
                break;
                case "ATTEMPTED":
                $error_message =  $this->lang('sage_error')."; ATTEMPTED"; //$this->lang('sage_ATTEMPTED');
                break;
                case "NOTAVAILABLE":
                $error_message = $this->lang('sage_error')."; NOTAVAILABLE";  //$this->lang('sage_NOTAVAILABLE');
                break;
                case "INCOMPLETE":
                $error_message = $this->lang('sage_error')."; INCOMPLETE"; //$this->lang('sage_INCOMPLETE');
                break;
                default:
                $error_message = $this->lang('sage_default');
            }

            $resp = array(
                 'authorized'       =>  FALSE,
                 'error_message'    =>  $error_message,
                 'failed'           =>  TRUE,
                 'declined'         =>  FALSE,
                 'transaction_id'   =>  NULL
                );
        }
    }
    return $resp;
}

public function create_token($credit_card_number)
{
    $post_array = array(
        'VPSProtocol'               => "3.00",
        'TxType'                    => 'TOKEN',
        'Vendor'                    => $this->plugin_settings('vendor_name'),
        'Currency'                  => ( $this->order('currency_code')?  $this->order('currency_code') : "GBP"),  
        'NotificationURL'           => $this->response_script(ucfirst(get_class($this)), array('create_token')),  
        'Profile'                     => $this->plugin_settings('profile'),
    );  

    $data =     $this->data_array_to_string($post_array);

    $connect =  $this->curl_transaction($this->tokenhost,$data); 

    if (!$connect)
    {
        $auth['failed']         = TRUE;
        $auth['authorized']     = FALSE;
        $auth['declined']       = FALSE;
        $auth['error_message']  = $this->lang('curl_gateway_failure'); 
        return $auth; 
    }

    $transaction =  $this->split_url_string($connect, "\r\n");
    $next_url = explode("NextURL=", $connect);

    if (!empty($next_url[1]))
    {
        $next_url = str_replace("\r\n","",$next_url[1]);
    }

    if (!is_array($transaction))
    {
        $auth['failed']         = TRUE;
        $auth['authorized']     = FALSE;
        $auth['declined']       = FALSE;
        $auth['error_message']  = $this->lang('sage_failed');

        return $auth;
    }

    if ("OK" != strtoupper($transaction['Status']))
    {
        switch(strtoupper($transaction['Status']))
        {
            case "MALFORMED":
                $auth['error_message'] = $this->lang('sage_malformed'); 
                $auth['error_message'] .= $transaction['StatusDetail']; 
                break; 
            case "INVALID":
                $auth['error_message'] = $this->lang('sage_invalid');
                $auth['error_message'] .= $transaction['StatusDetail']; 
                break;
            case "ERROR":
                $auth['error_message'] = $this->lang('sage_error');
                break;
            default:
                $auth['error_message'] = $this->lang('sage_default');
        }

        $auth['failed']         = TRUE;
        $auth['authorized']     = FALSE;
        $auth['declined']       = FALSE;
        $auth['transaction_id'] = NULL; 
        return $auth; 
    }

    $this->update_order(array('sage_key' => $transaction['SecurityKey']));
    $this->gateway_exit_offsite(NULL, $next_url); exit;
}

/**
 * payment_notification
 *
 * @return void
 * @author Chris Newton
 * @since 1.0
 **/
function extload($post)
{
    $auth['authorized']     =   FALSE; 
    $auth['declined']       =   FALSE; 
    $auth['failed']         =   TRUE; 
    $auth['error_message']  =   NULL; 
    $auth['transaction_id'] =   NULL;


    if (!empty($post['VendorTxCode']))
    {
        list($order_id) = explode("_", $post['VendorTxCode']); 

        $this->relaunch_cart(NULL, $order_id);  
    }
    else
    {
        die($this->lang('sage_default')); 
    }

    if (strpos( $this->order('return'), 'http') === 0)
    {
        $return_url =  $this->order('return'); 
    }
    else
    {
        $return_url = $this->create_url($this->order('return'));
    }


    if ("OK" == strtoupper($post['Status']))
    {
        $tmp = array(
            'VPSTxId'           =>  $post['VPSTxId'],
            'VendorTxCode'      =>  $post['VendorTxCode'],
            'Status'            =>  $post['Status'],
            'TxAuthNo'          =>  $post['TxAuthNo'],
            'BankAuthCode'      =>  $post['BankAuthCode'],
            'DeclineCode'       =>  $post['DeclineCode'],
            'ExpiryDate'            =>  $post['ExpiryDate'],
            'FraudResponse'     =>  $post['FraudResponse'],
            'VendorName'        =>  strtolower($this->plugin_settings('vendor_name')),
            'AVSCV2'            =>  $post['AVSCV2'],
            'SecurityKey'       =>  $this->order('sage_key'),
            'AddressResult'     =>  $post['AddressResult'],
            'PostCodeResult'    =>  $post['PostCodeResult'],
            'CV2Result'         =>  $post['CV2Result'],
            'GiftAid'           =>  $post['GiftAid'],
            '3DSecureStatus'    =>  $post['3DSecureStatus'],
            'CAVV'              =>  (!empty($post['CAVV'])?$post['CAVV']: ""),
            'AddressStatus'     =>  (!empty($post['AddressStatus'])?$post['AddressStatus']: ""),
            'PayerStatus'       =>  (!empty($post['PayerStatus'])?$post['PayerStatus']: ""),
            'CardType'          =>  $post['CardType'],
            'Last4Digits'       =>  $post['Last4Digits']
            ); 

        if (empty($tmp['AddressStatus']))
        {
            unset ($tmp['AddressStatus']);
            unset ($tmp['PayerStatus']); 
        }

        $md5 = implode("",$tmp);

        $md5hash = strtoupper(md5($md5));
        if (!empty($post['ct_action']) && $post['ct_action'] == "create_token")
        {   
            $tmp = array(
                'VPSTxId'           =>  $post['VPSTxId'],
                'VendorTxCode'      =>  $post['VendorTxCode'],
                'Status'            =>  $post['Status'],
                'VendorName'        =>  "eqvvs",
                'SecurityKey'       =>  $this->order('sage_key'),
                'Token'             =>  $post['Token']
                ); 

            $md5 = implode("",$tmp);

            $md5hash = strtoupper(md5($md5));
        }


        if ($md5hash != strtoupper($post['VPSSignature']))
        {
            $auth['authorized']     =   FALSE; 
            $auth['declined']       =   FALSE; 
            $auth['failed']         =   TRUE; 
            $auth['error_message']  =   $this->lang('sage_signature_not_valid'); 
            $auth['transaction_id'] =   NULL;

            $this->checkout_complete_offsite($auth, $order_id, 'stop_processing'); 
            // SAGE requires that we output this stuff. 
            @ob_clean();
            header("Content-type: text/plain");
            echo "Status=INVALID\r\n";
            echo "RedirectURL=".$return_url."\r\n";
            @ob_flush();
            exit; 
        }
        $auth['authorized']     =   TRUE; 
        $auth['declined']       =   FALSE; 
        $auth['failed']         =   FALSE; 
        $auth['error_message']  =   NULL; 
        $auth['transaction_id'] =   trim($post['VPSTxId'], "{}"); 
        // "Auth:".$post['TxAuthNo']."_Tx:".$post['VPSTxId']."_Vnd:".$post['VendorTxCode']."_Sec:".$_SESSION['cartthrob']['sage_key'];

        if (!empty($post['ct_action']) && $post['ct_action'] == "create_token")
        {
            $token = new Cartthrob_token();
            $token->set_token( $post['Token'] ); 
            exit; 
        }
        $this->checkout_complete_offsite($auth, $order_id, 'stop_processing'); 
        // SAGE requires that we output this stuff. 
        @ob_clean();
        header("Content-type: text/plain");
        echo "Status=OK\r\n";
        echo "RedirectURL=".$return_url."\r\n";
        @ob_flush();
        exit;
    }
    else
    {
        $status = NULL; 
        $redirect_url = NULL; 
        switch(strtoupper($post['Status']))
        {
            case "NOTAUTHED":
                $auth['error_message'] = $this->lang('sage_notauthed');
                $status = "Status=OK\r\n";
                break; 
            case "ABORT":
                $auth['error_message'] =  $this->lang('transaction_cancelled');  
                $status = "Status=OK\r\n";
                $redirect_url = "RedirectURL=".$return_url."\r\n";

                $this->set_status_canceled($auth, $this->order('entry_id'),  FALSE);    
                $this->save_cart();

                // but need to handle canceled as well.
                $auth['canceled'] = TRUE; 
                break;
            case "REJECTED": 
                $auth['error_message'] =  $this->lang('sage_rejected'); 
                $auth['declined']       =   TRUE; 
                $status =  "Status=INVALID\r\n";
                break; 
            case "AUTHENTICATED":
                $auth['error_message'] = $this->lang('sage_authenticated');
                $status =  "Status=OK\r\n";
                break;
            case "REGISTERED":
                $auth['error_message'] =  $this->lang('sage_registered');
                $status = "Status=OK\r\n";
                break;
            case "ERROR":
                $auth['error_message'] = $this->lang('sage_error');
                $status = "Status=INVALID\r\n";
                break;
            default:
                $auth['error_message'] =  $this->lang('sage_default');
        $auth['authorized']     =   FALSE; 
        $auth['transaction_id'] =   NULL;
        }

        if (!empty($post['ct_action']) && $post['ct_action'] == "create_token")
        {
            $token = new Cartthrob_token();
            $token->set_error_message( $auth['error_message']  );
            exit; 
        }


        $this->checkout_complete_offsite($auth, $order_id, 'stop_processing'); 
        @ob_clean();
        header("Content-type: text/plain");
        if ($status)
        {
            echo $status; 
        }
        else
        {
            echo "Status=INVALID\r\n";
        }
        if ($redirect_url)
        {
            echo $redirect_url; 
        }
        else
        {
            echo "RedirectURL=".$return_url."\r\n";
        }
        // SAGE requires that we output this stuff. 
        @ob_flush();
        exit;       
    }
} }
  • This question seems better posed directly to SagePay. Surely they can check their logs and tell you quickly what's missing. I don't see anyone really having an answer for you here since this is so 3rd Party. – Anna_MediaGirl Feb 19 '15 at 18:22
  • Fair point Anna yep - I wondered more whether anyone had attempted the upgrade of gateway manually to 3.00 and had any success (and in doing so had possibly encountered the same issues!) Here's hoping I can come back and answer it myself in the days to come ;) – Ben Coleman Feb 19 '15 at 21:38
1

Having spoken to the very helpful Chris @ Cartthrob during a dedicated support session, thought I'd come back to answer this - the soon to be latest edition of Cartthrob will handle Sagepay 3.00 vps - I can confirm all worked great from my point of view, as the update was conducted during the support session, and I was immediately able to both continue to process checkout, and take advantage of the new data fields via MySagePay.

I should also point out that I had updated to the latest version of cartthrob prior to that, but did not have the updated version of extload.php in my /themes/ folder - I believe this was error my end with a git commit failing to ignore reverting this file, but it would certainly be the first point of reference I'd check for anyone else out there struggling - I may perhaps have cracked the gateway issue myself had I not inadvertently reverted that file.

For reference, the vps has to be updated to 3.00 in all 3 areas on the sage gateway (I'm referring specifically to the offsite server version Cartthrob_sage_s.php) and the new mandatory fields (bankauth et al) added to the last set of fields within the gateway.

        $tmp = array(
            'VPSTxId'           =>  urldecode($post['VPSTxId']),
            'VendorTxCode'      =>  urldecode($post['VendorTxCode']),
            'Status'            =>  urldecode($post['Status']),
            'TxAuthNo'          =>  urldecode($post['TxAuthNo']),
            'VendorName'        =>  strtolower($this->plugin_settings('vendor_name')),
            'AVSCV2'            =>  urldecode($post['AVSCV2']),
            'SecurityKey'       =>  $this->order('sage_key'),
            'AddressResult'     =>  urldecode($post['AddressResult']),
            'PostCodeResult'    =>  urldecode($post['PostCodeResult']),
            'CV2Result'         =>  urldecode($post['CV2Result']),
            'GiftAid'           =>  urldecode($post['GiftAid']),
            '3DSecureStatus'    =>  urldecode($post['3DSecureStatus']),
            'CAVV'              =>  (!empty($post['CAVV'])? urldecode($post['CAVV']): ""),
            'AddressStatus'     =>  (!empty($post['AddressStatus'])? urldecode($post['AddressStatus']): ""),
            'PayerStatus'       =>  (!empty($post['PayerStatus'])? urldecode($post['PayerStatus']): ""),
            'CardType'          =>  urldecode($post['CardType']),
            'Last4Digits'       =>  urldecode($post['Last4Digits']), 
            'DeclineCode'       => urldecode($post['DeclineCode']),
            'ExpiryDate'        => urldecode($post['ExpiryDate']), 
            'FraudResponse'     => urldecode($post['fraudResponse']), 
            'BankAuthCode'      => urldecode($post['BankAuthCode'])
            );
| improve this answer | |
  • I wonder what is happening with "the soon to be latest edition of Cartthrob will handle Sagepay 3.00" now Cartthrob announced closure? Is the above all that is needed, or is there more work, just in case the update is not released? – Laurence Cope Jun 17 '15 at 11:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.