commerce_sagepay_common.inc in Drupal Commerce SagePay Integration 7
Common utility functions shared by all SagePay modules.
File
includes/commerce_sagepay_common.incView source
<?php
/**
* @file
* Common utility functions shared by all SagePay modules.
*/
/**
* Sets up a new form for the submit to Sage Pay button (off site redirect).
*
* @param array $form
* The form array.
* @param array $form_state
* Currency values and form state.
* @param commerce_order $order
* The Commerce Ordering being processed.
* @param array $settings
* The Sage Pay settings.
*
* @return mixed
* The form array.
*/
function commerce_sagepay_order_form($form, &$form_state, $order, $settings) {
// Wrap the order for easier access to data.
$wrapper = entity_metadata_wrapper('commerce_order', $order);
$total = commerce_line_items_total($wrapper->commerce_line_items);
// Add tax if we have sales tax in the order.
$total['amount'] = $wrapper->commerce_order_total->amount
->value();
// Load customer profile.
$profile = commerce_customer_profile_load($order->commerce_customer_billing[LANGUAGE_NONE][0]['profile_id']);
// Get user billing address.
$billing_address = $profile->commerce_customer_address[LANGUAGE_NONE][0];
// Get user delivery address.
$delivery_address = NULL;
if (isset($order->commerce_customer_shipping)) {
$delivery_profile = commerce_customer_profile_load($order->commerce_customer_shipping[LANGUAGE_NONE][0]['profile_id']);
$delivery_address = $delivery_profile->commerce_customer_address[LANGUAGE_NONE][0];
}
// Encrypt the order details (address and amount) ready to send to SagePay.
$encrypted_order = _commerce_sagepay_encrypted_order($settings, $order, $total, $billing_address, $delivery_address);
// Determine the correct transaction type based on the gateway settings.
switch (variable_get(SAGEPAY_SETTING_TRANSACTION_TYPE)) {
case COMMERCE_CREDIT_AUTH_CAPTURE:
$tx_type = 'PAYMENT';
break;
case COMMERCE_CREDIT_AUTH_ONLY:
$tx_type = 'DEFERRED';
break;
default:
// Set to deferred by default if there is no setting for the gateway.
$tx_type = 'DEFERRED';
}
// Build the data array that will be translated into hidden form values.
$data = array(
'VPSProtocol' => SAGEPAY_PROTOCOL,
'TxType' => $tx_type,
'Vendor' => variable_get(SAGEPAY_SETTING_VENDOR_NAME),
'Crypt' => $encrypted_order,
);
// Determine the correct url based on the transaction mode.
switch (variable_get(SAGEPAY_SETTING_TRANSACTION_MODE)) {
case SAGEPAY_TXN_MODE_LIVE:
$server_url = SAGEPAY_FORM_SERVER_LIVE;
break;
case SAGEPAY_TXN_MODE_TEST:
$server_url = SAGEPAY_FORM_SERVER_TEST;
break;
case SAGEPAY_TXN_MODE_SIMULATION:
$server_url = SAGEPAY_FORM_SERVER_SIMULATION;
break;
}
$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;
}
/**
* Create a Transaction and associate it with the order.
*
* @param array $payment_method
* The details of the payment method being used.
* @param commerce_order $order
* The Commerce order associated with the transaction.
* @param array $charge
* The charge details array including amount and currency.
* @param array $tokens
* Tokens available for the transaction message.
* @param int $transaction_status
* The transaction status (a constant defined by Drupal Commerce).
* @param string $remote_status
* A String indicated the status of the transaction at Sage Pay.
* @param commerce_payment_transaction $transaction
* If a transaction is being updated, this will be present.
*/
function commerce_sagepay_transaction($payment_method, $order, $charge, $tokens, $transaction_status, $remote_status, $transaction = NULL) {
$tokens['VendorTxCode'] = $order->data['VendorTxId'];
if (!isset($transaction)) {
$transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id);
$transaction->instance_id = $payment_method['instance_id'];
$transaction->amount = $charge['amount'];
$transaction->currency_code = $charge['currency_code'];
}
else {
$transaction->revision = 1;
}
// $transaction->payload += $tokens;
$transaction->payload = array_merge($transaction->payload, $tokens);
if (array_key_exists('VPSTxId', $tokens)) {
$transaction->remote_id = $tokens['VPSTxId'];
}
$transaction->remote_status = $remote_status;
// 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. ';
$transaction_message .= 'VPSTxId=@vpstxid. ';
$transaction_message .= 'Auth Code=@authcode. ';
$transaction_message .= 'Address Check: @address. ';
$transaction_message .= 'Postcode Check: @postcode. ';
$transaction_message .= 'AVSCV2 Result: @avs. ';
$transaction_message .= '3D Secure: @tds. ';
$transaction_message .= 'Address Status: @addressstatus. ';
$transaction_message .= 'Payer Status: @payerstatus. ';
$transaction_message .= 'Card Type: @cardtype. ';
$transaction_message .= 'last 4 Digits: @last4digits. ';
$transaction_message .= 'Fraud Response: @fraudresponse. ';
$transaction_message .= 'Surcharge: @surcharge. ';
$transaction_message .= 'Bank Auth Code: @bankauthcode. ';
$transaction_message .= 'Decline Code: @declinecode. ';
$transaction->message = $transaction_message;
$transaction->message_variables = array(
'@status' => isset($tokens['Status']) ? $tokens['Status'] : $transaction->status,
'@statusdetail' => isset($tokens['StatusDetail']) ? $tokens['StatusDetail'] : 'N/A',
'@vpstxid' => isset($tokens['VPSTxId']) ? $tokens['VPSTxId'] : 'N/A',
'@authcode' => isset($tokens['TxAuthNo']) ? $tokens['TxAuthNo'] : 'N/A',
'@address' => isset($tokens['AddressResult']) ? $tokens['AddressResult'] : 'N/A',
'@postcode' => isset($tokens['PostCodeResult']) ? $tokens['PostCodeResult'] : 'N/A',
'@avs' => isset($tokens['AVSCV2']) ? $tokens['AVSCV2'] : 'N/A',
'@tds' => isset($tokens['3DSecureStatus']) ? $tokens['3DSecureStatus'] : 'N/A',
'@giftaid' => isset($tokens['GiftAid']) ? $tokens['GiftAid'] : 'No',
'@addressstatus' => isset($tokens['AddressStatus']) ? $tokens['AddressStatus'] : 'N/A',
'@payerstatus' => isset($tokens['PayerStatus']) ? $tokens['PayerStatus'] : 'N/A',
'@cardtype' => isset($tokens['CardType']) ? $tokens['CardType'] : 'N/A',
'@last4digits' => isset($tokens['Last4Digits']) ? $tokens['Last4Digits'] : 'N/A',
'@fraudresponse' => isset($tokens['FraudResponse']) ? $tokens['FraudResponse'] : 'N/A',
'@surcharge' => isset($tokens['Surcharge']) ? $tokens['Surcharge'] : 'N/A',
'@bankauthcode' => isset($tokens['BankAuthCode']) ? $tokens['BankAuthCode'] : 'N/A',
'@declinecode' => isset($tokens['DeclineCode']) ? $tokens['DeclineCode'] : 'N/A',
);
commerce_payment_transaction_save($transaction);
return $transaction;
}
/**
* Helper function to process the response from SagePay of any transaction type.
*
* Return TRUE for a successful transaction.
*
* @param array $payment_method
* The payment method being used.
* @param commerce_order $order
* The Commerce Order to match against the response.
* @param array $tokens
* Tokens available to the response.
*
* @return bool
* Return TRUE is the payment was successful.
* Return FALSE if payment is rejected or fails for any reason.
*/
function commerce_sagepay_process_response($payment_method, $order, $tokens, $transaction = NULL) {
// Default no charge for failed transactions.
// (these values will not be in the callback for a failed or cancelled).
$def_charge = array(
'amount' => 0,
'currency_code' => '',
);
$order_id = $order->order_id;
// Check for a valid status callback.
switch ($tokens['Status']) {
case 'ABORT':
watchdog('commerce_sagepay', 'ABORT error from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ALERT);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Your SagePay transaction was aborted.'), 'error');
return FALSE;
case 'NOTAUTHED':
watchdog('commerce_sagepay', 'NOTAUTHED error from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ALERT);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Your transaction was not authorised.'), 'error');
return FALSE;
case 'REJECTED':
watchdog('commerce_sagepay', 'REJECTED error from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ALERT);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Your transaction was rejected. Reason: 3D-Authentication failed.'), 'warning');
$target_step = 'checkout_review';
drupal_alter('commerce_sagepay_3dsecure_fail_destination', $target_step);
commerce_order_status_update($order, $target_step);
return FALSE;
case 'MALFORMED':
watchdog('commerce_sagepay', 'MALFORMED error from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ALERT);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Sorry the transaction has failed.'), 'error');
return FALSE;
case 'INVALID':
watchdog('commerce_sagepay', 'INVALID error from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ERROR);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Sorry an error occurred while processing your transaction. %message', array(
'%message' => $tokens['StatusDetail'],
)), 'error');
$target_step = 'checkout_review';
// Allow custom modules to change which page you end up at when the order
// is invalid.
drupal_alter('commerce_sagepay_invalid_destination', $target_step);
commerce_order_status_update($order, $target_step);
return FALSE;
case 'ERROR':
watchdog('commerce_sagepay', 'System ERROR from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ERROR);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Sorry an error occurred while processing your transaction. %message', array(
'%message' => $tokens['StatusDetail'],
)), 'error');
return FALSE;
case 'FAIL':
watchdog('commerce_sagepay', 'System FAIL from SagePay for order %order_id with message %msg', array(
'%order_id' => $order_id,
'%msg' => $tokens['StatusDetail'],
), WATCHDOG_ERROR);
commerce_sagepay_transaction($payment_method, $order, $def_charge, $tokens, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
drupal_set_message(t('Sorry an error occurred while processing your transaction. %message', array(
'%message' => $tokens['StatusDetail'],
)), 'error');
return FALSE;
case 'OK':
$arr_charge = array();
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
if (array_key_exists('Amount', $tokens)) {
$arr_charge['amount'] = $tokens['Amount'];
$arr_charge['currency_code'] = $order_wrapper->commerce_order_total->currency_code
->value();
}
/* As we have already created a transaction, we no longer need to reset this and reload
$transaction = NULL; */
// If 3D secure is in use, we may have to load a partial transaction.
if (module_exists('sagepay_3d_secure')) {
$transaction_3d = sagepay_3d_secure_load_transaction($tokens, $order);
if ($transaction_3d) {
$transaction = $transaction_3d;
}
}
$remote_status = SAGEPAY_REMOTE_STATUS_PAYMENT;
if (isset($transaction->remote_status)) {
if ($transaction->remote_status == 'DEFERRED') {
$remote_status = SAGEPAY_REMOTE_STATUS_DEFERRED;
}
}
// Check for the scenario where we stored the original requested remote
// status prior to 3d Secure.
if (isset($transaction->payload['original_remote_status'])) {
$remote_status = $transaction->payload['original_remote_status'];
unset($transaction->payload['original_remote_status']);
}
commerce_sagepay_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_SUCCESS, $remote_status, $transaction);
break;
case 'AUTHENTICATED':
$arr_charge = array();
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$arr_charge['amount'] = $tokens['Amount'];
$arr_charge['currency_code'] = $order_wrapper->commerce_order_total->currency_code
->value();
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'],
));
commerce_sagepay_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_PENDING, SAGEPAY_REMOTE_STATUS_AUTHENTICATE, $transaction);
break;
case 'REGISTERED':
$arr_charge = array();
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$arr_charge['amount'] = $tokens['Amount'];
$arr_charge['currency_code'] = $order_wrapper->commerce_order_total->currency_code
->value();
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'],
));
commerce_sagepay_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_PENDING, SAGEPAY_REMOTE_STATUS_REGISTERED, $transaction);
break;
case 'PPREDIRECT':
// Card type was PayPal and the server has responded with a PayPal
// redirect URL.
$arr_charge = array();
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$arr_charge['amount'] = $tokens['Amount'];
$arr_charge['currency_code'] = $order_wrapper->commerce_order_total->currency_code
->value();
watchdog('commerce_sagepay', 'REGISTERED PAYPAL Payment callback
received
from SagePay for order %order_id with status code %status', array(
'%order_id' => $order_id,
'%status' => $tokens['Status'],
));
commerce_sagepay_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_PENDING, SAGEPAY_REMOTE_STATUS_PAYPAL_REDIRECT);
break;
case '3DAUTH':
// Server has replied with a 3D Secure authentication request.
// Store the returned variables in the order object for processing
// by the 3D Secure module.
// The returned variables should be:
// 1) PAReq.
// 2) ACSURL.
// 3) MD.
$tds_data = array();
$tds_data['PAReq'] = $tokens['PAReq'];
$tds_data['ACSURL'] = $tokens['ACSURL'];
$tds_data['MD'] = $tokens['MD'];
$tds_data['TermUrl'] = url('commerce-sagepay/3d_secure_callback/' . $order->order_id, array(
'absolute' => TRUE,
));
$order->data['extra_authorisation'] = $tds_data;
$arr_charge = array();
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$arr_charge['amount'] = $tokens['Amount'];
$arr_charge['currency_code'] = $order_wrapper->commerce_order_total->currency_code
->value();
// Store the original remote status in the payload otherwise we lose it
// when we change the remote status to 3D Secure.
$tokens['original_remote_status'] = $transaction->remote_status;
commerce_sagepay_transaction($payment_method, $order, $arr_charge, $tokens, COMMERCE_PAYMENT_STATUS_PENDING, SAGEPAY_REMOTE_STATUS_3D_SECURE, $transaction);
break;
default:
// 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);
drupal_set_message(t('Sorry an error occurred while processing your transaction. %message', array(
'%message' => $tokens['StatusDetail'],
)), 'error');
return FALSE;
}
return TRUE;
}
/*
* Generate a unique VendorTxId
*
* @param commerce_order $order
* The Commerce Order.
*
* @param string $type
* Optional type
*
* @param string $prefix
* Override the prefix
*
* @return string
* Returns a unique string that can be used as VendorTxId
*/
function _commerce_sagepay_vendor_tx_code($order, $type = FALSE, $prefix = FALSE) {
// check prefix
if ($prefix === FALSE) {
// default to configured
$prefix = variable_get(SAGEPAY_SETTING_TRANSACTION_PREFIX, FALSE);
}
// check order_id
$order_id = isset($order->order_id) ? $order->order_id : '';
// Generate a random number to be appended so that the order can be
// resubmitted to SagePage in the event the user clicks back to modify the
// order before completing. (otherwise SagePay rejects this
// as a duplicate).
$random_number = rand(0, 32000) * rand(0, 32000);
$parts = array();
// add prefix
if (!empty($prefix)) {
$parts[] = $prefix;
}
// add type
if (!empty($type)) {
$parts[] = $type;
}
// add order id
if (!empty($order_id)) {
$parts[] = $order_id;
}
// add random number
$parts[] = $random_number;
// return the tx code
return implode('_', $parts);
}
/*
* Extract an order id from a VendorTxCode
*
* @param string $vendor_tx_code
* a valid VendorTxCode
*
* @return boolean|string
* Returns the Order Id or boolean FALSE
*/
function _commerce_sagepay_vendor_tx_code_to_order_id($vendor_tx_code) {
$order_id = FALSE;
if (!empty($vendor_tx_code)) {
$parts = explode('_', $vendor_tx_code);
// at the very least there should be 2 parts
if (count($parts) == 2) {
// {order_id}_{random number}
$order_id = $parts[0];
}
else {
if (count($parts) == 3) {
// {prefix}_{order_id}_{random number}
// or {type}_{order_id}_{random number}
$order_id = $parts[1];
}
else {
if (count($parts) == 4) {
// should be in the format {prefix}_{type}_{order_id}_{random number}
$order_id = $parts[2];
}
}
}
}
return $order_id;
}
/**
* Encrypt the order details ready to send to SagePay Server.
*
* @param array $settings
* The payment gateway settings.
* @param commerce_order $order
* The Commerce Order being encrypted.
* @param int $total
* The total value of the order.
* @param commerce_profile $billing_address
* The billing address of the order.
* @param commerce_profile $delivery_address
* The delivery address of the order.
* @param string $integration_method
* The Sage Pay integration method being used.
* @param array $pane_values
* The values from the payment checkout pane.
*
* @return array|string
* Returns a String for Form integration method or an array for Server /
* Direct.
*/
function _commerce_sagepay_encrypted_order($settings, $order, $total, $billing_address, $delivery_address = NULL, $integration_method = SAGEPAY_FORM, $pane_values = NULL) {
if (!empty($pane_values['credit_card']['type'])) {
$pane_values['credit_card']['type'] = commerce_sagepay_lookup_sagepay_card_type($pane_values['credit_card']['type']);
}
// Check for Commerce Card on File value which indicates someone is trying
// to reuse a stored payment card.
// Redirect the request to the sagepay_token module.
if (module_exists('sagepay_token')) {
if (isset($pane_values) && array_key_exists('cardonfile', $pane_values) && $pane_values['cardonfile'] != 'new') {
$saved_card_data = commerce_cardonfile_load($pane_values['cardonfile']);
if (!array_key_exists('commerce_recurring', $pane_values) && !commerce_cardonfile_access('view', $saved_card_data)) {
$saved_card_data = NULL;
}
if (isset($saved_card_data)) {
$integration_method = SAGEPAY_TOKEN;
}
}
}
// generate a unique VendorTxId
$vendor_tx_code = _commerce_sagepay_vendor_tx_code($order);
// Update order number in database with the random number we just generated.
$order->data['VendorTxId'] = $vendor_tx_code;
commerce_order_save($order);
// Convert commerce int to decimal.
$order_amount = number_format(commerce_currency_amount_to_decimal($total['amount'], $total['currency_code']), 2, '.', '');
// Check if we need to encode cart.
$cart_setting = variable_get('sagepay_send_basket_contents');
$encoded_cart = '';
if ($cart_setting == '1') {
module_load_include('inc', 'commerce_sagepay', 'includes/commerce_sagepay_formatters');
$encoded_cart = _commerce_sagepay_cart_to_string($order);
}
elseif ($cart_setting == '2') {
module_load_include('inc', 'commerce_sagepay', 'includes/commerce_sagepay_formatters');
$encoded_cart = _commerce_sagepay_cart_to_xml($order)
->saveXML();
}
// Add a default postcode if the address supplied didn't have one.
// SagePay requires a postcode even if some countries do not have them.
$billing_address['postal_code'] = _commerce_sagepay_ensure_postal_code($billing_address);
// Determine the transaction type based on the payment gateway settings.
switch (variable_get(SAGEPAY_SETTING_TRANSACTION_TYPE)) {
case COMMERCE_CREDIT_AUTH_CAPTURE:
$tx_type = 'PAYMENT';
break;
case COMMERCE_CREDIT_AUTH_ONLY:
$tx_type = 'AUTHENTICATE';
break;
default:
// Set to deferred if there is no setting for the payment gateway.
$tx_type = 'DEFERRED';
}
$query = array(
'VPSProtocol' => SAGEPAY_PROTOCOL,
'Vendor' => variable_get(SAGEPAY_SETTING_VENDOR_NAME),
'VendorTxCode' => $vendor_tx_code,
'Amount' => $order_amount,
'Currency' => $total['currency_code'],
'Description' => variable_get(SAGEPAY_SETTING_ORDER_DESCRIPTION),
'CustomerName' => $billing_address['first_name'] . ' ' . $billing_address['last_name'],
'CustomerEmail' => $order->mail,
'VendorEmail' => variable_get(SAGEPAY_SETTING_VENDOR_EMAIL),
'SendEmail' => variable_get(SAGEPAY_SETTING_SEND_EMAIL),
'eMailMessage' => variable_get(SAGEPAY_SETTING_EMAIL_MESSAGE),
'BillingSurname' => substr($billing_address['last_name'], 0, 20),
'BillingFirstnames' => substr($billing_address['first_name'], 0, 20),
'BillingAddress1' => $billing_address['thoroughfare'],
'BillingAddress2' => $billing_address['premise'],
'BillingCity' => $billing_address['locality'],
'BillingPostcode' => $billing_address['postal_code'],
'BillingCountry' => $billing_address['country'],
'DeliverySurname' => substr($billing_address['last_name'], 0, 20),
'DeliveryFirstnames' => substr($billing_address['first_name'], 0, 20),
'DeliveryAddress1' => $billing_address['thoroughfare'],
'DeliveryAddress2' => $billing_address['premise'],
'DeliveryCity' => $billing_address['locality'],
'DeliveryPostcode' => $billing_address['postal_code'],
'DeliveryCountry' => $billing_address['country'],
'ApplyAVSCV2' => variable_get(SAGEPAY_SETTING_APPLY_AVS_CV2),
'Apply3DSecure' => variable_get(SAGEPAY_SETTING_APPLY_3D_SECURE),
);
// Add module version to transaction as referrerID to aid debugging.
$module_info = system_get_info('module', 'commerce_sagepay');
$version = str_replace('-', '_', $module_info['version']);
// Sage Pay doesn't like - so replace.
$arr_version = explode('+', $version);
// Split the version in case it's a dev version
$version = $arr_version[0];
$query['ReferrerID'] = 'commerce_sagepay_' . $version;
switch ($integration_method) {
case SAGEPAY_FORM:
$query['SuccessURL'] = isset($settings['return']) ? $settings['return'] : '';
$query['FailureURL'] = isset($settings['cancel_return']) ? $settings['cancel_return'] : '';
break;
case SAGEPAY_SERVER:
$query['NotificationURL'] = url('commerce-sagepay-server/vps-callback/' . $order->order_id . '/' . $order->data['payment_redirect_key'], array(
'absolute' => TRUE,
));
$query['TxType'] = $tx_type;
if (variable_get(SAGEPAY_SETTING_LOW_PROFILE, 1) == 1) {
$query['Profile'] = 'LOW';
}
else {
$query['Profile'] = 'NORMAL';
}
$query['AccountType'] = variable_get(SAGEPAY_SETTING_ACCOUNT_TYPE, 'E');
break;
case SAGEPAY_DIRECT:
$query['TxType'] = $tx_type;
$query['CardHolder'] = $billing_address['first_name'] . ' ' . $billing_address['last_name'];
$query['CardNumber'] = $pane_values['credit_card']['number'];
$query['ExpiryDate'] = $pane_values['credit_card']['exp_month'] . substr($pane_values['credit_card']['exp_year'], 2, 2);
// Check if a start date was entered as we have removed the required
// field flag.
if ($pane_values['credit_card']['start_month'] != '0' && $pane_values['credit_card']['start_year'] != '0') {
$query['StartDate'] = $pane_values['credit_card']['start_month'] . substr($pane_values['credit_card']['start_year'], 2, 2);
}
$issue_number = isset($query['IssueNumber']) ? $query['IssueNumber'] : '';
if ($issue_number != '') {
$query['IssueNumber'] = str_pad($pane_values['credit_card']['issue'], 2, '0', STR_PAD_LEFT);
}
$query['CV2'] = $pane_values['credit_card']['code'];
$query['CardType'] = $pane_values['credit_card']['type'];
// Add 3D Secure flag only if the 3d Secure module is enabled for DIRECT.
if (module_exists('sagepay_3d_secure')) {
$query['Apply3DSecure'] = variable_get(SAGEPAY_SETTING_APPLY_3D_SECURE);
}
else {
$query['Apply3DSecure'] = 2;
}
$query['AccountType'] = variable_get(SAGEPAY_SETTING_ACCOUNT_TYPE, 'E');
break;
case SAGEPAY_PAYPAL:
$query['TxType'] = $tx_type;
$query['CardType'] = 'PAYPAL';
$query['PayPalCallbackURL'] = url('commerce-sagepay/paypal-callback/' . $order->order_id, array(
'absolute' => TRUE,
));
break;
case SAGEPAY_TOKEN:
$query['TxType'] = $tx_type;
$query['Token'] = $saved_card_data->remote_id;
$query['StoreToken'] = 1;
// Disable CV2 check as we do not store this data in Card on File.
$query['ApplyAVSCV2'] = 2;
}
switch ($cart_setting) {
case 1:
$query['Basket'] = $encoded_cart;
break;
case 2:
$query['BasketXML'] = $encoded_cart;
break;
}
// Add check for state for US addresses only.
if ($billing_address['country'] == 'US') {
$query['BillingState'] = $billing_address['administrative_area'];
$query['DeliveryState'] = $billing_address['administrative_area'];
}
// Override with supplied delivery address if we have one.
if (isset($delivery_address)) {
$query['DeliverySurname'] = substr($delivery_address['last_name'], 0, 20);
$query['DeliveryFirstnames'] = substr($delivery_address['first_name'], 0, 20);
$query['DeliveryAddress1'] = $delivery_address['thoroughfare'];
$query['DeliveryAddress2'] = $delivery_address['premise'];
$query['DeliveryCity'] = $delivery_address['locality'];
$query['DeliveryPostcode'] = _commerce_sagepay_ensure_postal_code($delivery_address);
$query['DeliveryCountry'] = $delivery_address['country'];
if ($delivery_address['country'] == 'US' && $delivery_address['administrative_area']) {
$query['DeliveryState'] = $delivery_address['administrative_area'];
}
}
// Call hook to allow other modules to modify order data before it is.
$query['integration_method'] = $integration_method;
drupal_alter('sagepay_order_data', $query, $order);
// Invoke rules events which allow manipulation of the order data array.
rules_invoke_all('commerce_sagepay_prepare_transaction', $query, $order);
// Check if rules have added any overrides and merge these into the
// transaction.
if (isset($order->data['sagepay_overrides'])) {
$query = array_merge($query, $order->data['sagepay_overrides']);
}
unset($query['integration_method']);
// For Server mode, we can return the Array now before encryption.
if (in_array($integration_method, array(
SAGEPAY_SERVER,
SAGEPAY_DIRECT,
SAGEPAY_TOKEN,
SAGEPAY_PAYPAL,
))) {
return $query;
}
$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);
// Encrypt order details using base64 and the secret key from the settings.
switch (variable_get(SAGEPAY_SETTING_TRANSACTION_MODE)) {
case SAGEPAY_TXN_MODE_LIVE:
$encoded_string = _commerce_sagepay_encrypt_and_encode($query_string, variable_get(SAGEPAY_SETTING_ENCRYPTION_KEY));
break;
default:
$encoded_string = _commerce_sagepay_encrypt_and_encode($query_string, variable_get(SAGEPAY_SETTING_TEST_ENCRYPTION_KEY));
}
return $encoded_string;
}
/*
* Helper function to ensure that there is a valid postal code
*
* @param array $address
* The address to extract the postal code from
*
* @param string $default
* The default value to use when not found or empty
*
* @return string
*/
function _commerce_sagepay_ensure_postal_code($address, $key = 'postal_code', $default = '0000') {
$value = isset($address[$key]) ? trim($address[$key]) : $default;
if (empty($value)) {
$value = $default;
}
return $value;
}
/**
* Helper function for SagePay Form.
*
* Converts a string of tokens into an array.
*
* @param string $tokenizedstring
* The string to be converted.
*
* @return array
* An array of tokens.
*/
function _commerce_sagepay_form_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',
'FraudResponse',
'Surcharge',
'BankAuthCode',
'DeclineCode',
);
// 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] = new stdClass();
$result[$i]->start = $start;
$result[$i]->token = $tokens[$i];
}
}
// 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.
$value_start = $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, $value_start);
}
else {
$value_length = $result[$i + 1]->start - $result[$i]->start - strlen($result[$i]->token) - 2;
$output[$result[$i]->token] = substr($tokenizedstring, $value_start, $value_length);
}
}
// Return the ouput array.
return $output;
}
/**
* Send a POST request to SagePay and return the response as an array.
*
* @param string $url
* The url to POST to.
* @param array $data
* The data to post.
*
* @return array
* The response from Sage Pay.
*/
function _commerce_sagepay_request_post($url, $data) {
set_time_limit(60);
$output = array();
$curl_session = curl_init();
curl_setopt($curl_session, CURLOPT_URL, $url);
curl_setopt($curl_session, CURLOPT_HEADER, 0);
curl_setopt($curl_session, CURLOPT_POST, 1);
curl_setopt($curl_session, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl_session, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl_session, CURLOPT_TIMEOUT, 30);
curl_setopt($curl_session, CURLOPT_SSL_VERIFYHOST, 2);
/* Support for peer verification - as per https://drupal.org/node/1931760#comment-7365072
If you get an error related to peer verification, you may need to download a CA certificate
bundle file and place it in a safe location on your web server, and update your settings.php to set the
commerce_sagepay_cacert variable to contain the absolute path of the file.
Alternately, you may be able to update your php.ini to point to the file with the curl.cainfo setting.
*/
$ca_cert_path = variable_get('commerce_sagepay_cacert', FALSE);
if (!empty($ca_cert_path)) {
curl_setopt($curl_session, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($curl_session, CURLOPT_CAINFO, $ca_cert_path);
}
else {
curl_setopt($curl_session, CURLOPT_SSL_VERIFYPEER, 0);
}
$rawresponse = curl_exec($curl_session);
$response = explode(chr(10), $rawresponse);
if (curl_error($curl_session)) {
$output['Status'] = "FAIL";
$output['StatusDetail'] = curl_error($curl_session);
}
curl_close($curl_session);
for ($i = 0; $i < count($response); $i++) {
$split_at = strpos($response[$i], "=");
$output[trim(substr($response[$i], 0, $split_at))] = trim(substr($response[$i], $split_at + 1));
}
return $output;
}
function commerce_sagepay_lookup_sagepay_card_type($card_type) {
switch ($card_type) {
case 'mastercard':
return 'MC';
case 'visaelectron':
return 'UKE';
default:
return $card_type;
}
}
Functions
Name![]() |
Description |
---|---|
commerce_sagepay_lookup_sagepay_card_type | |
commerce_sagepay_order_form | Sets up a new form for the submit to Sage Pay button (off site redirect). |
commerce_sagepay_process_response | Helper function to process the response from SagePay of any transaction type. |
commerce_sagepay_transaction | Create a Transaction and associate it with the order. |
_commerce_sagepay_encrypted_order | Encrypt the order details ready to send to SagePay Server. |
_commerce_sagepay_ensure_postal_code | |
_commerce_sagepay_form_get_tokens | Helper function for SagePay Form. |
_commerce_sagepay_request_post | Send a POST request to SagePay and return the response as an array. |
_commerce_sagepay_vendor_tx_code | |
_commerce_sagepay_vendor_tx_code_to_order_id |