uc_cybersource.module in Ubercart 5
Same filename and directory in other branches
A module used for CyberSource's Silent Order POST method of payment.
Developed by Ryan of Ubercart.org.
Development sponsored by Acquia - http://acquia.com
File
payment/uc_cybersource/uc_cybersource.moduleView source
<?php
/**
* @file
* A module used for CyberSource's Silent Order POST method of payment.
*
* Developed by Ryan of Ubercart.org.
*
* Development sponsored by Acquia - http://acquia.com
*/
/**
* Implementation of hook_menu().
*/
function uc_cybersource_menu($may_cache) {
if ($may_cache) {
$items[] = array(
'path' => 'cs/receipt',
'title' => t('Payment received'),
'callback' => 'uc_cybersource_receipt',
'access' => TRUE,
'type' => MENU_CALLBACK,
);
}
else {
$items[] = array(
'path' => 'admin/store/orders/' . arg(3) . '/cs_tax',
'title' => t('Order @order_id Taxes', array(
'@order_id' => arg(3),
)),
'callback' => 'uc_cybersource_tax_test',
'callback arguments' => array(
arg(3),
),
'access' => user_access('administer store'),
'type' => MENU_CALLBACK,
);
}
return $items;
}
/**
* Implementation of hook_form_alter().
*/
function uc_cybersource_form_alter($form_id, &$form) {
if ($form_id == 'uc_payment_gateways_form') {
$form['#submit']['uc_cybersource_payment_gateway_settings_submit'] = array();
}
}
// Submit handler for payment gateway settings form to encrypt fields.
function uc_cybersource_payment_gateway_settings_submit($form_id, $form_values) {
// If CC encryption has been configured properly.
if ($key = uc_credit_encryption_key()) {
// Setup our encryption object.
$crypt = new uc_encryption_class();
// Encrypt the Merchant ID and Transaction key.
if (!empty($form_values['uc_cybersource_soap_merchant_id'])) {
variable_set('uc_cybersource_soap_merchant_id', $crypt
->encrypt($key, $form_values['uc_cybersource_soap_merchant_id']));
}
if (!empty($form_values['uc_cybersource_soap_transaction_key'])) {
variable_set('uc_cybersource_soap_transaction_key', $crypt
->encrypt($key, $form_values['uc_cybersource_soap_transaction_key']));
}
// Store any errors.
uc_store_encryption_errors($crypt, 'uc_cybersource');
}
}
/*******************************************************************************
* Hook Functions (Ubercart)
******************************************************************************/
function uc_cybersource_payment_gateway() {
$gateways[] = array(
'id' => 'cybersource',
'title' => t('CyberSource'),
'description' => t('Process credit card payments using the Silent Order POST service of CyberSource.'),
'settings' => 'uc_cybersource_settings_form',
'credit' => 'uc_cybersource_charge',
'credit_txn_types' => array(
UC_CREDIT_AUTH_ONLY,
UC_CREDIT_PRIOR_AUTH_CAPTURE,
UC_CREDIT_AUTH_CAPTURE,
UC_CREDIT_REFERENCE_TXN,
),
);
return $gateways;
}
/*******************************************************************************
* Callback Functions, Forms, and Tables
******************************************************************************/
function uc_cybersource_receipt() {
drupal_goto('<front>');
}
function uc_cybersource_settings_form() {
if (variable_get('uc_cybersource_method', 'post') == 'post' && !file_exists(drupal_get_path('module', 'uc_cybersource') . '/HOP.php')) {
drupal_set_message(t('You must download the security script from your CyberSource account (found in Tools & Settings > Hosted Order Page > Security) and place it in the ubercart/payment/uc_cybersource directory to use the Silent Order POST.'), 'error');
}
$form['uc_cybersource_server'] = array(
'#type' => 'select',
'#title' => t('Payment server'),
'#description' => t('CyberSource server used when processing payments.'),
'#options' => array(
'production' => t('Production'),
'test' => t('Test'),
),
'#default_value' => variable_get('uc_cybersource_server', 'test'),
);
$form['uc_cybersource_method'] = array(
'#type' => 'radios',
'#title' => t('Payment method'),
'#description' => t('You must ensure your CyberSource account and web server are able to use the service you select.<br />Silent Order POST requires cURL support and a modified <a href="!url">HOP.php</a>.<br />The SOAP Toolkit API requires the SOAP and DOM extensions for PHP.', array(
'!url' => url('http://www.ubercart.org/contrib/139', NULL, NULL, TRUE),
)),
'#options' => array(
'post' => t('Silent Order POST'),
// 'api' => t('Simple Order API'),
'soap' => t('SOAP Toolkit API'),
),
'#default_value' => variable_get('uc_cybersource_method', 'post'),
);
$form['uc_cybersource_avs'] = array(
'#type' => 'radios',
'#title' => t('Ensure address verification'),
'#options' => array(
'true' => t('Process transaction only if address passes verification.'),
'false' => t('Process transaction regardless of the result of address verification.'),
),
'#default_value' => variable_get('uc_cybersource_avs', 'true'),
);
$login = _uc_cybersource_soap_login_data();
$form['soap'] = array(
'#type' => 'fieldset',
'#title' => t('SOAP Toolkit API settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['soap']['uc_cybersource_soap_merchant_id'] = array(
'#type' => 'textfield',
'#title' => t('Merchant ID'),
'#default_value' => $login['merchant_id'],
);
$form['soap']['uc_cybersource_soap_transaction_key'] = array(
'#type' => 'textarea',
'#title' => t('Transaction key'),
'#default_value' => $login['transaction_key'],
);
$form['soap']['uc_cybersource_soap_create_profile'] = array(
'#type' => 'checkbox',
'#title' => t('Create a CyberSource Basic Profile for every new credit card order processed.'),
'#default_value' => variable_get('uc_cybersource_soap_create_profile', FALSE),
);
$form['soap']['uc_cybersource_soap_tax_calculate'] = array(
'#type' => 'checkbox',
'#title' => t('Enable calculation of taxes through the CyberSource tax service.'),
'#default_value' => variable_get('uc_cybersource_soap_tax_calculate', FALSE),
);
$form['soap']['ship_from'] = array(
'#type' => 'fieldset',
'#title' => t('Tax calculation "Ship from" address'),
'#description' => t('This address will be used when calculating taxes with CyberSource tax service.'),
);
$form['soap']['ship_from']['cs_ship_from_first_name'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#default_value' => variable_get('cs_ship_from_first_name', ''),
);
$form['soap']['ship_from']['cs_ship_from_last_name'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => variable_get('cs_ship_from_last_name', ''),
);
$form['soap']['ship_from']['cs_ship_from_street1'] = array(
'#type' => 'textfield',
'#title' => t('Street address'),
'#default_value' => variable_get('cs_ship_from_street1', ''),
);
$form['soap']['ship_from']['cs_ship_from_city'] = array(
'#type' => 'textfield',
'#title' => t('City'),
'#default_value' => variable_get('cs_ship_from_city', ''),
);
$form['soap']['ship_from']['cs_ship_from_zone'] = array(
'#type' => 'textfield',
'#title' => t('State/Province'),
'#description' => t('Enter the 2 letter abbreviation of your state or province.'),
'#default_value' => variable_get('cs_ship_from_zone', ''),
'#maxlength' => 2,
);
$form['soap']['ship_from']['cs_ship_from_postal_code'] = array(
'#type' => 'textfield',
'#title' => t('ZIP/Postal code'),
'#default_value' => variable_get('cs_ship_from_postal_code', ''),
);
$form['soap']['ship_from']['cs_ship_from_country'] = array(
'#type' => 'textfield',
'#title' => t('Country code'),
'#description' => t("Enter the 2 letter ISO 3166-1 code; consult Wikipedia if you don't know yours."),
'#default_value' => variable_get('cs_ship_from_country', ''),
'#maxlength' => 2,
);
$form['soap']['ship_from']['cs_ship_from_email'] = array(
'#type' => 'textfield',
'#title' => t('E-mail address'),
'#default_value' => variable_get('cs_ship_from_email', ''),
);
return $form;
}
function uc_cybersource_charge($order_id, $amount, $data) {
global $user;
$order = uc_order_load($order_id);
$amount = uc_currency_format($amount, FALSE, FALSE, '.');
$cc_type = NULL;
if (isset($order->payment_details['cc_type'])) {
switch (strtolower($order->payment_details['cc_type'])) {
case 'amex':
case 'american express':
$cc_type = '003';
break;
case 'visa':
$cc_type = '001';
break;
case 'mastercard':
case 'master card':
$cc_type = '002';
break;
case 'discover':
$cc_type = '004';
break;
}
}
if (is_null($cc_type)) {
$cc_type = _uc_cybersource_card_type($order->payment_details['cc_number']);
if ($cc_type === FALSE && in_array($data['txn_type'], array(
UC_CREDIT_AUTH_ONLY,
UC_CREDIT_AUTH_CAPTURE,
))) {
drupal_set_message(t('The credit card type did not pass validation.'), 'error');
watchdog('uc_cybersource', t('Could not figure out cc type: @number / @type', array(
'@number' => $order->payment_details['cc_number'],
'@type' => $order->payment_details['cc_type'],
)), WATCHDOG_ERROR);
return array(
'success' => FALSE,
);
}
}
$country = uc_get_country_data(array(
'country_id' => $order->billing_country,
));
if ($country === FALSE) {
$country = array(
0 => array(
'country_iso_code_2' => 'US',
),
);
}
// Process the charge differently depending on the CyberSource method.
switch (variable_get('uc_cybersource_method', 'post')) {
// Support for the Silent Order POST.
case 'post':
return _uc_cybersource_post_charge($order, $amount, $data, $cc_type, $country);
// Support for the SOAP Toolkit API.
case 'soap':
// TODO: Refactor to use separate function for each API type.
// - i.e. _uc_cybersource_charge_request_soap($order, $amount, $data);
// require_once(drupal_get_path('module', 'uc_cybersource') .'/SOAP.php');
return _uc_cybersource_soap_charge($order, $amount, $data, $cc_type, $country);
case 'api':
$config = cybs_load_config('cybs.ini');
if (variable_get('uc_cybersource_server', 'test') == 'test') {
$config['sendToProduction'] = 'false';
}
$request['ccAuthService_run'] = 'true';
if (variable_get('uc_cybersource_transaction_type', 'sale') == 'sale') {
$request['ccCaptureService_run'] = 'true';
}
$request['merchantReferenceCode'] = $order_id;
$request['purchaseTotals_currency'] = 'USD';
$request['purchaseTotals_grandTotalAmount'] = $amount;
drupal_set_message('<pre>' . print_r($config, TRUE) . '</pre>');
drupal_set_message('<pre>' . print_r($request, TRUE) . '</pre>');
break;
}
}
function _uc_cybersource_post_charge($order, $amount, $data, $cc_type, $country) {
// Check for cURL support.
if (!function_exists('curl_init')) {
drupal_set_message(t('Cybersource requires cURL. Please talk to your system administrator to get this configured.'));
return array(
'success' => FALSE,
);
}
// Include the HOP.php per the module instructions.
$hop = drupal_get_path('module', 'uc_cybersource') . '/HOP.php';
if (!file_exists($hop)) {
drupal_set_message(t('Silent Order POST requires the HOP.php provided by CyberSource.'));
return array(
'success' => FALSE,
);
}
require_once $hop;
$request = array(
'billTo_firstName' => $order->billing_first_name,
'billTo_lastName' => $order->billing_last_name,
'billTo_street1' => $order->billing_street1,
'billTo_city' => $order->billing_city,
'billTo_country' => $country[0]['country_iso_code_2'],
'billTo_state' => uc_get_zone_code($order->billing_zone),
'billTo_postalCode' => $order->billing_postal_code,
'billTo_email' => $order->primary_email,
'card_accountNumber' => $order->payment_details['cc_number'],
'card_cardType' => $cc_type,
'card_expirationMonth' => $order->payment_details['cc_exp_month'],
'card_expirationYear' => $order->payment_details['cc_exp_year'],
);
if (variable_get('uc_credit_cvv_enabled', TRUE)) {
$request['card_cvNumber'] = $order->payment_details['cc_cvv'];
}
$currency = variable_get('uc_cybersource_currency', 'usd');
$merchantID = getMerchantID();
$timestamp = getmicrotime();
$data = $merchantID . $amount . $currency . $timestamp;
$pub = getPublicKey();
$serialNumber = getSerialNumber();
$pub_digest = hopHash($data, $pub);
$request['amount'] = $amount;
$request['currency'] = $currency;
$request['merchantID'] = $merchantID;
$request['orderNumber'] = $order_id;
$request['orderPage_timestamp'] = $timestamp;
$request['orderPage_ignoreAVS'] = variable_get('uc_cybersource_avs', 'true') == 'true' ? 'false' : 'true';
$request['orderPage_signaturePublic'] = $pub_digest;
$request['orderPage_version'] = '4';
$request['orderPage_serialNumber'] = $serialNumber;
$request['orderPage_transactionType'] = variable_get('uc_cybersource_transaction_type', 'sale');
$data = '';
while (list($key, $value) = each($request)) {
$data .= $key . '=' . urlencode(ereg_replace(',', '', $value)) . '&';
}
$data = substr($data, 0, -1);
if (variable_get('uc_cybersource_server', 'test') == 'test') {
$url = 'https://orderpagetest.ic3.com/hop/ProcessOrder.do';
}
else {
$url = 'https://orderpage.ic3.com/hop/ProcessOrder.do';
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_VERBOSE, 0);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
$response = curl_exec($ch);
if ($error = curl_error($ch)) {
watchdog('uc_cybersource', $error, WATCHDOG_ERROR);
}
curl_close($ch);
if (preg_match_all('`name=".+" value=".+"`', $response, $pairs) > 0) {
for ($i = 0; $i < count($pairs[0]); $i++) {
list($name, $value) = explode('" value="', substr($pairs[0][$i], 6, strlen($pairs[0][$i]) - 7));
$nvp[$name] = $value;
}
// Create the order and payment ledger comments.
$o_comment = t('<b>Credit card !type:</b> !amount<br /><b>Decision: @decision</b><br /><b>Reason:</b> !reason', array(
'!type' => variable_get('uc_cybersource_transaction_type', 'sale'),
'!amount' => uc_currency_format($nvp['orderAmount']),
'@decision' => $nvp['decision'],
'!reason' => _parse_cs_reason_code($nvp['reasonCode']),
));
$p_comment = t('!id<br />!decision, Reason: !reason', array(
'!id' => $nvp['orderPage_serialNumber'],
'!decision' => $nvp['decision'],
'!reason' => $nvp['reasonCode'],
));
if (!empty($nvp['ccAuthReply_avsCode'])) {
$o_comment .= t('<br /><b>AVS:</b> !avs', array(
'!avs' => _parse_cs_avs_code($nvp['ccAuthReply_avsCode']),
));
$p_comment .= t(', AVS: @avs', array(
'@avs' => $nvp['ccAuthReply_avsCode'],
));
}
if (!empty($nvp['ccAuthReply_cvCode'])) {
$o_comment .= t('<br /><b>CVV:</b> !cvv', array(
'!cvv' => _parse_cs_cvv_code($nvp['ccAuthReply_cvCode']),
));
$p_comment .= t(', CVV: @cvv', array(
'@cvv' => $nvp['ccAuthReply_cvCode'],
));
}
uc_order_comment_save($order_id, $user->uid, $o_comment, 'admin');
if ($nvp['decision'] == 'ACCEPT') {
$result = array(
'success' => TRUE,
'comment' => $p_comment,
'message' => $o_comment,
'uid' => $user->uid,
);
}
else {
$result = array(
'success' => FALSE,
'comment' => $p_comment,
'message' => $o_comment,
'uid' => $user->uid,
);
}
}
else {
$result = array(
'success' => FALSE,
'message' => t('No response returned from CyberSource.'),
);
}
return $result;
}
// Handles the SOAP charge request and Ubercart order save.
function _uc_cybersource_soap_charge($order, $amount, $data, $cc_type, $country) {
// Check for compatibility.
if (!class_exists('SoapClient') || !class_exists('DOMDocument')) {
drupal_set_message(t('CyberSource needs PHP to have the SOAP and DOM extensions enabled. Please talk to your system administrator to get this configured.'));
return array(
'success' => FALSE,
);
}
// Include the SOAP helper file.
require_once drupal_get_path('module', 'uc_cybersource') . '/uc_cybersource.soap.inc';
global $user;
// Set the URL for the CyberSource SOAP Toolkit API WSDL.
if (variable_get('uc_cybersource_server', 'test') == 'test') {
$url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
}
else {
$url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
}
// Variable currency... not used atm.
$currency = variable_get('uc_cybersource_currency', 'usd');
$billing_country = uc_get_country_data(array(
'country_id' => $order->billing_country,
));
$delivery_country = uc_get_country_data(array(
'country_id' => $order->delivery_country,
));
try {
$soapClient = new CyberSourceSoapClient($url, array());
// To see the functions and types that the SOAP extension can automatically
// generate from the WSDL file, uncomment this section and check the logs.
// $functions = $soapClient->__getFunctions();
// watchdog('uc_cybersource', '<pre>'. print_r($functions, TRUE) .'</pre>');
// $types = $soapClient->__getTypes();
// watchdog('uc_cybersource', '<pre>'. print_r($types, TRUE) .'</pre>');
$login = _uc_cybersource_soap_login_data();
// Create the request with some meta data.
$request = new stdClass();
$request->merchantID = $login['merchant_id'];
$request->merchantReferenceCode = $order->order_id;
$request->clientLibrary = 'PHP';
$request->clientLibraryVersion = phpversion();
$request->clientEnvironment = php_uname();
// Add the credit card authorization service.
if (in_array($data['txn_type'], array(
UC_CREDIT_AUTH_ONLY,
UC_CREDIT_AUTH_CAPTURE,
UC_CREDIT_REFERENCE_TXN,
))) {
$ccAuthService = new stdClass();
$ccAuthService->run = 'true';
$request->ccAuthService = $ccAuthService;
}
// Add the credit card capture service.
if (in_array($data['txn_type'], array(
UC_CREDIT_PRIOR_AUTH_CAPTURE,
UC_CREDIT_AUTH_CAPTURE,
UC_CREDIT_REFERENCE_TXN,
))) {
$ccCaptureService = new stdClass();
$ccCaptureService->run = 'true';
// Add the values for prior authorization capture.
if ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
$ccCaptureService->authRequestID = $data['auth_id'];
$ccCaptureService->authRequestToken = $order->data['cybersource'][$data['auth_id']];
}
$request->ccCaptureService = $ccCaptureService;
// Add the subscription ID for a reference transaction.
if ($data['txn_type'] == UC_CREDIT_REFERENCE_TXN) {
$recurringSubscriptionInfo = new stdClass();
$recurringSubscriptionInfo->subscriptionID = $data['ref_id'];
$request->recurringSubscriptionInfo = $recurringSubscriptionInfo;
$request->merchantReferenceCode .= ' (COF)';
}
}
// If enabled, create a subscription profile for this transaction.
if (variable_get('uc_cybersource_soap_create_profile', FALSE) && in_array($data['txn_type'], array(
UC_CREDIT_AUTH_ONLY,
UC_CREDIT_AUTH_CAPTURE,
))) {
// Skip if a profile already exists for this order.
if (!isset($order->data['uc_cybersource']['soap']['subscription_id'])) {
$recurringSubscriptionInfo = new stdClass();
$recurringSubscriptionInfo->amount = 0;
$recurringSubscriptionInfo->frequency = 'on-demand';
$request->recurringSubscriptionInfo = $recurringSubscriptionInfo;
$paySubscriptionCreateService = new stdClass();
$paySubscriptionCreateService->run = 'true';
$request->paySubscriptionCreateService = $paySubscriptionCreateService;
}
}
// Add the billing information.
$billTo = new stdClass();
$billTo->firstName = $order->billing_first_name;
$billTo->lastName = $order->billing_last_name;
$billTo->street1 = $order->billing_street1;
if ($order->billing_street2) {
$billTo->street2 = $order->billing_street2;
}
$billTo->city = $order->billing_city;
$billTo->state = uc_get_zone_code($order->billing_zone);
$billTo->postalCode = $order->billing_postal_code;
$billTo->country = $billing_country[0]['country_iso_code_2'];
if ($order->billing_phone) {
$billTo->phoneNumber = $order->billing_phone;
}
$billTo->email = $order->primary_email;
$billTo->customerID = $order->uid;
$request->billTo = $billTo;
// Add the credit card details if needed
if (in_array($data['txn_type'], array(
UC_CREDIT_AUTH_ONLY,
UC_CREDIT_AUTH_CAPTURE,
))) {
$card = new stdClass();
$card->accountNumber = $order->payment_details['cc_number'];
$card->expirationMonth = $order->payment_details['cc_exp_month'];
$card->expirationYear = $order->payment_details['cc_exp_year'];
$card->cardType = $cc_type;
if (variable_get('uc_credit_cvv_enabled', TRUE)) {
$card->cvNumber = $order->payment_details['cc_cvv'];
}
$request->card = $card;
}
// Add the order total information.
$purchaseTotals = new stdClass();
$purchaseTotals->currency = $currency;
// Specify the total to charge if it's less than the order total.
if ($amount < $order->order_total) {
$purchaseTotals->grandTotalAmount = $amount;
}
$request->purchaseTotals = $purchaseTotals;
// Separately add products and line item into the request items object if
// we're charging the full order total.
if ($amount == $order->order_total) {
$request->item = array();
$counter = 0;
// Add the products to the item array.
foreach ($order->products as $product) {
$obj = $request->item[] = new stdClass();
$obj->productName = $product->title;
$obj->unitPrice = $product->price;
$obj->quantity = $product->qty;
$obj->productSKU = $product->model;
$obj->productCode = 'default';
$obj->id = $counter;
$counter++;
}
// Add the line items to the item array.
foreach ((array) $order->line_items as $line_item) {
// Skip subtotal line items.
if (strpos($line_item['type'], 'subtotal') === FALSE) {
$obj = $request->item[] = new stdClass();
$obj->productName = $line_item['title'];
$obj->unitPrice = $line_item['amount'];
$obj->quantity = 1;
$obj->productSKU = $line_item['type'] . '_' . $line_item['line_item_id'];
$obj->id = $counter;
$counter++;
}
}
}
// Add business rules.
$business = new stdClass();
$business->ignoreAVSResult = variable_get('uc_cybersource_avs', 'true') == 'true' ? 'false' : 'true';
$request->businessRules = $business;
// Send the request to CyberSource and get the reply.
$reply = $soapClient
->runTransaction($request);
} catch (SoapFault $exception) {
// Log and display errors if Ubercart is unable to connect via SOAP.
watchdog('uc_cybersource', t('Unable to connect to CyberSource via SOAP.'), WATCHDOG_ERROR);
drupal_set_message(t('We apologize for the delay, but we are unable to process your credit card at this time. Please <a href="!url">contact sales</a> to complete your order.', array(
'!url' => url('contact'),
)), 'error');
}
// Process a reply from CyberSource.
if (isset($reply)) {
$types = uc_credit_transaction_types();
// Create the order and payment ledger comments.
$o_comment = t('<b>@type:</b> @amount<br /><b>Decision: @decision</b><br /><b>Reason:</b> !reason', array(
'@type' => $types[$data['txn_type']],
'@amount' => uc_currency_format($amount),
'@decision' => $reply->decision,
'!reason' => _parse_cs_reason_code($reply->reasonCode),
));
$p_comment = t('<b>@type:</b><br />@id<br />@decision, Reason: !reason', array(
'@type' => $types[$data['txn_type']],
'@id' => $reply->requestID,
'@decision' => $reply->decision,
'!reason' => $reply->reasonCode,
));
if (!empty($reply->ccAuthReply->avsCode)) {
$o_comment .= '<br />' . t('<b>AVS:</b> @avs', array(
'@avs' => _parse_cs_avs_code($reply->ccAuthReply->avsCode),
));
$p_comment .= t(', AVS: @avs', array(
'@avs' => $reply->ccAuthReply->avsCode,
));
}
if (!empty($reply->ccAuthReply->cvCode)) {
$o_comment .= '<br />' . t('<b>CVV:</b> @cvv', array(
'!cvv' => _parse_cs_cvv_code($reply->ccAuthReply->cvCode),
));
$p_comment .= t(', CVV: @cvv', array(
'@cvv' => $reply->ccAuthReply->cvCode,
));
}
uc_order_comment_save($order->order_id, $user->uid, $o_comment, 'admin');
// Store the subscription ID if one was created.
if (isset($reply->paySubscriptionCreateReply)) {
// If the create request was successful...
if ($reply->paySubscriptionCreateReply->reasonCode == '100') {
$id = $reply->paySubscriptionCreateReply->subscriptionID;
// Save the subscription ID to the order's data array.
$order->data = uc_credit_log_reference($order->order_id, $id, $order->payment_details['cc_number']);
uc_order_comment_save($order->order_id, 0, t('<b>CyberSource profile created.</b><br /><b>Subscription ID:</b> @id', array(
'@id' => $id,
)), 'admin');
}
else {
uc_order_comment_save($order->order_id, 0, t('<b>Attempt to create CyberSource profile failed.</b><br /><b>Reason:</b> @code', array(
'@code' => $reply->paySubscriptionCreateReply->reasonCode,
)), 'admin');
}
}
if ($reply->decision == 'ACCEPT') {
$result = array(
'success' => TRUE,
'comment' => $p_comment,
'message' => $o_comment,
'uid' => $user->uid,
);
// If this was an authorization only transaction...
if ($data['txn_type'] == UC_CREDIT_AUTH_ONLY) {
// Log the authorization to the order.
$order->data = uc_credit_log_authorization($order->order_id, $reply->requestID, $amount);
// Add the request token associated with the request ID.
$order->data['cybersource'][$reply->requestID] = $reply->requestToken;
// Save the updated data array to the database.
db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($order->data), $order->order_id);
}
elseif ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
uc_credit_log_prior_auth_capture($order->order_id, $data['auth_id']);
}
}
else {
$result = array(
'success' => FALSE,
'comment' => $p_comment,
'message' => $o_comment,
'uid' => $user->uid,
);
}
}
else {
$result = array(
'success' => FALSE,
'message' => t('No response returned from CyberSource.'),
);
}
// Don't log this as a payment if money wasn't actually captured.
if (in_array($data['txn_type'], array(
UC_CREDIT_AUTH_ONLY,
))) {
$result['log_payment'] = FALSE;
}
return $result;
}
// Displays the taxes for an order.
function uc_cybersource_tax_test($order_id) {
if ($order = uc_order_load($order_id)) {
// Fetch the taxes for the order.
$data = uc_cybersource_calculate_tax($order);
// Build an item list for the taxes.
$items = array();
foreach ($data as $tax) {
$items[] = t('@tax: @amount', array(
'@tax' => $tax['name'],
'@amount' => uc_currency_format($tax['amount']),
));
}
// Display a message if there are no taxes.
if (empty($items)) {
$items[] = t('No taxes returned for this order.');
}
return theme('item_list', $items);
}
else {
return t('Order not found.');
}
}
/**
* Calculates taxes for an order using CyberSource's tax service.
*
* @param $order
* An order object with address and product information.
* @return
* An array of associative arrays representing tax information with the keys
* 'id', 'name', and 'amount'.
*/
function uc_cybersource_calculate_tax($order) {
// Kick out if the tax service is not enabled.
if (!variable_set('uc_cybersource_soap_tax_calculate', FALSE)) {
return array();
}
// Check for compatibility.
if (!class_exists('SoapClient') || !class_exists('DOMDocument')) {
drupal_set_message(t('CyberSource needs PHP to have the SOAP and DOM extensions enabled. Please talk to your system administrator to get this configured.'));
return array();
}
if (!is_object($order)) {
return array();
}
// Include the SOAP helper file.
require_once drupal_get_path('module', 'uc_cybersource') . '/uc_cybersource.soap.inc';
global $user;
// Set the URL for the CyberSource SOAP Toolkit API WSDL.
if (variable_get('uc_cybersource_server', 'test') == 'test') {
$url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
}
else {
$url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
}
// Variable currency... not used atm.
$currency = variable_get('uc_cybersource_currency', 'usd');
$billing_country = uc_get_country_data(array(
'country_id' => $order->billing_country,
));
$delivery_country = uc_get_country_data(array(
'country_id' => $order->delivery_country,
));
try {
$soapClient = new CyberSourceSoapClient($url, array());
$login = _uc_cybersource_soap_login_data();
// Create the request with some meta data.
$request = new stdClass();
$request->merchantID = $login['merchant_id'];
$request->merchantReferenceCode = $order->order_id;
$request->clientLibrary = 'PHP';
$request->clientLibraryVersion = phpversion();
$request->clientEnvironment = php_uname();
// Add the billing information.
$billTo = new stdClass();
$billTo->firstName = $order->billing_first_name;
$billTo->lastName = $order->billing_last_name;
$billTo->street1 = $order->billing_street1;
if ($order->billing_street2) {
$billTo->street2 = $order->billing_street2;
}
$billTo->city = $order->billing_city;
$billTo->state = uc_get_zone_code($order->billing_zone);
$billTo->postalCode = $order->billing_postal_code;
$billTo->country = $billing_country[0]['country_iso_code_2'];
if ($order->billing_phone) {
$billTo->phoneNumber = $order->billing_phone;
}
$billTo->email = $order->primary_email;
$billTo->customerID = $order->uid;
$request->billTo = $billTo;
// Add the shipping information.
$shipTo = new stdClass();
$shipTo->firstName = $order->delivery_first_name;
$shipTo->lastName = $order->delivery_last_name;
$shipTo->street1 = $order->delivery_street1;
if ($order->billing_street2) {
$shipTo->street2 = $order->delivery_street2;
}
$shipTo->city = $order->delivery_city;
$shipTo->state = uc_get_zone_code($order->delivery_zone);
$shipTo->postalCode = $order->delivery_postal_code;
$shipTo->country = $delivery_country[0]['country_iso_code_2'];
$shipTo->email = $order->primary_email;
$request->shipTo = $shipTo;
// Add the company's ship from information.
$shipFrom = new stdClass();
$shipFrom->firstName = variable_get('cs_ship_from_first_name', '');
$shipFrom->lastName = variable_get('cs_ship_from_last_name', '');
$shipFrom->street1 = variable_get('cs_ship_from_street1', '');
$shipFrom->city = variable_get('cs_ship_from_city', '');
$shipFrom->state = variable_get('cs_ship_from_zone', '');
$shipFrom->postalCode = variable_get('cs_ship_from_postal_code', '');
$shipFrom->country = variable_get('cs_ship_from_country', '');
$shipFrom->email = variable_get('cs_ship_from_email', '');
$request->shipFrom = $shipFrom;
// TaxService
// US product codes:
// 70.280: Software Training Services
// 81112201.121: Business Use Services and Upgrades via Elect Dnld
// TODO: product code, international product code
// TODO: invoiceHeader->invoiceDate: to get correct refund amounts
// TODO: VAT
$taxService = new stdClass();
$taxService->nexus = 'MA CA';
$taxService->orderOriginCity = $taxService->orderAcceptanceCity = $shipFrom->city;
$taxService->orderOriginCountry = $taxService->orderAcceptanceCountry = $shipFrom->country;
$taxService->orderOriginState = $taxService->orderAcceptanceState = $shipFrom->state;
$taxService->orderOriginPostalCode = $taxService->orderAcceptancePostalCode = $shipFrom->postalCode;
$taxService->sellerRegistration = 'XXX TODO';
$taxService->run = 'true';
$request->taxService = $taxService;
// Add the order total information.
$purchaseTotals = new stdClass();
$purchaseTotals->currency = $currency;
// Add the products to the request.
$request->item = array();
$counter = 0;
// Add the products to the item array.
foreach ($order->products as $product) {
$obj = $request->item[] = new stdClass();
$obj->productName = $product->title;
$obj->unitPrice = $product->price;
$obj->quantity = $product->qty;
$obj->productSKU = $product->model;
$obj->productCode = 'default';
$obj->id = $counter;
$counter++;
}
// drupal_set_message('<pre>Request: '. print_r($request, TRUE) .'</pre>');
// Send the request to CyberSource and get the reply.
$reply = $soapClient
->runTransaction($request);
// drupal_set_message('<pre>Reply: '. print_r($reply, TRUE) .'</pre>');
} catch (SoapFault $exception) {
// Log and display errors if Ubercart is unable to connect via SOAP.
watchdog('uc_cybersource', t('Unable to connect to CyberSource via SOAP.'), WATCHDOG_ERROR);
drupal_set_message(t('We apologize for the delay, but we are unable to process your credit card at this time. Please <a href="!url">contact sales</a> to complete your order.', array(
'!url' => url('contact'),
)), 'error');
}
// Process a reply from CyberSource.
if (isset($reply)) {
$result = array();
if ($reply->reasonCode == '100') {
// Add a city tax if applicable.
if (floatval($reply->taxReply->totalCityTaxAmount) > 0) {
$result[] = array(
'id' => 'city',
'name' => t('@city city tax', array(
'@city' => floatval($reply->taxReply->city),
)),
'amount' => floatval($reply->taxReply->totalCityTaxAmount),
);
}
// Add a county tax if applicable.
if (floatval($reply->taxReply->totalCountyTaxAmount) > 0) {
$result[] = array(
'id' => 'county',
'name' => t('County tax'),
'amount' => floatval($reply->taxReply->totalCountryTaxAmount),
);
}
// Add a district tax if applicable.
if (floatval($reply->taxReply->totalDistrictTaxAmount) > 0) {
$result[] = array(
'id' => 'district',
'name' => t('District tax'),
'amount' => floatval($reply->taxReply->totalDistrictTaxAmount),
);
}
// Add a state tax if applicable.
if (floatval($reply->taxReply->totalStateTaxAmount) > 0) {
$result[] = array(
'id' => 'state',
'name' => t('@state state tax', array(
'@state' => $reply->taxReply->state,
)),
'amount' => floatval($reply->taxReply->totalStateTaxAmount),
);
}
// Verify that the component taxes equal the total.
$total = 0;
foreach ($result as $tax) {
$total += $tax['amount'];
}
// If it doesn't, log an error message and simply return the total.
if ($total != floatval($reply->taxReply->totalTaxAmount)) {
watchdog('uc_cybersource', t('Tax calculation produced uneven results. Expected a total of @total, received the following: @dump', array(
'@total' => uc_currency_format($reply->taxReply->totalTaxAmount),
'@dump' => '<pre>' . print_r($result, TRUE) . '</pre>',
)), WATCHDOG_ERROR);
$result = array(
array(
'id' => 'total',
'name' => t('Tax'),
'amount' => floatval($reply->taxReply->totalTaxAmount),
),
);
}
}
else {
watchdog('uc_cybersource', t('Attempted to calculate taxes failed for order @order_id - reason @code', array(
'@order_id' => $order->order_id,
'@code' => $reply->reasonCode,
)), WATCHDOG_ERROR);
}
}
else {
watchdog('uc_cybersource', t('Attempted to calculate taxes failed for order @order_id. No response returned from CyberSource.', array(
'@order_id' => $order->order_id,
)), WATCHDOG_ERROR);
$result = array();
}
return $result;
}
// Returns an array with the SOAP Merchant ID and Transaction key.
function _uc_cybersource_soap_login_data() {
static $data;
if (!empty($data)) {
return $data;
}
$merchant_id = variable_get('uc_cybersource_soap_merchant_id', '');
$transaction_key = variable_get('uc_cybersource_soap_transaction_key', '');
// If CC encryption has been configured properly.
if ($key = uc_credit_encryption_key()) {
// Setup our encryption object.
$crypt = new uc_encryption_class();
// Decrypt the Merchant ID and Transaction key.
if (!empty($merchant_id)) {
$merchant_id = $crypt
->decrypt($key, $merchant_id);
}
if (!empty($transaction_key)) {
$transaction_key = $crypt
->decrypt($key, $transaction_key);
}
// Store any errors.
uc_store_encryption_errors($crypt, 'uc_cybersource');
}
$data = array(
'merchant_id' => $merchant_id,
'transaction_key' => $transaction_key,
);
return $data;
}
// Returns the code for the credit card type.
function _uc_cybersource_card_type($cc_number) {
switch (substr(strval($cc_number), 0, 1)) {
case '3':
if (strlen($cc_number) == 14) {
return '005';
// Diners Club
}
elseif (strlen($cc_number) == 15) {
return '003';
// AmEx
}
else {
return '007';
// JCB
}
case '4':
return '001';
// Visa
case '5':
return '002';
// MasterCard
case '6':
return '004';
}
return FALSE;
}
// Returns the meaning of the reason code given by CyberSource.
function _parse_cs_reason_code($code) {
switch ($code) {
case '100':
return t('Successful transaction.');
case '102':
return t('One or more fields in the request are missing or invalid.<br /><b>Possible action:</b> Resend the request with the correct information.');
case '150':
return t('<b>Error:</b> General system failure.<br /><b>Possible action:</b> Wait a few minutes and resend the request.');
case '151':
return t('<b>Error:</b> The request was received, but a server time-out occurred. This error does not include time-outs between the client and the server.<br /><b>Possible action:</b> To avoid duplicating the order, do not resend the request until you have reviewed the order status in the Business Center.');
case '152':
return t('<b>Error:</b> The request was received, but a service did not finish running in time.<br /><b>Possible action:</b> To avoid duplicating the order, do not resend the request until you have reviewed the order status in the Business Center.');
case '200':
return t('The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the Address Verification Service (AVS) check.<br /><b>Possible action:</b> You can capture the authorization, but consider reviewing the order for the possibility of fraud.');
case '202':
return t('Expired card.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '203':
return t('General decline of the card. No other information provided by the issuing bank.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '204':
return t('Insufficient funds in the account.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '205':
return t("Stolen or lost card.<br /><b>Possible action:</b> Review the customer's information and determine if you want to request a different card from the customer.");
case '207':
return t('Issuing bank unavailable.<br /><b>Possible action:</b> Wait a few minutes and resend the request.');
case '208':
return t('Inactive card or card not authorized for card-not-present transactions.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '210':
return t('The card has reached the credit limit.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '211':
return t('The card verification number is invalid.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '220':
return t("The processor declined the request based on a general issue with the customer's account.<br /><b>Possible action:</b> Request a different form of payment.");
case '221':
return t('The customer matched an entry on the processor’s negative file.<br /><b>Possible action:</b> Review the order and contact the payment processor.');
case '222':
return t("The customer's bank account is frozen.<br /><b>Possible action:</b> Review the order or request a different form of payment.");
case '230':
return t('The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification number check.<br /><b>Possible action:</b> You can capture the authorization, but consider reviewing the order for the possibility of fraud.');
case '231':
return t('Invalid account number.<br /><b>Possible action:</b> Request a different card or other form of payment.');
case '232':
return t('The card type is not accepted by the payment processor.<br /><b>Possible action:</b> Request a different card or other form of payment. Also, check with CyberSource Customer Support to make sure that your account is configured correctly.');
case '233':
return t('The processor declined the request based on an issue with the request itself.<br /><b>Possible action:</b> Request a different form of payment.');
case '234':
return t('There is a problem with your CyberSource merchant configuration.<br /><b>Possible action:</b> Do not resend the request. Contact Customer Support to correct the configuration problem.');
case '236':
return t('Processor failure.<br /><b>Possible action:</b> Possible action: Wait a few minutes and resend the request.');
case '240':
return t('The card type sent is invalid or does not correlate with the credit card number.<br /><b>Possible action:</b> Ask your customer to verify that the card is really the type indicated in your Web store, then resend the request.');
case '250':
return t('<b>Error:</b> The request was received, but a time-out occurred with the payment processor.<br /><b>Possible action:</b> To avoid duplicating the transaction, do not resend the request until you have reviewed the transaction status in the Business Center.');
case '475':
return t('The customer is enrolled in payer authentication.<br /><b>Possible action:</b> Authenticate the cardholder before continuing with the transaction.');
case '476':
return t("The customer cannot be authenticated.<br /><b>Possible action:</b> Review the customer's order.");
case '520':
return t('The authorization request was approved by the issuing bank but declined by CyberSource based on your Smart Authorization settings.<br /><b>Possible action:</b> Do not capture the authorization without further review. Review the avsCode, cvResult, and factorCode fields to determine why CyberSource rejected the request.');
}
}
// Returns the meaning of the code for Address Verification.
function _parse_cs_avs_code($code) {
switch ($code) {
case 'A':
return t('Street address matches, but 5- and 9-digit postal codes do not match.');
case 'B':
return t('Street address matches, but postal code not verified. Returned only for non U.S.-issued Visa cards.');
case 'C':
return t('Street address and postal code do not match. Returned only for non U.S.-issued Visa cards.');
case 'D':
return t('Street address and postal code match. Returned only for non U.S.-issued Visa cards.');
case 'E':
return t('AVS data is invalid, or AVS is not allowed for this card type.');
case 'F':
return t("Card member's name does not match, but postal code matches. Returned only for the American Express card type.");
case 'G':
return t('Non-U.S. issuing bank does not support AVS.');
case 'H':
return t("Card member's name does not match. Street address and postal code match. Returned only for the American Express card type.");
case 'I':
return t('Address not verified. Returned only for non U.S.-issued Visa cards.');
case 'K':
return t("Card member's name matches but billing address and billing postal code do not match. Returned only for the American Express card type.");
case 'L':
return t("Card member's name and billing postal code match, but billing address does not match. Returned only for the American Express card type");
case 'N':
return t("Street address and postal code do not match. - or - Card member's name, street address and postal code do not match. Returned only for the American Express card type.");
case 'O':
return t("Card member's name and billing address match, but billing postal code does not match. Returned only for the American Express card type.");
case 'P':
return t('Postal code matches, but street address not verified. Returned only for non-U.S.-issued Visa cards.');
case 'R':
return t('System unavailable.');
case 'S':
return t('U.S.-issuing bank does not support AVS.');
case 'T':
return t("Card member's name does not match, but street address matches. Returned only for the American Express card type.");
case 'U':
return t('Address information unavailable. Returned if non-U.S. AVS is not available or if the AVS in a U.S. bank is not functioning properly.');
case 'W':
return t('Street address does not match, but 9-digit postal code matches.');
case 'X':
return t('Exact match. Street address and 9-digit postal code match.');
case 'Y':
return t('Exact match. Street address and 5-digit postal code match.');
case 'Z':
return t('Street address does not match, but 5-digit postal code matches.');
case '1':
return t('AVS is not supported for this processor or card type.');
case '2':
return t('The processor returned an unrecognized value for the AVS response.');
}
}
// Returns the meaning of the code sent back for CVV verification.
function _parse_cs_cvv_code($code) {
switch ($code) {
case 'D':
return t('Transaction determined suspicious by issuing bank.');
case 'I':
return t("Card verification number failed processor's data validation check.");
case 'M':
return t('Card verification number matched.');
case 'N':
return t('Card verification number not matched.');
case 'P':
return t('Card verification number not processed by processor for unspecified reason.');
case 'S':
return t('Card verification number is on the card but was not included in the request.');
case 'U':
return t('Card verification is not supported by the issuing bank.');
case 'X':
return t('Card verification is not supported by the card association.');
case '1':
return t('Card verification is not supported for this processor or card type.');
case '2':
return t('Unrecognized result code returned by processor for card verification response.');
case '3':
return t('No result code returned by processor.');
}
}
Functions
Name | Description |
---|---|
uc_cybersource_calculate_tax | Calculates taxes for an order using CyberSource's tax service. |
uc_cybersource_charge | |
uc_cybersource_form_alter | Implementation of hook_form_alter(). |
uc_cybersource_menu | Implementation of hook_menu(). |
uc_cybersource_payment_gateway | |
uc_cybersource_payment_gateway_settings_submit | |
uc_cybersource_receipt | |
uc_cybersource_settings_form | |
uc_cybersource_tax_test | |
_parse_cs_avs_code | |
_parse_cs_cvv_code | |
_parse_cs_reason_code | |
_uc_cybersource_card_type | |
_uc_cybersource_post_charge | |
_uc_cybersource_soap_charge | |
_uc_cybersource_soap_login_data |