uc_authorizenet.module 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. <?php
  2. /**
  3. * @file
  4. * Processes payments using Authorize.net. Supports AIM and ARB.
  5. */
  6. define('UC_AUTHORIZENET_TEST_GATEWAY_URL', 'https://test.authorize.net/gateway/transact.dll');
  7. define('UC_AUTHORIZENET_LIVE_GATEWAY_URL', 'https://secure.authorize.net/gateway/transact.dll');
  8. /**
  9. * Implements hook_menu().
  10. */
  11. function uc_authorizenet_menu() {
  12. $items = array();
  13. $items['authnet/silent-post'] = array(
  14. 'page callback' => 'uc_authorizenet_silent_post',
  15. 'access callback' => TRUE,
  16. 'type' => MENU_CALLBACK,
  17. 'file' => 'uc_authorizenet.pages.inc',
  18. );
  19. return $items;
  20. }
  21. /**
  22. * Implements hook_uc_payment_gateway().
  23. */
  24. function uc_authorizenet_uc_payment_gateway() {
  25. $gateways['authorizenet'] = array(
  26. 'title' => t('Authorize.net'),
  27. 'description' => t('Process credit card payments using the AIM service of Authorize.net.'),
  28. 'settings' => 'uc_authorizenet_settings_form',
  29. 'credit' => 'uc_authorizenet_charge',
  30. 'credit_txn_types' => array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_PRIOR_AUTH_CAPTURE, UC_CREDIT_AUTH_CAPTURE, UC_CREDIT_REFERENCE_SET, UC_CREDIT_REFERENCE_TXN),
  31. );
  32. return $gateways;
  33. }
  34. /**
  35. * Callback for payment gateway settings.
  36. */
  37. function uc_authorizenet_settings_form($form, &$form_state) {
  38. $login_data = _uc_authorizenet_login_data();
  39. // Allow admin to set duplicate window.
  40. $form['uc_authnet_duplicate_window'] = array(
  41. '#type' => 'select',
  42. '#title' => t('Duplicate window'),
  43. '#description' => t('Blocks submission of duplicate transactions within the specified window. Defaults to 120 seconds.'),
  44. '#default_value' => variable_get('uc_authnet_duplicate_window', 120),
  45. '#options' => drupal_map_assoc(array(0, 15, 30, 45, 60, 75, 90, 105, 120)),
  46. );
  47. $form['api_id_key'] = array(
  48. '#type' => 'fieldset',
  49. '#title' => t('API Login ID and Transaction Key'),
  50. '#description' => t('This information is required for Ubercart to interact with your payment gateway account. It is different from your login ID and password and may be found through your account settings page. Do not change the gateway URLs unless you are using this module with an Authorize.net-compatible gateway that requires different URLs.'),
  51. );
  52. $form['api_id_key']['uc_authnet_api_login_id'] = array(
  53. '#type' => 'textfield',
  54. '#title' => t('API Login ID'),
  55. '#default_value' => variable_get('uc_authnet_api_login_id', ''),
  56. );
  57. $form['api_id_key']['uc_authnet_api_transaction_key'] = array(
  58. '#type' => 'textfield',
  59. '#title' => t('Transaction Key'),
  60. '#default_value' => variable_get('uc_authnet_api_transaction_key', ''),
  61. );
  62. $form['api_id_key']['uc_authnet_api_test_gateway_url'] = array(
  63. '#type' => 'textfield',
  64. '#title' => t('Authorize.net Test Gateway URL'),
  65. '#default_value' => variable_get('uc_authnet_api_test_gateway_url', UC_AUTHORIZENET_TEST_GATEWAY_URL),
  66. );
  67. $form['api_id_key']['uc_authnet_api_live_gateway_url'] = array(
  68. '#type' => 'textfield',
  69. '#title' => t('Authorize.net Live Gateway URL'),
  70. '#default_value' => variable_get('uc_authnet_api_live_gateway_url', UC_AUTHORIZENET_LIVE_GATEWAY_URL),
  71. );
  72. $form['aim_settings'] = array(
  73. '#type' => 'fieldset',
  74. '#title' => t('AIM settings'),
  75. '#description' => t('These settings pertain to the Authorize.Net AIM payment method for card not present transactions.'),
  76. );
  77. $form['aim_settings']['uc_authnet_aim_txn_mode'] = array(
  78. '#type' => 'radios',
  79. '#title' => t('Transaction mode'),
  80. '#description' => t('Only specify a developer test account if you login to your account through https://test.authorize.net.<br />Adjust to live transactions when you are ready to start processing real payments.'),
  81. '#options' => array(
  82. 'live' => t('Live transactions in a live account'),
  83. 'live_test' => t('Test transactions in a live account'),
  84. 'developer_test' => t('Developer test account transactions'),
  85. ),
  86. '#default_value' => variable_get('uc_authnet_aim_txn_mode', 'live_test'),
  87. );
  88. $form['aim_settings']['uc_authnet_aim_email_customer'] = array(
  89. '#type' => 'checkbox',
  90. '#title' => t('Tell Authorize.net to e-mail the customer a receipt based on your account settings.'),
  91. '#default_value' => variable_get('uc_authnet_aim_email_customer', FALSE),
  92. );
  93. $form['aim_settings']['uc_authnet_response_debug'] = array(
  94. '#type' => 'checkbox',
  95. '#title' => t('Log full API response messages from Authorize.net for debugging.'),
  96. '#default_value' => variable_get('uc_authnet_response_debug', FALSE),
  97. );
  98. $form['arb_settings'] = array(
  99. '#type' => 'fieldset',
  100. '#title' => t('ARB settings'),
  101. '#description' => t('These settings pertain to the Authorize.Net Automated Recurring Billing service.'),
  102. );
  103. $form['arb_settings']['uc_authnet_arb_mode'] = array(
  104. '#type' => 'radios',
  105. '#title' => t('Transaction mode'),
  106. '#description' => t('Only specify developer mode if you login to your account through https://test.authorize.net.<br />Adjust to production mode when you are ready to start processing real recurring fees.'),
  107. '#options' => array(
  108. 'production' => t('Production'),
  109. 'developer' => t('Developer test'),
  110. 'disabled' => t('Disabled'),
  111. ),
  112. '#default_value' => variable_get('uc_authnet_arb_mode', 'disabled'),
  113. );
  114. $form['arb_settings']['uc_authnet_md5_hash'] = array(
  115. '#type' => 'textfield',
  116. '#title' => t('MD5 Hash'),
  117. '#description' => t('<b>Note:</b> You must first configure credit card encryption before setting this.<br />Enter the value here you entered in your Auth.Net account settings.'),
  118. '#default_value' => $login_data['md5_hash'],
  119. '#access' => user_access('administer credit cards'),
  120. );
  121. $form['arb_settings']['uc_authnet_report_arb_post'] = array(
  122. '#type' => 'checkbox',
  123. '#title' => t('Log reported ARB payments in watchdog.'),
  124. '#description' => t('Make sure you have set your Silent POST URL in Authorize.Net to @url.', array('@url' => url('authnet/silent-post', array('absolute' => TRUE)))),
  125. '#default_value' => variable_get('uc_authnet_report_arb_post', FALSE),
  126. );
  127. $form['cim_settings'] = array(
  128. '#type' => 'fieldset',
  129. '#title' => t('CIM settings'),
  130. '#description' => t('These settings pertain to the Authorize.Net Customer Information Management service.'),
  131. );
  132. $form['cim_settings']['uc_authnet_cim_profile'] = array(
  133. '#type' => 'checkbox',
  134. '#title' => t('Always create a CIM profile for securely storing CC info for later use.'),
  135. '#default_value' => variable_get('uc_authnet_cim_profile', FALSE),
  136. );
  137. $form['cim_settings']['uc_authnet_cim_mode'] = array(
  138. '#type' => 'radios',
  139. '#title' => t('Transaction mode'),
  140. '#description' => t('Only specify a developer test account if you login to your account through https://test.authorize.net.<br />Adjust to live transactions when you are ready to start processing real payments.'),
  141. '#options' => array(
  142. 'production' => t('Production'),
  143. 'developer' => t('Developer test'),
  144. 'disabled' => t('Disabled'),
  145. ),
  146. '#default_value' => variable_get('uc_authnet_cim_mode', 'disabled'),
  147. );
  148. return $form;
  149. }
  150. /**
  151. * Implements hook_form_FORM_ID_alter() for uc_payment_method_settings_form().
  152. */
  153. function uc_authorizenet_form_uc_payment_method_settings_form_alter(&$form, &$form_state) {
  154. if ($form_state['build_info']['args'][0] == 'credit') {
  155. $form['#submit'][] = 'uc_authorizenet_payment_gateway_settings_submit';
  156. }
  157. }
  158. /**
  159. * Submit handler for payment gateway settings form to encrypt fields.
  160. */
  161. function uc_authorizenet_payment_gateway_settings_submit($form, &$form_state) {
  162. // If CC encryption has been configured properly.
  163. if ($key = uc_credit_encryption_key()) {
  164. // Setup our encryption object.
  165. $crypt = new UbercartEncryption();
  166. // Encrypt the Login ID, Transaction key, and MD5 Hash.
  167. if (!empty($form_state['values']['uc_authnet_md5_hash'])) {
  168. variable_set('uc_authnet_md5_hash', $crypt->encrypt($key, $form_state['values']['uc_authnet_md5_hash']));
  169. }
  170. // Store any errors.
  171. uc_store_encryption_errors($crypt, 'uc_authorizenet');
  172. }
  173. }
  174. /**
  175. * Main handler for processing credit card transactions.
  176. */
  177. function uc_authorizenet_charge($order_id, $amount, $data) {
  178. // Load the order.
  179. $order = uc_order_load($order_id);
  180. // Perform the appropriate action based on the transaction type.
  181. switch ($data['txn_type']) {
  182. // Reference transactions are handled through Authorize.Net's CIM.
  183. case UC_CREDIT_REFERENCE_TXN:
  184. return _uc_authorizenet_cim_profile_charge($order, $amount, $data);
  185. // Set a reference only.
  186. case UC_CREDIT_REFERENCE_SET:
  187. // Return the error message if this failed.
  188. if ($message = _uc_authorizenet_cim_profile_create($order)) {
  189. return array('success' => FALSE, 'message' => $message);
  190. }
  191. else {
  192. return array('success' => TRUE, 'log_payment' => FALSE, 'message' => t('New customer profile created successfully at Authorize.Net.'));
  193. }
  194. // Accommodate all other transaction types.
  195. default:
  196. return _uc_authorizenet_charge($order, $amount, $data);
  197. }
  198. }
  199. /**
  200. * Creates a CIM profile using an order's data.
  201. */
  202. function _uc_authorizenet_cim_profile_create($order) {
  203. $server = variable_get('uc_authnet_cim_mode', 'disabled');
  204. // Help build the request.
  205. $request = _uc_authorizenet_cim_profile_create_request($order);
  206. // Request a profile from auth.net.
  207. $xml = _uc_authorizenet_xml_api_wrapper('createCustomerProfileRequest', _uc_authorizenet_array_to_xml($request));
  208. // Parse the response.
  209. $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  210. if ($response['resultCode'] == 'Error') {
  211. uc_order_comment_save($order->order_id, 0, t('Authorize.Net: Creating CIM profile failed.<br />@error - @text', array('@error' => $response['code'], '@text' => $response['text'])), 'admin');
  212. return $response['text'];
  213. }
  214. else {
  215. uc_order_comment_save($order->order_id, 0, t('Authorize.Net: CIM profile created - @id', array('@id' => $response['customerProfileId'])));
  216. }
  217. // Save the new credit reference to the db.
  218. $order->data = uc_credit_log_reference($order->order_id, $response['customerProfileId'], $order->payment_details['cc_number']);
  219. return '';
  220. }
  221. /**
  222. * Helper to create the CIM profile creation request.
  223. */
  224. function _uc_authorizenet_cim_profile_create_request($order) {
  225. return array(
  226. 'refId' => substr($order->order_id . '-' . REQUEST_TIME, 0, 20),
  227. 'profile' => array(
  228. 'merchantCustomerId' => substr($order->uid, 0, 20),
  229. 'description' => substr(t('Order @order taking place at @date', array('@order' => $order->order_id, '@date' => format_date(REQUEST_TIME))), 0, 255),
  230. 'email' => substr($order->primary_email, 0, 255),
  231. 'paymentProfiles' => array(
  232. 'billTo' => _uc_authorize_cim_xml_billto($order),
  233. 'payment' => array(
  234. 'creditCard' => array(
  235. 'cardNumber' => $order->payment_details['cc_number'],
  236. 'expirationDate' => $order->payment_details['cc_exp_year'] . '-' . str_pad($order->payment_details['cc_exp_month'], 2, '0', STR_PAD_LEFT),
  237. ),
  238. ),
  239. ),
  240. 'shipToList' => _uc_authorize_cim_xml_shipto($order),
  241. ),
  242. );
  243. }
  244. /**
  245. * Uses a reference to charge to a CIM profile.
  246. */
  247. function _uc_authorizenet_cim_profile_charge($order, $amount, $data) {
  248. global $user;
  249. $server = variable_get('uc_authnet_cim_mode', 'disabled');
  250. // Help build the request.
  251. $request = _uc_authorizenet_cim_profile_charge_request($order, $amount, $data);
  252. // Check error state.
  253. if (array_key_exists('errorCode', $request)) {
  254. $comment[] = $request['text'];
  255. $result = array(
  256. 'success' => FALSE,
  257. );
  258. }
  259. // Request went off smooth.
  260. else {
  261. // Request a profile from auth.net.
  262. $xml = _uc_authorizenet_xml_api_wrapper('createCustomerProfileTransactionRequest', _uc_authorizenet_array_to_xml($request));
  263. // Parse the response.
  264. $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  265. // Error state.
  266. if ($response['resultCode'] == 'Error') {
  267. $result = array(
  268. 'success' => FALSE,
  269. );
  270. $comment[] = '(' . $response['resultCode'] . ': ' . $response['text'] . ')';
  271. }
  272. // Transaction succeeded.
  273. else {
  274. $result = array(
  275. 'success' => TRUE,
  276. );
  277. // Build info message.
  278. $types = uc_credit_transaction_types();
  279. $comment[] = t('<b>@type:</b> @amount', array('@type' => $types[$data['txn_type']], '@amount' => uc_currency_format($amount)));
  280. }
  281. // Save a comment to the order.
  282. uc_order_comment_save($order->order_id, $user->uid, implode('<br />', $comment), 'admin');
  283. }
  284. // Build the response to the payment gateway API.
  285. return $result + array(
  286. 'comment' => implode(', ', $comment),
  287. 'message' => implode('<br />', $comment),
  288. 'uid' => $user->uid,
  289. );
  290. }
  291. /**
  292. * Helper for building the request for a CIM profile charge.
  293. */
  294. function _uc_authorizenet_cim_profile_charge_request($order, $amount, $data) {
  295. $profile = _uc_authorizenet_cim_profile_get($order, $data['ref_id']);
  296. if ($profile['resultCode'] == 'Error') {
  297. return $profile;
  298. }
  299. else {
  300. return array(
  301. 'refId' => substr($order->order_id . '-' . REQUEST_TIME, 0, 20),
  302. 'transaction' => array(
  303. 'profileTransAuthCapture' => array(
  304. 'amount' => uc_currency_format($amount, FALSE, FALSE, '.'),
  305. 'customerProfileId' => $profile['customerProfileId'],
  306. 'customerPaymentProfileId' => $profile['customerPaymentProfileId'],
  307. 'customerShippingAddressId' => $profile['customerAddressId'],
  308. 'order' => array(
  309. 'invoiceNumber' => $order->order_id,
  310. ),
  311. ),
  312. ),
  313. );
  314. }
  315. }
  316. /**
  317. * Gets a CIM profile stored at Authorize.Net.
  318. */
  319. function _uc_authorizenet_cim_profile_get($order, $profile_id) {
  320. $server = variable_get('uc_authnet_cim_mode', 'disabled');
  321. $request = array(
  322. 'customerProfileId' => $profile_id,
  323. );
  324. // Request a profile from auth.net.
  325. $xml = _uc_authorizenet_xml_api_wrapper('getCustomerProfileRequest', _uc_authorizenet_array_to_xml($request));
  326. // Parse the response.
  327. $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  328. return $response;
  329. }
  330. /**
  331. * Gets a CIM payment profile stored at auth.net.
  332. */
  333. function _uc_authorizenet_cim_payment_profile_get($order, $profile_id, $payment_profile_id) {
  334. $server = variable_get('uc_authnet_cim_mode', 'disabled');
  335. $request = array(
  336. 'customerProfileId' => $profile_id,
  337. );
  338. // Request a profile from auth.net.
  339. $xml = _uc_authorizenet_xml_api_wrapper('getCustomerPaymentProfileRequest', _uc_authorizenet_array_to_xml($request));
  340. // Parse the response.
  341. $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  342. return $response['resultCode'] == 'Error' ? FALSE : $response;
  343. }
  344. /**
  345. * Handles authorizations and captures through AIM at Authorize.net.
  346. */
  347. function _uc_authorizenet_charge($order, $amount, $data) {
  348. global $user;
  349. // Build a description of the order for logging in Auth.Net.
  350. $description = array();
  351. foreach ((array) $order->products as $product) {
  352. $description[] = $product->qty . 'x ' . $product->model;
  353. }
  354. $billing_address = $order->billing_street1;
  355. if ($order->billing_street2) {
  356. $billing_address .= ', ' . $order->billing_street2;
  357. }
  358. $delivery_address = $order->delivery_street1;
  359. if ($order->delivery_street2) {
  360. $delivery_address .= ', ' . $order->delivery_street2;
  361. }
  362. $billing_country = uc_get_country_data(array('country_id' => $order->billing_country));
  363. $delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
  364. // Build the POST data for the transaction.
  365. $submit_data = array(
  366. // Merchant information.
  367. 'x_login' => trim(variable_get('uc_authnet_api_login_id', '')),
  368. 'x_tran_key' => trim(variable_get('uc_authnet_api_transaction_key', '')),
  369. // Transaction information.
  370. 'x_version' => '3.1',
  371. 'x_type' => _uc_authorizenet_txn_map($data['txn_type']),
  372. // 'x_method' => $order->payment_method == 'credit' ? 'CC' : 'ECHECK',
  373. 'x_method' => 'CC',
  374. // 'x_recurring_billing' => 'FALSE',
  375. 'x_amount' => uc_currency_format($amount, FALSE, FALSE, '.'),
  376. 'x_card_num' => $order->payment_details['cc_number'],
  377. 'x_exp_date' => $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year'],
  378. 'x_card_code' => $order->payment_details['cc_cvv'],
  379. // 'x_trans_id' => '',
  380. // 'x_auth_code' => '',
  381. 'x_test_request' => variable_get('uc_authnet_aim_txn_mode', 'live_test') == 'live_test' ? 'TRUE' : 'FALSE',
  382. 'x_duplicate_window' => variable_get('uc_authnet_duplicate_window', 120),
  383. // Order information.
  384. 'x_invoice_num' => $order->order_id,
  385. 'x_description' => substr(implode(', ', $description), 0, 255),
  386. // Customer information.
  387. 'x_first_name' => substr($order->billing_first_name, 0, 50),
  388. 'x_last_name' => substr($order->billing_last_name, 0, 50),
  389. 'x_company' => substr($order->billing_company, 0, 50),
  390. 'x_address' => substr($billing_address, 0, 60),
  391. 'x_city' => substr($order->billing_city, 0, 40),
  392. 'x_state' => substr(uc_get_zone_code($order->billing_zone), 0, 40),
  393. 'x_zip' => substr($order->billing_postal_code, 0, 20),
  394. 'x_country' => !$billing_country ? '' : $billing_country[0]['country_iso_code_2'],
  395. 'x_phone' => substr($order->billing_phone, 0, 25),
  396. // 'x_fax' => substr('', 0, 25),
  397. 'x_email' => substr($order->primary_email, 0, 255),
  398. 'x_cust_id' => substr($order->uid, 0, 20),
  399. 'x_customer_ip' => substr(ip_address(), 0, 15),
  400. // Shipping information.
  401. 'x_ship_to_first_name' => substr($order->delivery_first_name, 0, 50),
  402. 'x_ship_to_last_name' => substr($order->delivery_last_name, 0, 50),
  403. 'x_ship_to_company' => substr($order->delivery_company, 0, 50),
  404. 'x_ship_to_address' => substr($delivery_address, 0, 60),
  405. 'x_ship_to_city' => substr($order->delivery_city, 0, 40),
  406. 'x_ship_to_state' => substr(uc_get_zone_code($order->delivery_zone), 0, 40),
  407. 'x_ship_to_zip' => substr($order->delivery_postal_code, 0, 20),
  408. 'x_ship_to_country' => !$delivery_country ? '' : $delivery_country[0]['country_iso_code_2'],
  409. // Extra information.
  410. 'x_delim_data' => 'TRUE',
  411. 'x_delim_char' => '|',
  412. 'x_encap_char' => '"',
  413. 'x_relay_response' => 'FALSE',
  414. 'x_email_customer' => variable_get('uc_authnet_aim_email_customer', FALSE) ? 'TRUE' : 'FALSE',
  415. );
  416. if ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
  417. $submit_data['x_trans_id'] = $data['auth_id'];
  418. }
  419. // Allow other modules to alter the transaction.
  420. drupal_alter('uc_authorizenet_transaction', $submit_data);
  421. // Determine the correct URL based on the transaction mode.
  422. if (variable_get('uc_authnet_aim_txn_mode', 'live_test') == 'developer_test') {
  423. $post_url = variable_get('uc_authnet_api_test_gateway_url', UC_AUTHORIZENET_TEST_GATEWAY_URL);
  424. }
  425. else {
  426. $post_url = variable_get('uc_authnet_api_live_gateway_url', UC_AUTHORIZENET_LIVE_GATEWAY_URL);
  427. }
  428. // Translate the data array into a string we can POST.
  429. $post_fields = array();
  430. foreach ($submit_data as $key => $value) {
  431. $post_fields[] = $key . '=' . urlencode($value);
  432. }
  433. // Setup the cURL request.
  434. $ch = curl_init();
  435. curl_setopt($ch, CURLOPT_URL, $post_url);
  436. curl_setopt($ch, CURLOPT_VERBOSE, 0);
  437. curl_setopt($ch, CURLOPT_POST, 1);
  438. curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $post_fields));
  439. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  440. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  441. curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
  442. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  443. $result = curl_exec($ch);
  444. // Log any errors to the watchdog.
  445. if ($error = curl_error($ch)) {
  446. watchdog('uc_authorizenet', 'cURL error: @error', array('@error' => $error), WATCHDOG_ERROR);
  447. return array('success' => FALSE);
  448. }
  449. curl_close($ch);
  450. $response = explode('|', $result);
  451. if (variable_get('uc_authnet_response_debug', FALSE)) {
  452. watchdog('uc_authorizenet', 'Debug response: !data', array('!data' => '<pre>' . check_plain(print_r($response, TRUE)) . '</pre>'));
  453. }
  454. // Trim off the encapsulating character from the results.
  455. for ($i = 0; $i < count($response); $i++) {
  456. $response[$i] = substr($response[$i], 1, strlen($response[$i]) - 2);
  457. }
  458. /*
  459. * Response key index:
  460. * 0 = Response Code
  461. * 2 = Response Reason Code
  462. * 3 = Response Reason Text
  463. * 4 = Authorization Code
  464. * 5 = Address Verification Service (AVS) Response
  465. * 6 = Transaction ID; needed for CREDIT, PRIOR_AUTH_CAPTURE, and VOID transactions.
  466. * 9 = Amount
  467. * 11 = Transaction Type
  468. * 32 = Tax Amount Charged
  469. * 37 = Transaction Response MD5 Hash
  470. * 38 = Card Code (CVV) Response
  471. */
  472. // If we didn't get an approval response code...
  473. if ($response[0] != '1') {
  474. // Fail the charge with the reason text in the decline message.
  475. $result = array(
  476. 'success' => FALSE,
  477. 'message' => t('Credit card payment declined: @message', array('@message' => $response[3])),
  478. 'uid' => $user->uid,
  479. );
  480. }
  481. else {
  482. // Build a message for display and comments in the payments table.
  483. $message = t('Type: @type<br />ID: @id', array('@type' => _uc_authorizenet_txn_type($response[11]), '@id' => $response[6]));
  484. $result = array(
  485. 'success' => TRUE,
  486. 'comment' => $message,
  487. 'message' => $message,
  488. 'data' => array('module' => 'uc_authorizenet', 'txn_type' => $response[11], 'txn_id' => $response[6], 'txn_authcode' => $response[4]),
  489. 'uid' => $user->uid,
  490. );
  491. // If this was an authorization only transaction...
  492. if ($data['txn_type'] == UC_CREDIT_AUTH_ONLY) {
  493. // Log the authorization to the order.
  494. uc_credit_log_authorization($order->order_id, $response[6], $amount);
  495. }
  496. elseif ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
  497. uc_credit_log_prior_auth_capture($order->order_id, $data['auth_id']);
  498. }
  499. // Create a transaction reference if specified in the payment gateway
  500. // settings and this is an appropriate transaction type.
  501. if (variable_get('uc_authnet_cim_profile', FALSE) && in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY, UC_CREDIT_AUTH_CAPTURE))) {
  502. // Ignore the returned message for now; that will appear in the comments.
  503. _uc_authorizenet_cim_profile_create($order);
  504. }
  505. }
  506. // Don't log this as a payment money wasn't actually captured.
  507. if (in_array($data['txn_type'], array(UC_CREDIT_AUTH_ONLY))) {
  508. $result['log_payment'] = FALSE;
  509. }
  510. // Build an admin order comment.
  511. $comment = t('<b>@type</b><br /><b>@status:</b> @message<br />Amount: @amount<br />AVS response: @avs',
  512. array('@type' => _uc_authorizenet_txn_type($response[11]), '@status' => $result['success'] ? t('ACCEPTED') : t('REJECTED'), '@message' => $response[3], '@amount' => uc_currency_format($response[9]), '@avs' => _uc_authorizenet_avs($response[5])));
  513. // Add the CVV response if enabled.
  514. if (variable_get('uc_credit_cvv_enabled', TRUE)) {
  515. $comment .= '<br />' . t('CVV match: @cvv', array('@cvv' => _uc_authorizenet_cvv($response[38])));
  516. }
  517. // Save the comment to the order.
  518. uc_order_comment_save($order->order_id, $user->uid, $comment, 'admin');
  519. return $result;
  520. }
  521. /**
  522. * Sends an XML API Request to Authorize.Net.
  523. *
  524. * @param string $server
  525. * The name of the server to send a request to - 'production' or 'developer'.
  526. * @param string $xml
  527. * The XML to send to Authorize.Net.
  528. * @param $callback
  529. * The name of the function that should process the response.
  530. *
  531. * @return bool
  532. * TRUE or FALSE indicating the success of the API request.
  533. */
  534. function uc_authorizenet_xml_api($server, $xml) {
  535. if ($server == 'production') {
  536. $post_url = 'https://api.authorize.net/xml/v1/request.api';
  537. }
  538. elseif ($server == 'developer') {
  539. $post_url = 'https://apitest.authorize.net/xml/v1/request.api';
  540. }
  541. else {
  542. return FALSE;
  543. }
  544. $ch = curl_init();
  545. curl_setopt($ch, CURLOPT_URL, $post_url);
  546. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  547. curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
  548. curl_setopt($ch, CURLOPT_HEADER, 1);
  549. curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
  550. curl_setopt($ch, CURLOPT_POST, 1);
  551. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  552. $response = curl_exec($ch);
  553. // Log any errors to the watchdog.
  554. if ($error = curl_error($ch)) {
  555. watchdog('uc_authorizenet', 'cURL error: @error', array('@error' => $error), WATCHDOG_ERROR);
  556. return FALSE;
  557. }
  558. curl_close($ch);
  559. return $response;
  560. }
  561. /**
  562. * Updates an ARB subscription; for simplicity's sake, payment schedule
  563. * information cannot be updated at this time.
  564. *
  565. * @param $subscription_id
  566. * The ID of the subscription at Authorize.Net.
  567. * @param array $updates
  568. * An array of data to update using key/value pairs from the XML API for ARB;
  569. * keys should be children of the subscription element in the XML.
  570. * See the ARB_guide.pdf from Authorize.Net for ARBCreateSubscriptionRequests.
  571. *
  572. * @return bool
  573. * TRUE or FALSE indicating the success of the cancellation.
  574. */
  575. function uc_authorizenet_arb_update($subscription_id, $updates, $order_id = NULL) {
  576. $server = variable_get('uc_authnet_arb_mode', 'disabled');
  577. unset($updates['paymentSchedule']);
  578. // Build the data array for the request.
  579. $data = array(
  580. 'refId' => substr($order_id . '-' . REQUEST_TIME, 0, 20),
  581. 'subscriptionId' => $subscription_id,
  582. 'subscription' => $updates,
  583. );
  584. // Build the XML string.
  585. $xml = _uc_authorizenet_xml_api_wrapper('ARBUpdateSubscriptionRequest', _uc_authorizenet_array_to_xml($data));
  586. // Send the request off to the server and get the response.
  587. $response = uc_authorizenet_xml_api($server, $xml);
  588. // Fail if the response is empty or FALSE.
  589. if (!$response) {
  590. return FALSE;
  591. }
  592. // Parse the response into a data array.
  593. $data = _uc_authorizenet_arb_parse_response($response);
  594. if ($data['resultCode'] == 'Error') {
  595. if (!empty($order_id)) {
  596. uc_order_comment_save($order_id, 0, t('Authorize.Net: Subscription @subscription_id updated failed.<br />@error - @text', array('@subscription_id' => $subscription_id, '@error' => $data['code'], '@text' => $data['text'])), 'admin');
  597. }
  598. return FALSE;
  599. }
  600. uc_order_comment_save($order_id, 0, t('Authorize.Net: Subscription @subscription_id updated.', array('@subscription_id' => $subscription_id)), 'admin');
  601. return TRUE;
  602. }
  603. /**
  604. * Helper function for XML API requests.
  605. *
  606. * Wraps XML API request child elements in the request element and includes
  607. * the merchant authentication information.
  608. */
  609. function _uc_authorizenet_xml_api_wrapper($request, $xml) {
  610. return '<?xml version="1.0" encoding="utf-8"?><' . $request
  611. . ' xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"><merchantAuthentication>'
  612. . '<name>' . trim(variable_get('uc_authnet_api_login_id', '')) . '</name>'
  613. . '<transactionKey>' . trim(variable_get('uc_authnet_api_transaction_key', ''))
  614. . '</transactionKey></merchantAuthentication>' . $xml . '</' . $request . '>';
  615. }
  616. /**
  617. * Converts a hierarchical array of elements into an XML string.
  618. */
  619. function _uc_authorizenet_array_to_xml($data) {
  620. $xml = '';
  621. // Loop through the elements in the data array.
  622. foreach ($data as $element => $contents) {
  623. if (is_array($contents)) {
  624. // Render the element with its child elements.
  625. $xml .= '<' . $element . '>' . _uc_authorizenet_array_to_xml($contents) . '</' . $element . '>';
  626. }
  627. else {
  628. // Render the element with its contents.
  629. $xml .= '<' . $element . '>' . htmlspecialchars($contents) . '</' . $element . '>';
  630. }
  631. }
  632. return $xml;
  633. }
  634. /**
  635. * Returns the message text for an AVS response code.
  636. */
  637. function _uc_authorizenet_avs($code) {
  638. $text = $code . ' - ';
  639. switch ($code) {
  640. case 'A':
  641. $text .= t('Address (Street) matches, ZIP does not');
  642. break;
  643. case 'B':
  644. $text .= t('Address information not provided for AVS check');
  645. break;
  646. case 'E':
  647. $text .= t('AVS error');
  648. break;
  649. case 'G':
  650. $text .= t('Non-U.S. Card Issuing Bank');
  651. break;
  652. case 'N':
  653. $text .= t('No Match on Address (Street) or ZIP');
  654. break;
  655. case 'P':
  656. $text .= t('AVS not applicable for this transaction');
  657. break;
  658. case 'R':
  659. $text .= t('Retry – System unavailable or timed out');
  660. break;
  661. case 'S':
  662. $text .= t('Service not supported by issuer');
  663. break;
  664. case 'U':
  665. $text .= t('Address information is unavailable');
  666. break;
  667. case 'W':
  668. $text .= t('Nine digit ZIP matches, Address (Street) does not');
  669. break;
  670. case 'X':
  671. $text .= t('Address (Street) and nine digit ZIP match');
  672. break;
  673. case 'Y':
  674. $text .= t('Address (Street) and five digit ZIP match');
  675. break;
  676. case 'Z':
  677. $text .= t('Five digit ZIP matches, Address (Street) does not');
  678. break;
  679. }
  680. return $text;
  681. }
  682. /**
  683. * Returns the message text for a CVV match.
  684. */
  685. function _uc_authorizenet_cvv($code) {
  686. $text = $code . ' - ';
  687. switch ($code) {
  688. case 'M':
  689. $text .= t('Match');
  690. break;
  691. case 'N':
  692. $text .= t('No Match');
  693. break;
  694. case 'P':
  695. $text .= t('Not Processed');
  696. break;
  697. case 'S':
  698. $text .= t('Should have been present');
  699. break;
  700. case 'U':
  701. $text .= t('Issuer unable to process request');
  702. break;
  703. }
  704. return $text;
  705. }
  706. /**
  707. * Returns the title of the transaction type.
  708. */
  709. function _uc_authorizenet_txn_type($type) {
  710. switch (strtoupper($type)) {
  711. case 'AUTH_CAPTURE':
  712. return t('Authorization and capture');
  713. case 'AUTH_ONLY':
  714. return t('Authorization only');
  715. case 'PRIOR_AUTH_CAPTURE':
  716. return t('Prior authorization capture');
  717. case 'CAPTURE_ONLY':
  718. return t('Capture only');
  719. case 'CREDIT':
  720. return t('Credit');
  721. case 'VOID':
  722. return t('Void');
  723. }
  724. }
  725. /**
  726. * Returns the Auth.Net transaction type corresponding to a UC type.
  727. */
  728. function _uc_authorizenet_txn_map($type) {
  729. switch ($type) {
  730. case UC_CREDIT_AUTH_ONLY:
  731. return 'AUTH_ONLY';
  732. case UC_CREDIT_PRIOR_AUTH_CAPTURE:
  733. return 'PRIOR_AUTH_CAPTURE';
  734. case UC_CREDIT_AUTH_CAPTURE:
  735. return 'AUTH_CAPTURE';
  736. case UC_CREDIT_CREDIT:
  737. return 'CREDIT';
  738. case UC_CREDIT_VOID:
  739. return 'VOID';
  740. }
  741. }
  742. /**
  743. * Maps an order's billing information to an array for later XML conversion.
  744. */
  745. function _uc_authorize_cim_xml_billto($order) {
  746. $billing_country = uc_get_country_data(array('country_id' => $order->billing_country));
  747. return array(
  748. 'firstName' => substr($order->billing_first_name, 0, 50),
  749. 'lastName' => substr($order->billing_last_name, 0, 50),
  750. 'company' => substr($order->billing_company, 0, 50),
  751. 'address' => substr($order->billing_street1, 0, 60),
  752. 'city' => substr($order->billing_city, 0, 40),
  753. 'state' => substr(uc_get_zone_code($order->billing_zone), 0, 2),
  754. 'zip' => substr($order->billing_postal_code, 0, 20),
  755. 'country' => !$billing_country ? '' : $billing_country[0]['country_iso_code_2'],
  756. 'phoneNumber' => substr($order->billing_phone, 0, 25),
  757. );
  758. }
  759. /**
  760. * Maps an order's shipping information to an array for later XML conversion.
  761. */
  762. function _uc_authorize_cim_xml_shipto($order) {
  763. $delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
  764. return array(
  765. 'firstName' => substr($order->delivery_first_name, 0, 50),
  766. 'lastName' => substr($order->delivery_last_name, 0, 50),
  767. 'company' => substr($order->delivery_company, 0, 50),
  768. 'address' => substr($order->delivery_street1, 0, 60),
  769. 'city' => substr($order->delivery_city, 0, 40),
  770. 'state' => substr(uc_get_zone_code($order->delivery_zone), 0, 2),
  771. 'zip' => substr($order->delivery_postal_code, 0, 20),
  772. 'country' => !$delivery_country ? '' : $delivery_country[0]['country_iso_code_2'],
  773. );
  774. }
  775. /**
  776. * Parses an Authorize.Net XML CIM API response.
  777. */
  778. function _uc_authorizenet_cim_parse_response($content) {
  779. // Find the elements in the XML and build the return array.
  780. $data = array(
  781. 'refId' => _uc_authorizenet_substr_between($content, 'refId'),
  782. 'resultCode' => _uc_authorizenet_substr_between($content, 'resultCode'),
  783. 'code' => _uc_authorizenet_substr_between($content, 'code'),
  784. 'text' => _uc_authorizenet_substr_between($content, 'text'),
  785. 'customerProfileId' => _uc_authorizenet_substr_between($content, 'customerProfileId'),
  786. 'directResponse' => _uc_authorizenet_substr_between($content, 'directResponse'),
  787. 'customerPaymentProfileId' => _uc_authorizenet_substr_between($content, 'customerPaymentProfileId'),
  788. 'customerAddressId' => _uc_authorizenet_substr_between($content, 'customerAddressId'),
  789. );
  790. return $data;
  791. }
  792. /**
  793. * Parses an Authorize.Net XML API response; from sample PHP for ARB.
  794. */
  795. function _uc_authorizenet_arb_parse_response($content) {
  796. // Find the elements in the XML and build the return array.
  797. $data = array(
  798. 'refId' => _uc_authorizenet_substr_between($content, 'refId'),
  799. 'resultCode' => _uc_authorizenet_substr_between($content, 'resultCode'),
  800. 'code' => _uc_authorizenet_substr_between($content, 'code'),
  801. 'text' => _uc_authorizenet_substr_between($content, 'text'),
  802. 'subscriptionId' => _uc_authorizenet_substr_between($content, 'subscriptionId'),
  803. );
  804. return $data;
  805. }
  806. /**
  807. * Helper function for parsing responses; adapted from sample PHP for ARB.
  808. */
  809. function _uc_authorizenet_substr_between($string, $element) {
  810. $open = '<' . $element . '>';
  811. $close = '</' . $element . '>';
  812. // Fail if we can't find the open or close tag for the element.
  813. if (strpos($string, $open) === FALSE || strpos($string, $close) === FALSE) {
  814. return FALSE;
  815. }
  816. $start = strpos($string, $open) + strlen($open);
  817. $end = strpos($string, $close);
  818. return substr($string, $start, $end - $start);
  819. }
  820. /**
  821. * Decrypts the login data for using Auth.Net APIs.
  822. */
  823. function _uc_authorizenet_login_data() {
  824. static $data;
  825. if (!empty($data)) {
  826. return $data;
  827. }
  828. $md5_hash = variable_get('uc_authnet_md5_hash', '');
  829. // If CC encryption has been configured properly.
  830. if ($key = uc_credit_encryption_key()) {
  831. // Setup our encryption object.
  832. $crypt = new UbercartEncryption();
  833. // Decrypt the MD5 Hash.
  834. if (!empty($md5_hash)) {
  835. $md5_hash = $crypt->decrypt($key, $md5_hash);
  836. }
  837. // Store any errors.
  838. uc_store_encryption_errors($crypt, 'uc_authorizenet');
  839. }
  840. $data = array(
  841. 'md5_hash' => $md5_hash,
  842. );
  843. return $data;
  844. }