'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(''); } 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.
Silent Order POST requires cURL support and a modified HOP.php.
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('
'. print_r($config, TRUE) .'
'); drupal_set_message('
'. print_r($request, TRUE) .'
'); 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('Credit card !type: !amount
Decision: @decision
Reason: !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
!decision, Reason: !reason', array('!id' => $nvp['orderPage_serialNumber'], '!decision' => $nvp['decision'], '!reason' => $nvp['reasonCode'])); if (!empty($nvp['ccAuthReply_avsCode'])) { $o_comment .= t('
AVS: !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('
CVV: !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', '
'. print_r($functions, TRUE) .'
'); // $types = $soapClient->__getTypes(); // watchdog('uc_cybersource', '
'. print_r($types, TRUE) .'
'); $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 contact sales 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('@type: @amount
Decision: @decision
Reason: !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('@type:
@id
@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 .= '
'. t('AVS: @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 .= '
'. t('CVV: @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('CyberSource profile created.
Subscription ID: @id', array('@id' => $id)), 'admin'); } else { uc_order_comment_save($order->order_id, 0, t('Attempt to create CyberSource profile failed.
Reason: @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('
Request: '. print_r($request, TRUE) .'
'); // Send the request to CyberSource and get the reply. $reply = $soapClient->runTransaction($request); // drupal_set_message('
Reply: '. print_r($reply, TRUE) .'
'); } 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 contact sales 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' => '
'. print_r($result, TRUE) .'
')), 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'; // Discover } 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.
Possible action: Resend the request with the correct information.'); case '150': return t('Error: General system failure.
Possible action: Wait a few minutes and resend the request.'); case '151': return t('Error: The request was received, but a server time-out occurred. This error does not include time-outs between the client and the server.
Possible action: 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('Error: The request was received, but a service did not finish running in time.
Possible action: 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.
Possible action: You can capture the authorization, but consider reviewing the order for the possibility of fraud.'); case '202': return t('Expired card.
Possible action: 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.
Possible action: Request a different card or other form of payment.'); case '204': return t('Insufficient funds in the account.
Possible action: Request a different card or other form of payment.'); case '205': return t("Stolen or lost card.
Possible action: 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.
Possible action: Wait a few minutes and resend the request.'); case '208': return t('Inactive card or card not authorized for card-not-present transactions.
Possible action: Request a different card or other form of payment.'); case '210': return t('The card has reached the credit limit.
Possible action: Request a different card or other form of payment.'); case '211': return t('The card verification number is invalid.
Possible action: 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.
Possible action: Request a different form of payment."); case '221': return t('The customer matched an entry on the processor’s negative file.
Possible action: Review the order and contact the payment processor.'); case '222': return t("The customer's bank account is frozen.
Possible action: 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.
Possible action: You can capture the authorization, but consider reviewing the order for the possibility of fraud.'); case '231': return t('Invalid account number.
Possible action: Request a different card or other form of payment.'); case '232': return t('The card type is not accepted by the payment processor.
Possible action: 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.
Possible action: Request a different form of payment.'); case '234': return t('There is a problem with your CyberSource merchant configuration.
Possible action: Do not resend the request. Contact Customer Support to correct the configuration problem.'); case '236': return t('Processor failure.
Possible action: 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.
Possible action: 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('Error: The request was received, but a time-out occurred with the payment processor.
Possible action: 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.
Possible action: Authenticate the cardholder before continuing with the transaction.'); case '476': return t("The customer cannot be authenticated.
Possible action: 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.
Possible action: 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.'); } }