uc_cybersource.module 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
  1. <?php
  2. /**
  3. * @file
  4. * A module used for CyberSource's Silent Order POST
  5. * and Hosted Order Page methods of payment.
  6. *
  7. * Development sponsored by:
  8. * Acquia - http://acquia.com
  9. *
  10. * Harvard Magazine Classifieds through Growing Venture Solutions
  11. * https://classifieds.harvardmagazine.com/
  12. * http://growingventuresolutions.com
  13. */
  14. /**
  15. * Implements hook_menu().
  16. */
  17. function uc_cybersource_menu() {
  18. $items['cybersource/hop-post'] = array(
  19. 'title' => 'Payment received',
  20. 'page callback' => 'uc_cybersource_hop_post',
  21. 'access callback' => TRUE,
  22. 'type' => MENU_CALLBACK,
  23. );
  24. // Callback functions for Website Payments Standard.
  25. $items['cybersource/hop-complete/%uc_order'] = array(
  26. 'title' => 'CyberSource payment complete',
  27. 'page callback' => 'uc_cybersource_hop_complete',
  28. 'page arguments' => array(2),
  29. 'access arguments' => array('access content'),
  30. 'type' => MENU_CALLBACK,
  31. );
  32. $items['admin/store/orders/%uc_order/cs_tax'] = array(
  33. 'title' => 'Order Taxes',
  34. 'page callback' => 'uc_cybersource_tax_test',
  35. 'page arguments' => array(3),
  36. 'access arguments' => array('administer store'),
  37. 'type' => MENU_CALLBACK,
  38. );
  39. return $items;
  40. }
  41. /**
  42. * Implements hook_page_alter().
  43. */
  44. function uc_cybersource_page_alter(&$page) {
  45. $block = block_load('system', 'main');
  46. // Add to the review page hidden form fields with data to post to
  47. // CyberSource HOP.
  48. if (isset($page[$block->region]['system_main']['#theme']) && $page[$block->region]['system_main']['#theme'] == 'uc_cart_checkout_review' && ($order_id = intval($_SESSION['cart_order'])) > 0) {
  49. $order = uc_order_load($order_id);
  50. if ($order->payment_method == 'cybersource_hop') {
  51. $page[$block->region]['system_main']['#form'] = drupal_get_form('uc_cybersource_hop_form', $order);
  52. }
  53. }
  54. }
  55. /**
  56. * Implements hook_form_FORM_ID_alter() for uc_payment_method_settings_form().
  57. */
  58. function uc_cybersource_form_uc_payment_method_settings_form_alter(&$form, &$form_state) {
  59. if ($form_state['build_info']['args'][0] == 'credit') {
  60. $form['#submit'][] = 'uc_cybersource_payment_gateway_settings_submit';
  61. }
  62. }
  63. /**
  64. * Submit handler for payment gateway settings form to encrypt fields.
  65. */
  66. function uc_cybersource_payment_gateway_settings_submit($form, &$form_state) {
  67. // If CC encryption has been configured properly.
  68. if ($key = uc_credit_encryption_key()) {
  69. // Setup our encryption object.
  70. $crypt = new UbercartEncryption();
  71. // Encrypt the Merchant ID and Transaction key.
  72. if (!empty($form_state['values']['uc_cybersource_soap_merchant_id'])) {
  73. variable_set('uc_cybersource_soap_merchant_id', $crypt->encrypt($key, $form_state['values']['uc_cybersource_soap_merchant_id']));
  74. }
  75. if (!empty($form_state['values']['uc_cybersource_soap_transaction_key'])) {
  76. variable_set('uc_cybersource_soap_transaction_key', $crypt->encrypt($key, $form_state['values']['uc_cybersource_soap_transaction_key']));
  77. }
  78. // Store any errors.
  79. uc_store_encryption_errors($crypt, 'uc_cybersource');
  80. }
  81. }
  82. /*******************************************************************************
  83. * Hook Functions (Ubercart)
  84. ******************************************************************************/
  85. /**
  86. * Implements hook_uc_payment_gateway().
  87. */
  88. function uc_cybersource_uc_payment_gateway() {
  89. // CyberSource APIs other than HOP require uc_credit to be enabled.
  90. if (!module_exists('uc_credit')) {
  91. return;
  92. }
  93. $gateways['cybersource'] = array(
  94. 'title' => t('CyberSource Silent Order POST'),
  95. 'description' => t('Process credit card payments using the Silent Order POST service of CyberSource.'),
  96. 'settings' => 'uc_cybersource_settings_form',
  97. 'credit' => 'uc_cybersource_charge',
  98. 'credit_txn_types' => array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_PRIOR_AUTH_CAPTURE, UC_CREDIT_AUTH_CAPTURE, UC_CREDIT_REFERENCE_TXN),
  99. );
  100. return $gateways;
  101. }
  102. /**
  103. * Processes a payment POST from the CyberSource Hosted Order Page API.
  104. */
  105. function uc_cybersource_hop_post() {
  106. if (!uc_cybersource_hop_include()) {
  107. watchdog('uc_cybersource_hop', 'Unable to receive HOP POST due to missing or unreadable HOP.php file.', array(), 'error');
  108. drupal_add_http_header('Status', '503 Service unavailable');
  109. drupal_set_title(t('Unable to receive HOP POST.'));
  110. print t('The site was unable to receive a HOP post because of a missing or unreadble HOP.php');
  111. exit();
  112. }
  113. $verify = VerifyTransactionSignature($_POST);
  114. watchdog('uc_cybersource_hop', 'Receiving payment notification at URL for order @orderNumber',
  115. array('@orderNumber' => $_POST['orderNumber'] ));
  116. if (!isset($_POST['orderNumber'])) {
  117. watchdog('uc_cybersource_hop', 'CS HOP attempted with invalid order number.', array(), WATCHDOG_ERROR);
  118. return;
  119. }
  120. if (!$verify) {
  121. watchdog('uc_cybersource_hop', 'Receiving invalid payment notification at URL for order @orderNumber. <pre>@debug</pre>',
  122. array('@orderNumber' => $_POST['orderNumber'], '@debug' => print_r($_POST, TRUE) ));
  123. return;
  124. }
  125. // Assign posted variables to local variables.
  126. $decision = check_plain($_POST['decision']);
  127. $reason_code = check_plain($_POST['reasonCode']);
  128. $reason = _parse_cs_reason_code($reason_code);
  129. $payment_amount = check_plain($_POST['orderAmount']);
  130. $payment_currency = check_plain($_POST['paymentCurrency']);
  131. $request_id = check_plain($_POST['requestID']);
  132. $request_token = check_plain($_POST['orderPage_requestToken']);
  133. $reconciliation_id = check_plain($_POST['reconciliationID']);
  134. $order_id = check_plain($_POST['orderNumber']);
  135. $payer_email = check_plain($_POST['billTo_email']);
  136. $order = uc_order_load($_POST['orderNumber']);
  137. switch ($decision) {
  138. case 'ACCEPT':
  139. watchdog('uc_cybersource_hop', 'CyberSource verified successful payment.');
  140. $duplicate = (bool) db_query_range('SELECT 1 FROM {uc_payment_cybersource_hop_post} WHERE order_id = :order_id AND decision = :decision', 0, 1, array(':order_id' => $order_id, ':decision' => 'ACCEPT'))->fetchField();
  141. if ($duplicate) {
  142. watchdog('uc_cybersource_hop', 'CS HOP transaction for order @order-id has been processed before.', array('@order_id' => $order_id), WATCHDOG_NOTICE);
  143. return;
  144. }
  145. db_insert('uc_payment_cybersource_hop_post')
  146. ->fields(array(
  147. 'order_id' => $order_id,
  148. 'request_id' => $request_id,
  149. 'request_token' => $request_token,
  150. 'reconciliation_id' => $reconciliation_id,
  151. 'gross' => $payment_amount,
  152. 'decision' => $decision,
  153. 'reason_code' => $reason_code,
  154. 'payer_email' => $payer_email,
  155. 'received' => REQUEST_TIME,
  156. ))
  157. ->execute();
  158. $comment = t('CyberSource request ID: @txn_id', array('@txn_id' => $request_id));
  159. uc_payment_enter($order_id, 'cybersource_hop', $payment_amount, $order->uid, NULL, $comment);
  160. uc_cart_complete_sale($order);
  161. uc_order_comment_save($order_id, 0, t('Payment of @amount @currency submitted through CyberSource with request ID @rid.', array('@amount' => $payment_amount, '@currency' => $payment_currency, '@rid' => $request_id)), 'order', 'payment_received');
  162. break;
  163. case 'ERROR':
  164. uc_order_comment_save($order_id, 0, t("Payment error:@reason with request ID @rid", array('@reason' => $reason, '@rid' => '@request_id')), 'admin');
  165. break;
  166. case 'REJECT':
  167. uc_order_comment_save($order_id, 0, t("Payment is rejected:@reason with request ID @rid", array('@reason' => $reason, '@rid' => '@request_id')), 'admin');
  168. break;
  169. case 'REVIEW':
  170. uc_order_update_status($order_id, 'review');
  171. uc_order_comment_save($order_id, 0, t('Payment is in review & not complete: @reason. Request ID @rid', array('@reason' => $reason, '@rid' => '@request_id')), 'admin');
  172. break;
  173. }
  174. }
  175. /**
  176. * Checks for HOP.php and includes it or returns FALSE if it cannot be found.
  177. */
  178. function uc_cybersource_hop_include() {
  179. $hop_paths[0] = 'sites/all/libraries/uc_cybersource/HOP.php';
  180. $hop_paths[1] = drupal_get_path('module', 'uc_cybersource') . '/HOP.php';
  181. // Loop through possible paths, include and return TRUE when HOP.php is
  182. // located.
  183. foreach ($hop_paths as $key => $path) {
  184. if (file_exists($path)) {
  185. require_once($path);
  186. return TRUE;
  187. }
  188. }
  189. // We didn't find HOP.php in any of the possible paths.
  190. return FALSE;
  191. }
  192. /**
  193. * Adds the CyberSource fields to the payment gateway settings form.
  194. */
  195. function uc_cybersource_settings_form($form, &$form_state) {
  196. // Check for the HOP.php for Silent Order POST.
  197. if (variable_get('uc_cybersource_method', 'post') == 'post' &&
  198. !uc_cybersource_hop_include()) {
  199. 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. Remember to open it and replace instances of L( with csL(.'), 'error');
  200. }
  201. $form['uc_cybersource_server'] = array(
  202. '#type' => 'select',
  203. '#title' => t('Payment server'),
  204. '#description' => t('CyberSource server used when processing payments.'),
  205. '#options' => array(
  206. 'production' => t('Production'),
  207. 'test' => t('Test'),
  208. ),
  209. '#default_value' => variable_get('uc_cybersource_server', 'test'),
  210. );
  211. $form['uc_cybersource_method'] = array(
  212. '#type' => 'radios',
  213. '#title' => t('Payment method'),
  214. '#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', array('absolute' => TRUE)))),
  215. '#options' => array(
  216. 'post' => t('Silent Order POST'),
  217. // 'api' => t('Simple Order API'),
  218. 'soap' => t('SOAP Toolkit API'),
  219. ),
  220. '#default_value' => variable_get('uc_cybersource_method', 'post'),
  221. );
  222. $form['uc_cybersource_avs'] = array(
  223. '#type' => 'radios',
  224. '#title' => t('Ensure address verification'),
  225. '#options' => array(
  226. 'true' => t('Process transaction only if address passes verification.'),
  227. 'false' => t('Process transaction regardless of the result of address verification.'),
  228. ),
  229. '#default_value' => variable_get('uc_cybersource_avs', 'true'),
  230. );
  231. $login = _uc_cybersource_soap_login_data();
  232. $form['soap'] = array(
  233. '#type' => 'fieldset',
  234. '#title' => t('SOAP Toolkit API settings'),
  235. '#collapsible' => TRUE,
  236. '#collapsed' => TRUE,
  237. );
  238. $form['soap']['uc_cybersource_soap_merchant_id'] = array(
  239. '#type' => 'textfield',
  240. '#title' => t('Merchant ID'),
  241. '#default_value' => $login['merchant_id'],
  242. );
  243. $form['soap']['uc_cybersource_soap_transaction_key'] = array(
  244. '#type' => 'textarea',
  245. '#title' => t('Transaction key'),
  246. '#default_value' => $login['transaction_key'],
  247. );
  248. $form['soap']['uc_cybersource_soap_create_profile'] = array(
  249. '#type' => 'checkbox',
  250. '#title' => t('Create a CyberSource Basic Profile for every new credit card order processed.'),
  251. '#default_value' => variable_get('uc_cybersource_soap_create_profile', FALSE),
  252. );
  253. $form['soap']['uc_cybersource_soap_tax_calculate'] = array(
  254. '#type' => 'checkbox',
  255. '#title' => t('Enable calculation of taxes through the CyberSource tax service.'),
  256. '#default_value' => variable_get('uc_cybersource_soap_tax_calculate', FALSE),
  257. );
  258. $form['soap']['ship_from'] = array(
  259. '#type' => 'fieldset',
  260. '#title' => t('Tax calculation "Ship from" address'),
  261. '#description' => t('This address will be used when calculating taxes with CyberSource tax service.'),
  262. );
  263. $form['soap']['ship_from']['cs_ship_from_first_name'] = array(
  264. '#type' => 'textfield',
  265. '#title' => t('First name'),
  266. '#default_value' => variable_get('cs_ship_from_first_name', ''),
  267. );
  268. $form['soap']['ship_from']['cs_ship_from_last_name'] = array(
  269. '#type' => 'textfield',
  270. '#title' => t('Last name'),
  271. '#default_value' => variable_get('cs_ship_from_last_name', ''),
  272. );
  273. $form['soap']['ship_from']['cs_ship_from_street1'] = array(
  274. '#type' => 'textfield',
  275. '#title' => t('Street address'),
  276. '#default_value' => variable_get('cs_ship_from_street1', ''),
  277. );
  278. $form['soap']['ship_from']['cs_ship_from_city'] = array(
  279. '#type' => 'textfield',
  280. '#title' => t('City'),
  281. '#default_value' => variable_get('cs_ship_from_city', ''),
  282. );
  283. $form['soap']['ship_from']['cs_ship_from_zone'] = array(
  284. '#type' => 'textfield',
  285. '#title' => t('State/Province'),
  286. '#description' => t('Enter the 2 letter abbreviation of your state or province.'),
  287. '#default_value' => variable_get('cs_ship_from_zone', ''),
  288. '#maxlength' => 2,
  289. );
  290. $form['soap']['ship_from']['cs_ship_from_postal_code'] = array(
  291. '#type' => 'textfield',
  292. '#title' => t('ZIP/Postal code'),
  293. '#default_value' => variable_get('cs_ship_from_postal_code', ''),
  294. );
  295. $form['soap']['ship_from']['cs_ship_from_country'] = array(
  296. '#type' => 'textfield',
  297. '#title' => t('Country code'),
  298. '#description' => t("Enter the 2 letter ISO 3166-1 code; consult Wikipedia if you don't know yours."),
  299. '#default_value' => variable_get('cs_ship_from_country', ''),
  300. '#maxlength' => 2,
  301. );
  302. $form['soap']['ship_from']['cs_ship_from_email'] = array(
  303. '#type' => 'textfield',
  304. '#title' => t('E-mail address'),
  305. '#default_value' => variable_get('cs_ship_from_email', ''),
  306. );
  307. return $form;
  308. }
  309. /**
  310. * Defines payment method properties.
  311. *
  312. * @return
  313. * An array with property and value pairs of CyberSource payment method.
  314. */
  315. function uc_cybersource_payment_method() {
  316. $methods[] = array(
  317. 'id' => 'cybersource_hop',
  318. 'name' => t('CyberSource Hosted Order Page'),
  319. 'title' => "Credit/Debit card payment processed by CyberSource",
  320. 'review' => t('Credit/Debit card payment processed by CyberSource'),
  321. 'desc' => t('Payment with CyberSource HOP Service.'),
  322. 'callback' => 'uc_payment_method_cybersource_hop',
  323. 'weight' => 1,
  324. 'checkout' => FALSE,
  325. 'no_gateway' => TRUE,
  326. );
  327. return $methods;
  328. }
  329. /**
  330. * Payment method callback.
  331. */
  332. function uc_payment_method_cybersource_hop($op, &$order) {
  333. if ($op == 'settings') {
  334. $form['uc_cybersource_hop_server'] = array(
  335. '#type' => 'select',
  336. '#title' => t('CyberSource HOP server'),
  337. '#description' => t('Select between production/live or test mode.'),
  338. '#options' => array(
  339. 'https://orderpagetest.ic3.com/hop/orderform.jsp' => t('Test Center'),
  340. 'https://orderpage.ic3.com/hop/orderform.jsp' => t('Production/Live')),
  341. '#default_value' => variable_get('uc_cybersource_hop_server', 'https://orderpagetest.ic3.com/hop/orderform.jsp'),
  342. );
  343. $form['uc_cybersource_hop_transaction_type'] = array(
  344. '#type' => 'radios',
  345. '#title' => t('CyberSource transaction type'),
  346. '#description' => t('Authorize and settle, or authorize only for capture on CyberSource.com'),
  347. '#options' => array(
  348. 'authorization' => t('Authorize only'),
  349. 'sale' => t('Authorize and capture'),
  350. ),
  351. '#default_value' => variable_get('uc_cybersource_hop_transaction_type', 'sale'),
  352. );
  353. $form['uc_cybersource_cs_hop_button_text'] = array(
  354. '#type' => 'textfield',
  355. '#title' => t('CyberSource "Buy button" text'),
  356. '#description' => t('This text appears on the button users press to process their payment on the Hosted Order Page.'),
  357. '#default_value' => variable_get('uc_cybersource_cs_hop_button_text', t('Process payment')),
  358. );
  359. return $form;
  360. }
  361. }
  362. /**
  363. * Finalizes CyberSource transaction.
  364. */
  365. function uc_cybersource_hop_complete($order) {
  366. // If the order ID specified in the return URL is not the same as the one in
  367. // the user's session, we need to assume this is either a spoof or that the
  368. // user tried to adjust the order on this side while at PayPal. If it was a
  369. // legitimate checkout, the CyberSource POST will still register, so the
  370. // gets processed correctly. We'll leave an ambiguous message just in case.
  371. if (intval($_SESSION['cart_order']) != $order->order_id) {
  372. drupal_set_message(t('Thank you for your order! We will be notified by CyberSource that we have received your payment.'));
  373. drupal_goto('cart');
  374. }
  375. // This lets us know it's a legitimate access of the complete page.
  376. $_SESSION['uc_checkout'][$_SESSION['cart_order']]['do_complete'] = TRUE;
  377. drupal_goto('cart/checkout/complete');
  378. }
  379. /**
  380. * Defines values to be posted to CyberSource.
  381. *
  382. * @return
  383. * Transaction data arrays are returned as hidden form values.
  384. */
  385. function uc_cybersource_hop_form($form, $form_state, $order) {
  386. if (!uc_cybersource_hop_include()) {
  387. drupal_set_message(t('Hosted Order Page requires the HOP.php provided by CyberSource.'));
  388. // TODO - Does returning false here make sense?
  389. return array('success' => FALSE);
  390. }
  391. $billing_country = uc_get_country_data(array('country_id' => $order->billing_country));
  392. $delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
  393. $data = array(
  394. 'billTo_firstName' => $order->billing_first_name,
  395. 'billTo_lastName' => $order->billing_last_name,
  396. 'billTo_street1' => $order->billing_street1,
  397. 'billTo_city' => $order->billing_city,
  398. 'billTo_country' => $billing_country[0]['country_iso_code_2'],
  399. 'billTo_state' => uc_get_zone_code($order->billing_zone),
  400. 'billTo_postalCode' => $order->billing_postal_code,
  401. 'billTo_email' => $order->primary_email,
  402. 'billTo_phoneNumber' => $order->billing_phone,
  403. );
  404. if (uc_order_is_shippable($order)) {
  405. $data += array(
  406. 'shipTo_firstName' => $order->delivery_first_name,
  407. 'shipTo_lastName' => $order->delivery_last_name,
  408. 'shipTo_street1' => $order->delivery_street1,
  409. 'shipTo_street2' => $order->delivery_street2,
  410. 'shipTo_city' => $order->delivery_city,
  411. 'shipTo_country' => $delivery_country[0]['country_iso_code_2'],
  412. 'shipTo_state' => uc_get_zone_code($order->delivery_zone),
  413. 'shipTo_postalCode' => $order->delivery_postal_code,
  414. );
  415. }
  416. $shipping = 0;
  417. foreach ($order->line_items as $item) {
  418. if ($item['type'] == 'shipping') {
  419. $shipping += $item['amount'];
  420. }
  421. }
  422. $tax = 0;
  423. if (module_exists('uc_taxes')) {
  424. foreach (uc_taxes_calculate($order) as $tax_item) {
  425. $tax += $tax_item->amount;
  426. }
  427. }
  428. $amount = $order->order_total - $shipping - $tax;
  429. $currency = variable_get('uc_cybersource_hop_currency', 'USD');
  430. $merchantID = getMerchantID();
  431. $timestamp = getmicrotime();
  432. $datax = $merchantID . $amount . $currency . $timestamp;
  433. $pub = function_exists('getSharedSecret') ? getSharedSecret() : getPublicKey();
  434. $serialNumber = getSerialNumber();
  435. $pub_digest = hopHash($datax, $pub);
  436. $data['amount'] = $amount;
  437. $data['currency'] = $currency;
  438. $data['merchantID'] = $merchantID;
  439. $data['orderNumber'] = $order->order_id;
  440. $data['orderPage_timestamp'] = $timestamp;
  441. $data['orderPage_ignoreAVS'] = variable_get('uc_cybersource_hop_avs', 'true') == 'true' ? 'false' : 'true';
  442. $data['orderPage_signaturePublic'] = $pub_digest;
  443. $data['orderPage_version'] = '4';
  444. $data['orderPage_serialNumber'] = $serialNumber;
  445. $data['orderPage_transactionType'] = variable_get('uc_cybersource_hop_transaction_type', 'sale');
  446. $data['orderPage_sendMerchantReceiptEmail'] = variable_get('uc_cybersource_hop_merchant_receipt_email', 'true');
  447. $data['orderPage_sendMerchantURLPost'] = 'true';
  448. // CyberSource posts payment confirmation to this URL.
  449. $data['orderPage_merchantURLPostAddress'] = url('cybersource/hop-post', array('absolute' => TRUE));
  450. $data['orderPage_buyButtonText'] = t('Checkout');
  451. $receipt_url = url('cybersource/hop-complete/' . $order->order_id, array('absolute' => TRUE));
  452. $data['orderPage_receiptResponseURL'] = $receipt_url;
  453. $data['orderPage_buyButtonText'] = variable_get('uc_cybersource_cs_hop_button_text', t('Process payment'));
  454. $comments = t('Order @order-id at @store-name', array('@order-id' => $order->order_id, '@store-name' => uc_store_name()));
  455. $alter_data['order'] = $order;
  456. $alter_data['comments'] = $comments;
  457. $alter_data['merchant_fields'] = array();
  458. // Allow other modules to alter the comment & merchant field data stored
  459. // with CyberSource.
  460. drupal_alter('uc_cybersource_data', $alter_data);
  461. $data['comments'] = $alter_data['comments'];
  462. if (!empty($alter_data['merchant_fields'])) {
  463. foreach ($alter_data['merchant_fields'] as $key => $value) {
  464. $data[$key] = $value;
  465. }
  466. }
  467. foreach ($data as $name => $value) {
  468. if (!empty($value)) {
  469. $form[$name] = array('#type' => 'hidden', '#value' => $value);
  470. }
  471. }
  472. $form['#action'] = variable_get('uc_cybersource_hop_server', 'https://orderpagetest.ic3.com/hop/orderform.jsp');
  473. $form['submit'] = array(
  474. '#type' => 'submit',
  475. '#value' => t('Submit order'),
  476. );
  477. return $form;
  478. }
  479. /**
  480. * Charges card.
  481. */
  482. function uc_cybersource_charge($order_id, $amount, $data) {
  483. global $user;
  484. $order = uc_order_load($order_id);
  485. $amount = uc_currency_format($amount, FALSE, FALSE, '.');
  486. $cc_type = NULL;
  487. if (isset($order->payment_details['cc_type'])) {
  488. switch (strtolower($order->payment_details['cc_type'])) {
  489. case 'amex':
  490. case 'american express':
  491. $cc_type = '003';
  492. break;
  493. case 'visa':
  494. $cc_type = '001';
  495. break;
  496. case 'mastercard':
  497. case 'master card':
  498. $cc_type = '002';
  499. break;
  500. case 'discover':
  501. $cc_type = '004';
  502. break;
  503. }
  504. }
  505. if (is_null($cc_type)) {
  506. $cc_type = _uc_cybersource_card_type($order->payment_details['cc_number']);
  507. if ($cc_type === FALSE && in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_AUTH_CAPTURE))) {
  508. drupal_set_message(t('The credit card type did not pass validation.'), 'error');
  509. watchdog('uc_cybersource', 'Could not figure out cc type: @number / @type', array('@number' => $order->payment_details['cc_number'], '@type' => $order->payment_details['cc_type']), WATCHDOG_ERROR);
  510. return array('success' => FALSE);
  511. }
  512. }
  513. $country = uc_get_country_data(array('country_id' => $order->billing_country));
  514. if ($country === FALSE) {
  515. $country = array(0 => array('country_iso_code_2' => 'US'));
  516. }
  517. // Process the charge differently depending on the CyberSource method.
  518. switch (variable_get('uc_cybersource_method', 'post')) {
  519. // Support for the Silent Order POST.
  520. case 'post':
  521. return _uc_cybersource_post_charge($order, $amount, $data, $cc_type, $country);
  522. // Support for the SOAP Toolkit API.
  523. case 'soap':
  524. // TODO: Refactor to use separate function for each API type.
  525. // - i.e. _uc_cybersource_charge_request_soap($order, $amount, $data);
  526. // require_once(drupal_get_path('module', 'uc_cybersource') . '/SOAP.php');
  527. return _uc_cybersource_soap_charge($order, $amount, $data, $cc_type, $country);
  528. case 'api':
  529. $config = cybs_load_config('cybs.ini');
  530. if (variable_get('uc_cybersource_server', 'test') == 'test') {
  531. $config['sendToProduction'] = 'false';
  532. }
  533. $request['ccAuthService_run'] = 'true';
  534. if (variable_get('uc_cybersource_hop_transaction_type', 'sale') == 'sale') {
  535. $request['ccCaptureService_run'] = 'true';
  536. }
  537. $request['merchantReferenceCode'] = $order_id;
  538. $request['purchaseTotals_currency'] = 'USD';
  539. $request['purchaseTotals_grandTotalAmount'] = $amount;
  540. drupal_set_message('<pre>' . print_r($config, TRUE) . '</pre>');
  541. drupal_set_message('<pre>' . print_r($request, TRUE) . '</pre>');
  542. break;
  543. }
  544. }
  545. /**
  546. * POSTs transaction to CyberSource.
  547. */
  548. function _uc_cybersource_post_charge($order, $amount, $data, $cc_type, $country) {
  549. // Include the HOP.php per the module instructions.
  550. if (!uc_cybersource_hop_include()) {
  551. drupal_set_message(t('Silent Order POST requires the HOP.php provided by CyberSource.'));
  552. return array('success' => FALSE);
  553. }
  554. $request = array(
  555. 'billTo_firstName' => $order->billing_first_name,
  556. 'billTo_lastName' => $order->billing_last_name,
  557. 'billTo_street1' => $order->billing_street1,
  558. 'billTo_city' => $order->billing_city,
  559. 'billTo_country' => $country[0]['country_iso_code_2'],
  560. 'billTo_state' => uc_get_zone_code($order->billing_zone),
  561. 'billTo_postalCode' => $order->billing_postal_code,
  562. 'billTo_email' => $order->primary_email,
  563. 'card_accountNumber' => $order->payment_details['cc_number'],
  564. 'card_cardType' => $cc_type,
  565. 'card_expirationMonth' => $order->payment_details['cc_exp_month'],
  566. 'card_expirationYear' => $order->payment_details['cc_exp_year'],
  567. );
  568. if (variable_get('uc_credit_cvv_enabled', TRUE)) {
  569. $request['card_cvNumber'] = $order->payment_details['cc_cvv'];
  570. }
  571. $currency = variable_get('uc_cybersource_currency', 'usd');
  572. $merchantID = getMerchantID();
  573. $timestamp = getmicrotime();
  574. $data = $merchantID . $amount . $currency . $timestamp;
  575. $pub = function_exists('getSharedSecret') ? getSharedSecret() : getPublicKey();
  576. $serialNumber = getSerialNumber();
  577. $pub_digest = hopHash($data, $pub);
  578. $request['amount'] = $amount;
  579. $request['currency'] = $currency;
  580. $request['merchantID'] = $merchantID;
  581. $request['orderNumber'] = $order->order_id;
  582. $request['orderPage_timestamp'] = $timestamp;
  583. $request['orderPage_ignoreAVS'] = variable_get('uc_cybersource_avs', 'true') == 'true' ? 'false' : 'true';
  584. $request['orderPage_signaturePublic'] = $pub_digest;
  585. $request['orderPage_version'] = '4';
  586. $request['orderPage_serialNumber'] = $serialNumber;
  587. $request['orderPage_transactionType'] = variable_get('uc_cybersource_hop_transaction_type', 'sale');
  588. $data = '';
  589. while (list($key, $value) = each($request)) {
  590. $data .= $key . '=' . urlencode(str_replace(',', '', $value)) . '&';
  591. }
  592. $data = substr($data, 0, -1);
  593. if (variable_get('uc_cybersource_server', 'test') == 'test') {
  594. $url = 'https://orderpagetest.ic3.com/hop/ProcessOrder.do';
  595. }
  596. else {
  597. $url = 'https://orderpage.ic3.com/hop/ProcessOrder.do';
  598. }
  599. $ch = curl_init();
  600. curl_setopt($ch, CURLOPT_URL, $url);
  601. curl_setopt($ch, CURLOPT_VERBOSE, 0);
  602. curl_setopt($ch, CURLOPT_POST, 1);
  603. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  604. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  605. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  606. curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
  607. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  608. $response = curl_exec($ch);
  609. if ($error = curl_error($ch)) {
  610. watchdog('uc_cybersource', '@error', array('@error' => $error), WATCHDOG_ERROR);
  611. }
  612. curl_close($ch);
  613. if (preg_match_all('`name=".+" value=".+"`', $response, $pairs) > 0) {
  614. for ($i = 0; $i < count($pairs[0]); $i++) {
  615. list($name, $value) = explode('" value="', substr($pairs[0][$i], 6, strlen($pairs[0][$i]) - 7));
  616. $nvp[$name] = $value;
  617. }
  618. // Create the order and payment ledger comments.
  619. $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_hop_transaction_type', 'sale'), '!amount' => uc_currency_format($nvp['orderAmount']), '@decision' => $nvp['decision'], '!reason' => _uc_cybersource_parse_reason_code($nvp['reasonCode'])));
  620. $p_comment = t('!id<br />!decision, Reason: !reason', array('!id' => $nvp['orderPage_serialNumber'], '!decision' => $nvp['decision'], '!reason' => $nvp['reasonCode']));
  621. if (!empty($nvp['ccAuthReply_avsCode'])) {
  622. $o_comment .= t('<br /><b>AVS:</b> !avs', array('!avs' => _uc_cybersource_parse_avs_code($nvp['ccAuthReply_avsCode'])));
  623. $p_comment .= t(', AVS: @avs', array('@avs' => $nvp['ccAuthReply_avsCode']));
  624. }
  625. if (!empty($nvp['ccAuthReply_cvCode'])) {
  626. $o_comment .= t('<br /><b>CVV:</b> !cvv', array('!cvv' => _uc_cybersource_parse_cvv_code($nvp['ccAuthReply_cvCode'])));
  627. $p_comment .= t(', CVV: @cvv', array('@cvv' => $nvp['ccAuthReply_cvCode']));
  628. }
  629. uc_order_comment_save($order->order_id, $user->uid, $o_comment, 'admin');
  630. if ($nvp['decision'] == 'ACCEPT') {
  631. $result = array(
  632. 'success' => TRUE,
  633. 'comment' => $p_comment,
  634. 'message' => $o_comment,
  635. 'uid' => $user->uid,
  636. );
  637. }
  638. else {
  639. $result = array(
  640. 'success' => FALSE,
  641. 'comment' => $p_comment,
  642. 'message' => $o_comment,
  643. 'uid' => $user->uid,
  644. );
  645. }
  646. }
  647. else {
  648. $result = array(
  649. 'success' => FALSE,
  650. 'message' => t('No response returned from CyberSource.'),
  651. );
  652. }
  653. return $result;
  654. }
  655. /**
  656. * Handles the SOAP charge request and Ubercart order save.
  657. */
  658. function _uc_cybersource_soap_charge($order, $amount, $data, $cc_type, $country) {
  659. // Include the SOAP helper file.
  660. module_load_include('inc', 'uc_cybersource', 'uc_cybersource.soap');
  661. global $user;
  662. // Set the URL for the CyberSource SOAP Toolkit API WSDL.
  663. if (variable_get('uc_cybersource_server', 'test') == 'test') {
  664. $url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
  665. }
  666. else {
  667. $url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
  668. }
  669. // Variable currency... not used at the moment.
  670. $currency = variable_get('uc_cybersource_currency', 'usd');
  671. $billing_country = uc_get_country_data(array('country_id' => $order->billing_country));
  672. $delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
  673. try {
  674. $soapClient = new CyberSourceSoapClient($url, array());
  675. // To see the functions and types that the SOAP extension can automatically
  676. // generate from the WSDL file, uncomment this section and check the logs.
  677. // $functions = $soapClient->__getFunctions();
  678. // watchdog('uc_cybersource', '<pre>' . print_r($functions, TRUE) . '</pre>');
  679. // $types = $soapClient->__getTypes();
  680. // watchdog('uc_cybersource', '<pre>' . print_r($types, TRUE) . '</pre>');
  681. $login = _uc_cybersource_soap_login_data();
  682. // Create the request with some meta data.
  683. $request = new stdClass();
  684. $request->merchantID = $login['merchant_id'];
  685. $request->merchantReferenceCode = $order->order_id;
  686. $request->clientLibrary = 'PHP';
  687. $request->clientLibraryVersion = phpversion();
  688. $request->clientEnvironment = php_uname();
  689. // Add the credit card authorization service.
  690. if (in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_AUTH_CAPTURE, UC_CREDIT_REFERENCE_TXN))) {
  691. $ccAuthService = new stdClass();
  692. $ccAuthService->run = 'true';
  693. $request->ccAuthService = $ccAuthService;
  694. }
  695. // Add the credit card capture service.
  696. if (in_array($data['txn_type'], array(UC_CREDIT_PRIOR_AUTH_CAPTURE, UC_CREDIT_AUTH_CAPTURE, UC_CREDIT_REFERENCE_TXN))) {
  697. $ccCaptureService = new stdClass();
  698. $ccCaptureService->run = 'true';
  699. // Add the values for prior authorization capture.
  700. if ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
  701. $ccCaptureService->authRequestID = $data['auth_id'];
  702. $ccCaptureService->authRequestToken = $order->data['cybersource'][$data['auth_id']];
  703. }
  704. $request->ccCaptureService = $ccCaptureService;
  705. // Add the subscription ID for a reference transaction.
  706. if ($data['txn_type'] == UC_CREDIT_REFERENCE_TXN) {
  707. $recurringSubscriptionInfo = new stdClass();
  708. $recurringSubscriptionInfo->subscriptionID = $data['ref_id'];
  709. $request->recurringSubscriptionInfo = $recurringSubscriptionInfo;
  710. $request->merchantReferenceCode .= ' (COF)';
  711. }
  712. }
  713. // If enabled, create a subscription profile for this transaction.
  714. if (variable_get('uc_cybersource_soap_create_profile', FALSE) && in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_AUTH_CAPTURE))) {
  715. // Skip if a profile already exists for this order.
  716. if (!isset($order->data['uc_cybersource']['soap']['subscription_id'])) {
  717. $recurringSubscriptionInfo = new stdClass();
  718. $recurringSubscriptionInfo->amount = 0;
  719. $recurringSubscriptionInfo->frequency = 'on-demand';
  720. $request->recurringSubscriptionInfo = $recurringSubscriptionInfo;
  721. $paySubscriptionCreateService = new stdClass();
  722. $paySubscriptionCreateService->run = 'true';
  723. $request->paySubscriptionCreateService = $paySubscriptionCreateService;
  724. }
  725. }
  726. // Add the billing information.
  727. $billTo = new stdClass();
  728. $billTo->firstName = $order->billing_first_name;
  729. $billTo->lastName = $order->billing_last_name;
  730. $billTo->street1 = $order->billing_street1;
  731. if ($order->billing_street2) {
  732. $billTo->street2 = $order->billing_street2;
  733. }
  734. $billTo->city = $order->billing_city;
  735. $billTo->state = uc_get_zone_code($order->billing_zone);
  736. $billTo->postalCode = $order->billing_postal_code;
  737. $billTo->country = $billing_country[0]['country_iso_code_2'];
  738. if ($order->billing_phone) {
  739. $billTo->phoneNumber = $order->billing_phone;
  740. }
  741. $billTo->email = $order->primary_email;
  742. $billTo->customerID = $order->uid;
  743. $request->billTo = $billTo;
  744. // Add the credit card details if needed.
  745. if (in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_AUTH_CAPTURE))) {
  746. $card = new stdClass();
  747. $card->accountNumber = $order->payment_details['cc_number'];
  748. $card->expirationMonth = $order->payment_details['cc_exp_month'];
  749. $card->expirationYear = $order->payment_details['cc_exp_year'];
  750. $card->cardType = $cc_type;
  751. if (variable_get('uc_credit_cvv_enabled', TRUE)) {
  752. $card->cvNumber = $order->payment_details['cc_cvv'];
  753. }
  754. $request->card = $card;
  755. }
  756. // Add the order total information.
  757. $purchaseTotals = new stdClass();
  758. $purchaseTotals->currency = $currency;
  759. $purchaseTotals->grandTotalAmount = $amount;
  760. $request->purchaseTotals = $purchaseTotals;
  761. // Separately add products and line item into the request items object if
  762. // we're charging the full order total.
  763. if (round($amount, 2) == round($order->order_total, 2)) {
  764. $request->item = array();
  765. $counter = 0;
  766. // Add the products to the item array.
  767. foreach ($order->products as $product) {
  768. $obj = $request->item[] = new stdClass();
  769. $obj->productName = $product->title;
  770. $obj->unitPrice = $product->price;
  771. $obj->quantity = $product->qty;
  772. $obj->productSKU = $product->model;
  773. $obj->productCode = 'default';
  774. $obj->id = $counter;
  775. $counter++;
  776. }
  777. // Add the line items to the item array.
  778. $discount_amount = 0;
  779. foreach ((array) $order->line_items as $line_item) {
  780. // Handle negative line items.
  781. if ($line_item['amount'] < 0) {
  782. $discount_amount += -$line_item['amount'];
  783. }
  784. // Skip subtotal line items.
  785. elseif (strpos($line_item['type'], 'subtotal') === FALSE) {
  786. $obj = $request->item[] = new stdClass();
  787. $obj->productName = $line_item['title'];
  788. $obj->unitPrice = $line_item['amount'];
  789. $obj->quantity = 1;
  790. $obj->productSKU = $line_item['type'] . '_' . $line_item['line_item_id'];
  791. $obj->id = $counter;
  792. $counter++;
  793. }
  794. }
  795. }
  796. // Add the total order discount into the request.
  797. if ($discount_amount != 0) {
  798. $request->purchaseTotals->discountAmount = $discount_amount;
  799. }
  800. // Add business rules.
  801. $business = new stdClass();
  802. $business->ignoreAVSResult = variable_get('uc_cybersource_avs', 'true') == 'true' ? 'false' : 'true';
  803. $request->businessRules = $business;
  804. // Send the request to CyberSource and get the reply.
  805. $reply = $soapClient->runTransaction($request);
  806. }
  807. catch (SoapFault $exception) {
  808. // Log and display errors if Ubercart is unable to connect via SOAP.
  809. watchdog('uc_cybersource', 'Unable to connect to CyberSource via SOAP.', array(), WATCHDOG_ERROR);
  810. 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');
  811. }
  812. // Process a reply from CyberSource.
  813. if (isset($reply)) {
  814. $types = uc_credit_transaction_types();
  815. // Create the order and payment ledger comments.
  816. $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' => _uc_cybersource_parse_reason_code($reply->reasonCode)));
  817. $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));
  818. if (!empty($reply->ccAuthReply->avsCode)) {
  819. $o_comment .= '<br />' . t('<b>AVS:</b> @avs', array('@avs' => _uc_cybersource_parse_avs_code($reply->ccAuthReply->avsCode)));
  820. $p_comment .= t(', AVS: @avs', array('@avs' => $reply->ccAuthReply->avsCode));
  821. }
  822. if (!empty($reply->ccAuthReply->cvCode)) {
  823. $o_comment .= '<br />' . t('<b>CVV:</b> @cvv', array('@cvv' => _uc_cybersource_parse_cvv_code($reply->ccAuthReply->cvCode)));
  824. $p_comment .= t(', CVV: @cvv', array('@cvv' => $reply->ccAuthReply->cvCode));
  825. }
  826. uc_order_comment_save($order->order_id, $user->uid, $o_comment, 'admin');
  827. // Store the subscription ID if one was created.
  828. if (isset($reply->paySubscriptionCreateReply)) {
  829. // If the create request was successful...
  830. if ($reply->paySubscriptionCreateReply->reasonCode == '100') {
  831. $id = $reply->paySubscriptionCreateReply->subscriptionID;
  832. // Save the subscription ID to the order's data array.
  833. $order->data = uc_credit_log_reference($order->order_id, $id, $order->payment_details['cc_number']);
  834. uc_order_comment_save($order->order_id, 0, t('<b>CyberSource profile created.</b><br /><b>Subscription ID:</b> @id', array('@id' => $id)), 'admin');
  835. }
  836. else {
  837. 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');
  838. }
  839. }
  840. if ($reply->decision == 'ACCEPT') {
  841. $result = array(
  842. 'success' => TRUE,
  843. 'comment' => $p_comment,
  844. 'message' => $o_comment,
  845. 'uid' => $user->uid,
  846. 'data' => array('module' => 'uc_cybersource', 'txn_type' => $data['txn_type'], 'request_id' => $reply->requestID),
  847. );
  848. // If this was an authorization only transaction...
  849. if ($data['txn_type'] == UC_CREDIT_AUTH_ONLY) {
  850. // Log the authorization to the order.
  851. $order->data = uc_credit_log_authorization($order->order_id, $reply->requestID, $amount);
  852. // Add the request token associated with the request ID.
  853. $order->data['cybersource'][$reply->requestID] = $reply->requestToken;
  854. // Save the updated data array to the database.
  855. db_update('uc_orders')
  856. ->fields(array('data' => serialize($order->data)))
  857. ->condition('order_id', $order->order_id)
  858. ->execute();
  859. }
  860. elseif ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
  861. uc_credit_log_prior_auth_capture($order->order_id, $data['auth_id']);
  862. }
  863. }
  864. else {
  865. $result = array(
  866. 'success' => FALSE,
  867. 'comment' => $p_comment,
  868. 'message' => $o_comment,
  869. 'uid' => $user->uid,
  870. );
  871. }
  872. }
  873. else {
  874. $result = array(
  875. 'success' => FALSE,
  876. 'message' => t('No response returned from CyberSource.'),
  877. );
  878. }
  879. // Don't log this as a payment if money wasn't actually captured.
  880. if (in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY))) {
  881. $result['log_payment'] = FALSE;
  882. }
  883. return $result;
  884. }
  885. /**
  886. * Displays the taxes for an order.
  887. */
  888. function uc_cybersource_tax_test($order) {
  889. // Fetch the taxes for the order.
  890. $data = uc_cybersource_uc_calculate_tax($order);
  891. // Build an item list for the taxes.
  892. $items = array();
  893. foreach ($data as $tax) {
  894. $items[] = t('@tax: @amount', array('@tax' => $tax['name'], '@amount' => uc_currency_format($tax['amount'])));
  895. }
  896. // Display a message if there are no taxes.
  897. if (empty($items)) {
  898. $items[] = t('No taxes returned for this order.');
  899. }
  900. return array(
  901. '#theme' => 'item_list',
  902. '#items' => $items,
  903. );
  904. }
  905. /**
  906. * Implements hook_uc_calculate_tax().
  907. *
  908. * Calculates taxes for an order using CyberSource's tax service.
  909. *
  910. * @param $order
  911. * An order object with address and product information.
  912. *
  913. * @return
  914. * An array of tax line item objects with the fields 'id', 'name', and
  915. * 'amount', keyed by id.
  916. */
  917. function uc_cybersource_uc_calculate_tax($order) {
  918. // Kick out if the tax service is not enabled.
  919. if (!variable_set('uc_cybersource_soap_tax_calculate', FALSE)) {
  920. return array();
  921. }
  922. if (!is_object($order)) {
  923. return array();
  924. }
  925. // Include the SOAP helper file.
  926. module_load_include('inc', 'uc_cybersource', 'uc_cybersource.soap');
  927. global $user;
  928. // Set the URL for the CyberSource SOAP Toolkit API WSDL.
  929. if (variable_get('uc_cybersource_server', 'test') == 'test') {
  930. $url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
  931. }
  932. else {
  933. $url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.38.wsdl';
  934. }
  935. // Variable currency... not used at the moment.
  936. $currency = variable_get('uc_cybersource_currency', 'usd');
  937. $billing_country = uc_get_country_data(array('country_id' => $order->billing_country));
  938. $delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
  939. try {
  940. $soapClient = new CyberSourceSoapClient($url, array());
  941. $login = _uc_cybersource_soap_login_data();
  942. // Create the request with some meta data.
  943. $request = new stdClass();
  944. $request->merchantID = $login['merchant_id'];
  945. $request->merchantReferenceCode = $order->order_id;
  946. $request->clientLibrary = 'PHP';
  947. $request->clientLibraryVersion = phpversion();
  948. $request->clientEnvironment = php_uname();
  949. // Add the billing information.
  950. $billTo = new stdClass();
  951. $billTo->firstName = $order->billing_first_name;
  952. $billTo->lastName = $order->billing_last_name;
  953. $billTo->street1 = $order->billing_street1;
  954. if ($order->billing_street2) {
  955. $billTo->street2 = $order->billing_street2;
  956. }
  957. $billTo->city = $order->billing_city;
  958. $billTo->state = uc_get_zone_code($order->billing_zone);
  959. $billTo->postalCode = $order->billing_postal_code;
  960. $billTo->country = $billing_country[0]['country_iso_code_2'];
  961. if ($order->billing_phone) {
  962. $billTo->phoneNumber = $order->billing_phone;
  963. }
  964. $billTo->email = $order->primary_email;
  965. $billTo->customerID = $order->uid;
  966. $request->billTo = $billTo;
  967. // Add the shipping information.
  968. $shipTo = new stdClass();
  969. $shipTo->firstName = $order->delivery_first_name;
  970. $shipTo->lastName = $order->delivery_last_name;
  971. $shipTo->street1 = $order->delivery_street1;
  972. if ($order->billing_street2) {
  973. $shipTo->street2 = $order->delivery_street2;
  974. }
  975. $shipTo->city = $order->delivery_city;
  976. $shipTo->state = uc_get_zone_code($order->delivery_zone);
  977. $shipTo->postalCode = $order->delivery_postal_code;
  978. $shipTo->country = $delivery_country[0]['country_iso_code_2'];
  979. $shipTo->email = $order->primary_email;
  980. $request->shipTo = $shipTo;
  981. // Add the company's ship from information.
  982. $shipFrom = new stdClass();
  983. $shipFrom->firstName = variable_get('cs_ship_from_first_name', '');
  984. $shipFrom->lastName = variable_get('cs_ship_from_last_name', '');
  985. $shipFrom->street1 = variable_get('cs_ship_from_street1', '');
  986. $shipFrom->city = variable_get('cs_ship_from_city', '');
  987. $shipFrom->state = variable_get('cs_ship_from_zone', '');
  988. $shipFrom->postalCode = variable_get('cs_ship_from_postal_code', '');
  989. $shipFrom->country = variable_get('cs_ship_from_country', '');
  990. $shipFrom->email = variable_get('cs_ship_from_email', '');
  991. $request->shipFrom = $shipFrom;
  992. // TaxService
  993. // US product codes:
  994. // 70.280: Software Training Services
  995. // 81112201.121: Business Use Services and Upgrades via Elect Dnld
  996. // TODO: product code, international product code
  997. // TODO: invoiceHeader->invoiceDate: to get correct refund amounts
  998. // TODO: VAT
  999. $taxService = new stdClass();
  1000. $taxService->nexus = 'MA CA';
  1001. $taxService->orderOriginCity = $taxService->orderAcceptanceCity = $shipFrom->city;
  1002. $taxService->orderOriginCountry = $taxService->orderAcceptanceCountry = $shipFrom->country;
  1003. $taxService->orderOriginState = $taxService->orderAcceptanceState = $shipFrom->state;
  1004. $taxService->orderOriginPostalCode = $taxService->orderAcceptancePostalCode = $shipFrom->postalCode;
  1005. $taxService->sellerRegistration = 'XXX TODO';
  1006. $taxService->run = 'true';
  1007. $request->taxService = $taxService;
  1008. // Add the order total information.
  1009. $purchaseTotals = new stdClass();
  1010. $purchaseTotals->currency = $currency;
  1011. // Add the products to the request.
  1012. $request->item = array();
  1013. $counter = 0;
  1014. // Add the products to the item array.
  1015. foreach ($order->products as $product) {
  1016. $obj = $request->item[] = new stdClass();
  1017. $obj->productName = $product->title;
  1018. $obj->unitPrice = $product->price;
  1019. $obj->quantity = $product->qty;
  1020. $obj->productSKU = $product->model;
  1021. $obj->productCode = 'default';
  1022. $obj->id = $counter;
  1023. $counter++;
  1024. }
  1025. // drupal_set_message('<pre>Request: ' . print_r($request, TRUE) . '</pre>');
  1026. // Send the request to CyberSource and get the reply.
  1027. $reply = $soapClient->runTransaction($request);
  1028. // drupal_set_message('<pre>Reply: ' . print_r($reply, TRUE) . '</pre>');
  1029. }
  1030. catch (SoapFault $exception) {
  1031. // Log and display errors if Ubercart is unable to connect via SOAP.
  1032. watchdog('uc_cybersource', 'Unable to connect to CyberSource via SOAP.', array(), WATCHDOG_ERROR);
  1033. 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');
  1034. }
  1035. // Process a reply from CyberSource.
  1036. if (isset($reply)) {
  1037. $result = array();
  1038. if ($reply->reasonCode == '100') {
  1039. // Add a city tax if applicable.
  1040. if (floatval($reply->taxReply->totalCityTaxAmount) > 0) {
  1041. $result['city'] = (object) array(
  1042. 'id' => 'city',
  1043. 'name' => t('@city city tax', array('@city' => floatval($reply->taxReply->city))),
  1044. 'amount' => floatval($reply->taxReply->totalCityTaxAmount),
  1045. );
  1046. }
  1047. // Add a county tax if applicable.
  1048. if (floatval($reply->taxReply->totalCountyTaxAmount) > 0) {
  1049. $result['county'] = (object) array(
  1050. 'id' => 'county',
  1051. 'name' => t('County tax'),
  1052. 'amount' => floatval($reply->taxReply->totalCountryTaxAmount),
  1053. );
  1054. }
  1055. // Add a district tax if applicable.
  1056. if (floatval($reply->taxReply->totalDistrictTaxAmount) > 0) {
  1057. $result['district'] = (object) array(
  1058. 'id' => 'district',
  1059. 'name' => t('District tax'),
  1060. 'amount' => floatval($reply->taxReply->totalDistrictTaxAmount),
  1061. );
  1062. }
  1063. // Add a state tax if applicable.
  1064. if (floatval($reply->taxReply->totalStateTaxAmount) > 0) {
  1065. $result['state'] = (object) array(
  1066. 'id' => 'state',
  1067. 'name' => t('@state state tax', array('@state' => $reply->taxReply->state)),
  1068. 'amount' => floatval($reply->taxReply->totalStateTaxAmount),
  1069. );
  1070. }
  1071. // Verify that the component taxes equal the total.
  1072. $total = 0;
  1073. foreach ($result as $tax) {
  1074. $total += $tax['amount'];
  1075. }
  1076. // If it doesn't, log an error message and simply return the total.
  1077. if ($total != floatval($reply->taxReply->totalTaxAmount)) {
  1078. watchdog('uc_cybersource', '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);
  1079. $result = array(
  1080. 'total' => (object) array(
  1081. 'id' => 'total',
  1082. 'name' => t('Tax'),
  1083. 'amount' => floatval($reply->taxReply->totalTaxAmount),
  1084. ),
  1085. );
  1086. }
  1087. }
  1088. else {
  1089. watchdog('uc_cybersource', 'Attempted to calculate taxes failed for order @order_id - reason @code', array('@order_id' => $order->order_id, '@code' => $reply->reasonCode), WATCHDOG_ERROR);
  1090. }
  1091. }
  1092. else {
  1093. watchdog('uc_cybersource', 'Attempted to calculate taxes failed for order @order_id. No response returned from CyberSource.', array('@order_id' => $order->order_id), WATCHDOG_ERROR);
  1094. $result = array();
  1095. }
  1096. /**
  1097. * Code for the Simple Order API that was never completed.
  1098. *
  1099. * else {
  1100. * $config = cybs_load_config('cybs.ini');
  1101. * if (variable_get('uc_cybersource_server', 'test') == 'test') {
  1102. * $config['sendToProduction'] = 'false';
  1103. * }
  1104. *
  1105. * $request['ccAuthService_run'] = 'true';
  1106. * if (variable_get('uc_cybersource_hop_transaction_type', 'sale') == 'sale') {
  1107. * $request['ccCaptureService_run'] = 'true';
  1108. * }
  1109. * $request['merchantReferenceCode'] = $order_id;
  1110. * $request['purchaseTotals_currency'] = 'USD';
  1111. * $request['purchaseTotals_grandTotalAmount'] = $amount;
  1112. *
  1113. * drupal_set_message('<pre>' . print_r($config, TRUE) . '</pre>');
  1114. * drupal_set_message('<pre>' . print_r($request, TRUE) . '</pre>');
  1115. * }
  1116. */
  1117. return $result;
  1118. }
  1119. /**
  1120. * Returns an array with the SOAP Merchant ID and Transaction key.
  1121. */
  1122. function _uc_cybersource_soap_login_data() {
  1123. static $data;
  1124. if (!empty($data)) {
  1125. return $data;
  1126. }
  1127. $merchant_id = variable_get('uc_cybersource_soap_merchant_id', '');
  1128. $transaction_key = variable_get('uc_cybersource_soap_transaction_key', '');
  1129. // If CC encryption has been configured properly.
  1130. if ($key = uc_credit_encryption_key()) {
  1131. // Setup our encryption object.
  1132. $crypt = new UbercartEncryption();
  1133. // Decrypt the Merchant ID and Transaction key.
  1134. if (!empty($merchant_id)) {
  1135. $merchant_id = $crypt->decrypt($key, $merchant_id);
  1136. }
  1137. if (!empty($transaction_key)) {
  1138. $transaction_key = $crypt->decrypt($key, $transaction_key);
  1139. }
  1140. // Store any errors.
  1141. uc_store_encryption_errors($crypt, 'uc_cybersource');
  1142. }
  1143. $data = array(
  1144. 'merchant_id' => $merchant_id,
  1145. 'transaction_key' => $transaction_key,
  1146. );
  1147. return $data;
  1148. }
  1149. /**
  1150. * Returns the code for the credit card type.
  1151. */
  1152. function _uc_cybersource_card_type($cc_number) {
  1153. switch (substr(strval($cc_number), 0, 1)) {
  1154. case '3':
  1155. if (strlen($cc_number) == 14) {
  1156. return '005'; // Diners Club
  1157. }
  1158. elseif (strlen($cc_number) == 15) {
  1159. return '003'; // AmEx
  1160. }
  1161. else {
  1162. return '007'; // JCB
  1163. }
  1164. case '4':
  1165. return '001'; // Visa
  1166. case '5':
  1167. return '002'; // MasterCard
  1168. case '6':
  1169. return '004'; // Discover
  1170. }
  1171. return FALSE;
  1172. }
  1173. /**
  1174. * Returns the meaning of the reason code given by CyberSource.
  1175. */
  1176. function _uc_cybersource_parse_reason_code($code) {
  1177. switch ($code) {
  1178. case '100':
  1179. return t('Successful transaction.');
  1180. case '102':
  1181. 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.');
  1182. case '150':
  1183. return t('<b>Error:</b> General system failure.<br /><b>Possible action:</b> Wait a few minutes and resend the request.');
  1184. case '151':
  1185. 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.');
  1186. case '152':
  1187. 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.');
  1188. case '200':
  1189. 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.');
  1190. case '202':
  1191. return t('Expired card.<br /><b>Possible action:</b> Request a different card or other form of payment.');
  1192. case '203':
  1193. 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.');
  1194. case '204':
  1195. return t('Insufficient funds in the account.<br /><b>Possible action:</b> Request a different card or other form of payment.');
  1196. case '205':
  1197. 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.");
  1198. case '207':
  1199. return t('Issuing bank unavailable.<br /><b>Possible action:</b> Wait a few minutes and resend the request.');
  1200. case '208':
  1201. 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.');
  1202. case '210':
  1203. return t('The card has reached the credit limit.<br /><b>Possible action:</b> Request a different card or other form of payment.');
  1204. case '211':
  1205. return t('The card verification number is invalid.<br /><b>Possible action:</b> Request a different card or other form of payment.');
  1206. case '220':
  1207. 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.");
  1208. case '221':
  1209. 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.');
  1210. case '222':
  1211. return t("The customer's bank account is frozen.<br /><b>Possible action:</b> Review the order or request a different form of payment.");
  1212. case '230':
  1213. 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.');
  1214. case '231':
  1215. return t('Invalid account number.<br /><b>Possible action:</b> Request a different card or other form of payment.');
  1216. case '232':
  1217. 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.');
  1218. case '233':
  1219. 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.');
  1220. case '234':
  1221. 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.');
  1222. case '236':
  1223. return t('Processor failure.<br /><b>Possible action:</b> Possible action: Wait a few minutes and resend the request.');
  1224. case '240':
  1225. 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.');
  1226. case '250':
  1227. 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.');
  1228. case '475':
  1229. return t('The customer is enrolled in payer authentication.<br /><b>Possible action:</b> Authenticate the cardholder before continuing with the transaction.');
  1230. case '476':
  1231. return t("The customer cannot be authenticated.<br /><b>Possible action:</b> Review the customer's order.");
  1232. case '520':
  1233. 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.');
  1234. }
  1235. }
  1236. /**
  1237. * Returns the meaning of the code for Address Verification.
  1238. */
  1239. function _uc_cybersource_parse_avs_code($code) {
  1240. switch ($code) {
  1241. case 'A':
  1242. return t('Street address matches, but 5- and 9-digit postal codes do not match.');
  1243. case 'B':
  1244. return t('Street address matches, but postal code not verified. Returned only for non U.S.-issued Visa cards.');
  1245. case 'C':
  1246. return t('Street address and postal code do not match. Returned only for non U.S.-issued Visa cards.');
  1247. case 'D':
  1248. return t('Street address and postal code match. Returned only for non U.S.-issued Visa cards.');
  1249. case 'E':
  1250. return t('AVS data is invalid, or AVS is not allowed for this card type.');
  1251. case 'F':
  1252. return t("Card member's name does not match, but postal code matches. Returned only for the American Express card type.");
  1253. case 'G':
  1254. return t('Non-U.S. issuing bank does not support AVS.');
  1255. case 'H':
  1256. return t("Card member's name does not match. Street address and postal code match. Returned only for the American Express card type.");
  1257. case 'I':
  1258. return t('Address not verified. Returned only for non U.S.-issued Visa cards.');
  1259. case 'K':
  1260. 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.");
  1261. case 'L':
  1262. 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");
  1263. case 'N':
  1264. 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.");
  1265. case 'O':
  1266. 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.");
  1267. case 'P':
  1268. return t('Postal code matches, but street address not verified. Returned only for non-U.S.-issued Visa cards.');
  1269. case 'R':
  1270. return t('System unavailable.');
  1271. case 'S':
  1272. return t('U.S.-issuing bank does not support AVS.');
  1273. case 'T':
  1274. return t("Card member's name does not match, but street address matches. Returned only for the American Express card type.");
  1275. case 'U':
  1276. 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.');
  1277. case 'W':
  1278. return t('Street address does not match, but 9-digit postal code matches.');
  1279. case 'X':
  1280. return t('Exact match. Street address and 9-digit postal code match.');
  1281. case 'Y':
  1282. return t('Exact match. Street address and 5-digit postal code match.');
  1283. case 'Z':
  1284. return t('Street address does not match, but 5-digit postal code matches.');
  1285. case '1':
  1286. return t('AVS is not supported for this processor or card type.');
  1287. case '2':
  1288. return t('The processor returned an unrecognized value for the AVS response.');
  1289. }
  1290. }
  1291. /**
  1292. * Returns the meaning of the code sent back for CVV verification.
  1293. */
  1294. function _uc_cybersource_parse_cvv_code($code) {
  1295. switch ($code) {
  1296. case 'D':
  1297. return t('Transaction determined suspicious by issuing bank.');
  1298. case 'I':
  1299. return t("Card verification number failed processor's data validation check.");
  1300. case 'M':
  1301. return t('Card verification number matched.');
  1302. case 'N':
  1303. return t('Card verification number not matched.');
  1304. case 'P':
  1305. return t('Card verification number not processed by processor for unspecified reason.');
  1306. case 'S':
  1307. return t('Card verification number is on the card but was not included in the request.');
  1308. case 'U':
  1309. return t('Card verification is not supported by the issuing bank.');
  1310. case 'X':
  1311. return t('Card verification is not supported by the card association.');
  1312. case '1':
  1313. return t('Card verification is not supported for this processor or card type.');
  1314. case '2':
  1315. return t('Unrecognized result code returned by processor for card verification response.');
  1316. case '3':
  1317. return t('No result code returned by processor.');
  1318. }
  1319. }