uc_credit.module 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  1. <?php
  2. /**
  3. * @file
  4. * Defines the credit card payment method and hooks in payment gateways.
  5. */
  6. /**
  7. * Just authorize an amount on a credit card account.
  8. */
  9. define('UC_CREDIT_AUTH_ONLY', 'authorize');
  10. /**
  11. * Capture funds from a prior authorization.
  12. */
  13. define('UC_CREDIT_PRIOR_AUTH_CAPTURE', 'prior_auth_capture');
  14. /**
  15. * Authorize and capture money all at once.
  16. */
  17. define('UC_CREDIT_AUTH_CAPTURE', 'auth_capture');
  18. /**
  19. * Set up a credit card reference through the payment gateway.
  20. */
  21. define('UC_CREDIT_REFERENCE_SET', 'reference_set');
  22. /**
  23. * Capture funds using a credit card reference.
  24. */
  25. define('UC_CREDIT_REFERENCE_TXN', 'reference_txn');
  26. /**
  27. * Remove a reference from the payment gateway.
  28. */
  29. define('UC_CREDIT_REFERENCE_REMOVE', 'reference_remove');
  30. /**
  31. * Credit funds to a reference at the payment gateway.
  32. */
  33. define('UC_CREDIT_REFERENCE_CREDIT', 'reference_credit');
  34. /**
  35. * Credit funds to a credit card account.
  36. */
  37. define('UC_CREDIT_CREDIT', 'credit');
  38. /**
  39. * Void a transaction before the transaction clears.
  40. */
  41. define('UC_CREDIT_VOID', 'void');
  42. /**
  43. * Name of encryption key file.
  44. */
  45. define('UC_CREDIT_KEYFILE_NAME', 'uc_credit.key');
  46. /**
  47. * Implements hook_help().
  48. */
  49. function uc_credit_help($path, $arg) {
  50. switch ($path) {
  51. case 'admin/store/orders/%/credit':
  52. return '<p>' . t('Use this terminal to process credit card payments through your default gateway.') . '</p>';
  53. }
  54. }
  55. /**
  56. * Implements hook_menu().
  57. */
  58. function uc_credit_menu() {
  59. $items['cart/checkout/credit/cvv_info'] = array(
  60. 'title' => 'CVV information',
  61. 'page callback' => 'uc_credit_cvv_info',
  62. 'access arguments' => array('access content'),
  63. 'type' => MENU_CALLBACK,
  64. 'file' => 'uc_credit.pages.inc',
  65. );
  66. $items['admin/store/orders/%uc_order/credit'] = array(
  67. 'title callback' => 'uc_credit_terminal_title',
  68. 'title arguments' => array(3),
  69. 'description' => 'Displays a form to process a credit card payment.',
  70. 'page callback' => 'uc_credit_terminal',
  71. 'page arguments' => array(3),
  72. 'access arguments' => array('process credit cards'),
  73. 'file' => 'uc_credit.admin.inc',
  74. );
  75. return $items;
  76. }
  77. /**
  78. * Implements hook_permission().
  79. */
  80. function uc_credit_permission() {
  81. return array(
  82. 'administer credit cards' => array(
  83. 'title' => t('Administer credit cards'),
  84. 'restrict access' => TRUE,
  85. ),
  86. 'process credit cards' => array(
  87. 'title' => t('Process credit cards'),
  88. ),
  89. 'view cc details' => array(
  90. 'title' => t('View credit card details'),
  91. ),
  92. );
  93. }
  94. /**
  95. * Implements hook_theme().
  96. */
  97. function uc_credit_theme() {
  98. return array(
  99. 'uc_credit_cvv_help' => array(
  100. 'file' => 'uc_credit.theme.inc',
  101. ),
  102. );
  103. }
  104. /**
  105. * Implements hook_init().
  106. */
  107. function uc_credit_init() {
  108. global $conf;
  109. $conf['i18n_variables'][] = 'uc_credit_fail_message';
  110. $conf['i18n_variables'][] = 'uc_credit_policy';
  111. }
  112. /**
  113. * Implements hook_form_FORM_ID_alter() for uc_cart_checkout_form().
  114. */
  115. function uc_credit_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
  116. // Cache the CC details for use in other functions.
  117. if (isset($_SESSION['sescrd'])) {
  118. uc_credit_cache('save', $_SESSION['sescrd']);
  119. // Store the encrypted details to the form for processing on submit.
  120. $form['payment_details_data'] = array(
  121. '#type' => 'hidden',
  122. '#value' => $_SESSION['sescrd'],
  123. );
  124. // Clear the session of the details.
  125. unset($_SESSION['sescrd']);
  126. }
  127. unset($_SESSION['cc_pay']);
  128. }
  129. /**
  130. * Implements hook_form_FORM_ID_alter() for uc_cart_checkout_review_form().
  131. */
  132. function uc_credit_form_uc_cart_checkout_review_form_alter(&$form, &$form_state) {
  133. // Check if the customer paid by CC and refreshed on the review page.
  134. if (isset($_SESSION['cc_pay']) && !isset($_SESSION['sescrd']) && empty($_POST['sescrd'])) {
  135. // Send them back to the checkout form to put in their details again.
  136. drupal_set_message(t('To protect our customers from identity theft, credit card details are erased when a browser refreshes on the checkout review page. Please enter your card details again and re-submit the form.'), 'error');
  137. $_SESSION['clear_cc'] = TRUE;
  138. unset($_SESSION['cc_pay']);
  139. drupal_goto('cart/checkout');
  140. }
  141. if (isset($_SESSION['sescrd'])) {
  142. // Cache the CC details for use in other functions.
  143. uc_credit_cache('save', $_SESSION['sescrd']);
  144. // Store the encrypted details to the form for processing on submit.
  145. $form['sescrd'] = array(
  146. '#type' => 'hidden',
  147. '#value' => base64_encode($_SESSION['sescrd']),
  148. );
  149. }
  150. else {
  151. $form['sescrd'] = array(
  152. '#type' => 'hidden',
  153. '#value' => '',
  154. );
  155. }
  156. // Add submit handler to preserve CC details for the back button and
  157. // failed order submissions.
  158. $form['actions']['back']['#submit'][] = 'uc_credit_cart_review_back_submit';
  159. // Reconstruct the submit handler array for before and after processing.
  160. $submit = array_merge(array('uc_credit_cart_review_pre_form_submit'), $form['#submit']);
  161. $submit[] = 'uc_credit_cart_review_post_form_submit';
  162. $form['#submit'] = $submit;
  163. // Clear the session of the details.
  164. unset($_SESSION['sescrd']);
  165. }
  166. /**
  167. * Implements hook_exit().
  168. */
  169. function uc_credit_exit() {
  170. // Make sure sensitive checkout session data doesn't persist on other pages.
  171. if (isset($_SESSION['sescrd'])) {
  172. if (isset($_GET['q'])) {
  173. // Separate the args ourself since the arg() function may not be loaded.
  174. $args = explode('/', $_GET['q']);
  175. if (!isset($args[1]) || $args[1] != 'checkout') {
  176. unset($_SESSION['sescrd']);
  177. }
  178. }
  179. else {
  180. unset($_SESSION['sescrd']);
  181. }
  182. }
  183. }
  184. /**
  185. * Implements hook_uc_store_status().
  186. */
  187. function uc_credit_uc_store_status() {
  188. // Throw up an error row if encryption has not been set up yet.
  189. if ($key = uc_credit_encryption_key()) {
  190. $statuses[] = array(
  191. 'status' => 'ok',
  192. 'title' => t('Credit card encryption'),
  193. 'desc' => t('Credit card data is encrypted during checkout for maximum security.'),
  194. );
  195. }
  196. else {
  197. $statuses[] = array(
  198. 'status' => 'error',
  199. 'title' => t('Credit card encryption'),
  200. 'desc' => t('You must review your <a href="!url">credit card security settings</a> and enable encryption before you can accept credit card payments.', array('!url' => url('admin/store/settings/payment/method/credit'))),
  201. );
  202. }
  203. return $statuses;
  204. }
  205. /**
  206. * Implements hook_uc_order().
  207. */
  208. function uc_credit_uc_order($op, $order, $arg2) {
  209. // Set up the encryption key and object for saving and loading.
  210. if (isset($order->payment_method) && $order->payment_method == 'credit' && ($op == 'save' || $op == 'load')) {
  211. // Log an error if encryption isn't configured properly.
  212. if (!uc_credit_encryption_key()) {
  213. watchdog('uc_credit', 'Credit card encryption must be set up to process credit cards.');
  214. }
  215. }
  216. switch ($op) {
  217. case 'submit':
  218. if (isset($order->payment_method) && $order->payment_method == 'credit') {
  219. // Clear out that session variable denoting this as a CC paid order.
  220. unset($_SESSION['cc_pay']);
  221. // Process CC transactions when an order is submitted after review.
  222. $gateway_id = uc_credit_default_gateway();
  223. $data = array(
  224. 'txn_type' => variable_get('uc_pg_' . $gateway_id . '_cc_txn_type', UC_CREDIT_AUTH_CAPTURE),
  225. );
  226. // Attempt to process the CC payment.
  227. $order->payment_details = uc_credit_cache('load');
  228. $pass = uc_payment_process_payment('credit', $order->order_id, $order->order_total, $data, TRUE, NULL, FALSE);
  229. // If the payment failed, store the data back in the session and
  230. // halt the checkout process.
  231. if (!$pass) {
  232. $message = variable_get('uc_credit_fail_message', t('We were unable to process your credit card payment. Please verify your details and try again. If the problem persists, contact us to complete your order.'));
  233. return array(array('pass' => FALSE, 'message' => $message));
  234. }
  235. }
  236. break;
  237. case 'save':
  238. if (isset($order->payment_method) && $order->payment_method == 'credit' && !empty($order->payment_details)) {
  239. _uc_credit_save_cc_data_to_order($order->payment_details, $order->order_id);
  240. }
  241. break;
  242. case 'load':
  243. if (isset($order->payment_method) && $order->payment_method == 'credit') {
  244. // Load the CC details from the credit cache if available.
  245. $order->payment_details = uc_credit_cache('load');
  246. // Otherwise load any details that might be stored in the data array.
  247. if (empty($order->payment_details) && isset($order->data['cc_data'])) {
  248. $order->payment_details = uc_credit_cache('save', $order->data['cc_data']);
  249. }
  250. }
  251. break;
  252. }
  253. }
  254. /**
  255. * Implements hook_uc_payment_method().
  256. */
  257. function uc_credit_uc_payment_method() {
  258. if (arg(0) == 'cart' && uc_credit_encryption_key() === FALSE) {
  259. return;
  260. }
  261. $path = base_path() . drupal_get_path('module', 'uc_credit');
  262. $title = t('Credit card:');
  263. $cc_types = array(
  264. 'visa' => t('Visa'),
  265. 'mastercard' => t('MasterCard'),
  266. 'discover' => t('Discover'),
  267. 'amex' => t('American Express'),
  268. );
  269. foreach ($cc_types as $type => $label) {
  270. if (variable_get('uc_credit_' . $type, TRUE)) {
  271. $title .= ' ' . theme('image', array(
  272. 'path' => drupal_get_path('module', 'uc_credit') . '/images/' . $type . '.gif',
  273. 'alt' => $label,
  274. 'attributes' => array('class' => array('uc-credit-cctype', 'uc-credit-cctype-' . $type)),
  275. ));
  276. }
  277. }
  278. $methods['credit'] = array(
  279. 'name' => t('Credit card'),
  280. 'title' => $title,
  281. 'desc' => t('Pay by credit card.'),
  282. 'callback' => 'uc_payment_method_credit',
  283. 'weight' => 2,
  284. 'checkout' => TRUE,
  285. );
  286. return $methods;
  287. }
  288. /**
  289. * Callback function for the Credit Card payment method.
  290. */
  291. function uc_payment_method_credit($op, &$order, $form = NULL, &$form_state = NULL) {
  292. switch ($op) {
  293. case 'cart-details':
  294. $details = uc_payment_method_credit_form(array(), $form_state, $order);
  295. return $details;
  296. case 'cart-process':
  297. if (!isset($form_state['values']['panes']['payment']['details']['cc_number'])) {
  298. return;
  299. }
  300. // Fetch the CC details from the $_POST directly.
  301. $cc_data = $form_state['values']['panes']['payment']['details'];
  302. $cc_data['cc_number'] = str_replace(' ', '', $cc_data['cc_number']);
  303. array_walk($cc_data, 'check_plain');
  304. // Recover cached CC data in
  305. // $form_state['values']['panes']['payment']['details'] if it exists.
  306. if (isset($form_state['values']['panes']['payment']['details']['payment_details_data'])) {
  307. $cache = uc_credit_cache('save', $form_state['values']['panes']['payment']['details']['payment_details_data']);
  308. }
  309. // Account for partial CC numbers when masked by the system.
  310. if (substr($cc_data['cc_number'], 0, strlen(t('(Last4)'))) == t('(Last4)')) {
  311. // Recover the number from the encrypted data in the form if truncated.
  312. if (isset($cache['cc_number'])) {
  313. $cc_data['cc_number'] = $cache['cc_number'];
  314. }
  315. else {
  316. $cc_data['cc_number'] = '';
  317. }
  318. }
  319. // Account for masked CVV numbers.
  320. if (!empty($cc_data['cc_cvv']) && $cc_data['cc_cvv'] == str_repeat('-', strlen($cc_data['cc_cvv']))) {
  321. // Recover the number from the encrypted data in $_POST if truncated.
  322. if (isset($cache['cc_cvv'])) {
  323. $cc_data['cc_cvv'] = $cache['cc_cvv'];
  324. }
  325. else {
  326. $cc_data['cc_cvv'] = '';
  327. }
  328. }
  329. // Go ahead and put the CC data in the payment details array.
  330. $order->payment_details = $cc_data;
  331. // Default our value for validation.
  332. $return = TRUE;
  333. // Make sure an owner value was entered.
  334. if (variable_get('uc_credit_owner_enabled', FALSE) && empty($cc_data['cc_owner'])) {
  335. form_set_error('panes][payment][details][cc_owner', t('Enter the owner name as it appears on the card.'));
  336. $return = FALSE;
  337. }
  338. // Validate the CC number if that's turned on/check for non-digits.
  339. if ((variable_get('uc_credit_validate_numbers', TRUE) && !_uc_credit_valid_card_number($cc_data['cc_number']))
  340. || !ctype_digit($cc_data['cc_number'])) {
  341. form_set_error('panes][payment][details][cc_number', t('You have entered an invalid credit card number.'));
  342. $return = FALSE;
  343. }
  344. // Validate the start date (if entered).
  345. if (variable_get('uc_credit_start_enabled', FALSE) && !_uc_credit_valid_card_start($cc_data['cc_start_month'], $cc_data['cc_start_year'])) {
  346. form_set_error('panes][payment][details][cc_start_month', t('The start date you entered is invalid.'));
  347. form_set_error('panes][payment][details][cc_start_year');
  348. $return = FALSE;
  349. }
  350. // Validate the card expiration date.
  351. if (!_uc_credit_valid_card_expiration($cc_data['cc_exp_month'], $cc_data['cc_exp_year'])) {
  352. form_set_error('panes][payment][details][cc_exp_month', t('The credit card you entered has expired.'));
  353. form_set_error('panes][payment][details][cc_exp_year');
  354. $return = FALSE;
  355. }
  356. // Validate the issue number (if entered). With issue numbers, '01' is
  357. // different from '1', but is_numeric() is still appropriate.
  358. if (variable_get('uc_credit_issue_enabled', FALSE) && !_uc_credit_valid_card_issue($cc_data['cc_issue'])) {
  359. form_set_error('panes][payment][details][cc_issue', t('The issue number you entered is invalid.'));
  360. $return = FALSE;
  361. }
  362. // Validate the CVV number if enabled.
  363. if (variable_get('uc_credit_cvv_enabled', TRUE) && !_uc_credit_valid_cvv($cc_data['cc_cvv'])) {
  364. form_set_error('panes][payment][details][cc_cvv', t('You have entered an invalid CVV number.'));
  365. $return = FALSE;
  366. }
  367. // Validate the bank name if enabled.
  368. if (variable_get('uc_credit_bank_enabled', FALSE) && empty($cc_data['cc_bank'])) {
  369. form_set_error('panes][payment][details][cc_bank', t('You must enter the issuing bank for that card.'));
  370. $return = FALSE;
  371. }
  372. // Initialize the encryption key and class.
  373. $key = uc_credit_encryption_key();
  374. $crypt = new UbercartEncryption();
  375. // Store the encrypted details in the session for the next pageload.
  376. // We are using base64_encode() because the encrypt function works with a
  377. // limited set of characters, not supporting the full Unicode character
  378. // set or even extended ASCII characters that may be present.
  379. // base64_encode() converts everything to a subset of ASCII, ensuring that
  380. // the encryption algorithm does not mangle names.
  381. $_SESSION['sescrd'] = $crypt->encrypt($key, base64_encode(serialize($order->payment_details)));
  382. // Log any errors to the watchdog.
  383. uc_store_encryption_errors($crypt, 'uc_credit');
  384. // If we're going to the review screen, set a variable that lets us know
  385. // we're paying by CC.
  386. if ($return) {
  387. $_SESSION['cc_pay'] = TRUE;
  388. }
  389. return $return;
  390. case 'cart-review':
  391. if (variable_get('uc_credit_type_enabled', FALSE)) {
  392. $review[] = array('title' => t('Card type'), 'data' => check_plain($order->payment_details['cc_type']));
  393. }
  394. if (variable_get('uc_credit_owner_enabled', FALSE)) {
  395. $review[] = array('title' => t('Card owner'), 'data' => check_plain($order->payment_details['cc_owner']));
  396. }
  397. $review[] = array('title' => t('Card number'), 'data' => uc_credit_display_number($order->payment_details['cc_number']));
  398. if (variable_get('uc_credit_start_enabled', FALSE)) {
  399. $start = $order->payment_details['cc_start_month'] . '/' . $order->payment_details['cc_start_year'];
  400. $review[] = array('title' => t('Start date'), 'data' => strlen($start) > 1 ? $start : '');
  401. }
  402. $review[] = array('title' => t('Expiration'), 'data' => $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year']);
  403. if (variable_get('uc_credit_issue_enabled', FALSE)) {
  404. $review[] = array('title' => t('Issue number'), 'data' => $order->payment_details['cc_issue']);
  405. }
  406. if (variable_get('uc_credit_bank_enabled', FALSE)) {
  407. $review[] = array('title' => t('Issuing bank'), 'data' => check_plain($order->payment_details['cc_bank']));
  408. }
  409. return $review;
  410. case 'order-view':
  411. $build = array();
  412. // Add the hidden span for the CC details if possible.
  413. if (user_access('view cc details')) {
  414. $rows = array();
  415. if (!empty($order->payment_details['cc_type'])) {
  416. $rows[] = t('Card type') . ': ' . check_plain($order->payment_details['cc_type']);
  417. }
  418. if (!empty($order->payment_details['cc_owner'])) {
  419. $rows[] = t('Card owner') . ': ' . check_plain($order->payment_details['cc_owner']);
  420. }
  421. if (!empty($order->payment_details['cc_number'])) {
  422. $rows[] = t('Card number') . ': ' . uc_credit_display_number($order->payment_details['cc_number']);
  423. }
  424. if (!empty($order->payment_details['cc_start_month']) && !empty($order->payment_details['cc_start_year'])) {
  425. $rows[] = t('Start date') . ': ' . $order->payment_details['cc_start_month'] . '/' . $order->payment_details['cc_start_year'];
  426. }
  427. if (!empty($order->payment_details['cc_exp_month']) && !empty($order->payment_details['cc_exp_year'])) {
  428. $rows[] = t('Expiration') . ': ' . $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year'];
  429. }
  430. if (!empty($order->payment_details['cc_issue'])) {
  431. $rows[] = t('Issue number') . ': ' . check_plain($order->payment_details['cc_issue']);
  432. }
  433. if (!empty($order->payment_details['cc_bank'])) {
  434. $rows[] = t('Issuing bank') . ': ' . check_plain($order->payment_details['cc_bank']);
  435. }
  436. $build['cc_info'] = array(
  437. '#prefix' => '<a href="#" onclick="jQuery(this).hide().next().show();">' . t('Show card details') . '</a><div style="display: none;">',
  438. '#markup' => implode('<br />', $rows),
  439. '#suffix' => '</div>',
  440. );
  441. // Add the form to process the card if applicable.
  442. if (user_access('process credit cards')) {
  443. $build['terminal'] = drupal_get_form('uc_credit_order_view_form', $order->order_id);
  444. }
  445. }
  446. return $build;
  447. case 'customer-view':
  448. $build = array();
  449. if (!empty($order->payment_details['cc_number'])) {
  450. $build['#markup'] = t('Card number') . ':<br />' . uc_credit_display_number($order->payment_details['cc_number']);
  451. }
  452. return $build;
  453. case 'order-details':
  454. return t('Use the terminal available through the<br />%button button on the View tab to<br />process credit card payments.', array('%button' => t('Process card')));
  455. case 'settings':
  456. form_load_include($form_state, 'inc', 'uc_credit', 'uc_credit.admin');
  457. return uc_credit_settings_form($form, $form_state);
  458. }
  459. }
  460. /**
  461. * Displays the credit card details form on the checkout screen.
  462. */
  463. function uc_payment_method_credit_form($form, &$form_state, $order) {
  464. // Normally the CC data is posted in via AJAX.
  465. if (!empty($form_state['values']['payment_details_data']) && arg(0) == 'cart') {
  466. $order->payment_details = uc_credit_cache('save', $form_state['values']['payment_details_data']);
  467. }
  468. // But we have to accommodate failed checkout form validation here.
  469. if (isset($_SESSION['sescrd'])) {
  470. $order->payment_details = uc_credit_cache('save', $_SESSION['sescrd']);
  471. unset($_SESSION['sescrd']);
  472. }
  473. if (!isset($order->payment_details) && isset($form_state['values']['panes']['payment']['details'])) {
  474. $order->payment_details = $form_state['values']['panes']['payment']['details'];
  475. $order->payment_details['cc_number'] = str_replace(' ', '', $order->payment_details['cc_number']);
  476. }
  477. if (!isset($order->payment_details)) {
  478. $order->payment_details = array();
  479. }
  480. $form['cc_policy'] = array(
  481. '#prefix' => '<p>',
  482. '#markup' => variable_get('uc_credit_policy', t('Your billing information must match the billing address for the credit card entered below or we will be unable to process your payment.')),
  483. '#suffix' => '</p>',
  484. );
  485. $types = variable_get('uc_credit_accepted_types', implode("\r\n", array(t('Visa'), t('Mastercard'), t('Discover'), t('American Express'))));
  486. if (variable_get('uc_credit_type_enabled', FALSE) && $types) {
  487. $form['cc_type'] = array(
  488. '#type' => 'select',
  489. '#title' => t('Card type'),
  490. '#options' => drupal_map_assoc(explode("\r\n", $types)),
  491. '#default_value' => isset($order->payment_details['cc_type']) ? $order->payment_details['cc_type'] : NULL,
  492. );
  493. }
  494. if (variable_get('uc_credit_owner_enabled', FALSE)) {
  495. $form['cc_owner'] = array(
  496. '#type' => 'textfield',
  497. '#title' => t('Card owner'),
  498. '#default_value' => isset($order->payment_details['cc_owner']) ? $order->payment_details['cc_owner'] : '',
  499. '#attributes' => array('autocomplete' => 'off'),
  500. '#size' => 32,
  501. '#maxlength' => 64,
  502. );
  503. }
  504. // Set up the default CC number on the credit card form.
  505. if (isset($_SESSION['clear_cc']) || !isset($order->payment_details['cc_number'])) {
  506. $default_num = NULL;
  507. }
  508. elseif (variable_get('uc_credit_validate_numbers', TRUE) && !_uc_credit_valid_card_number($order->payment_details['cc_number'])) {
  509. // Display the number as is if it does not validate so it can be corrected.
  510. $default_num = $order->payment_details['cc_number'];
  511. }
  512. else {
  513. // Otherwise default to the last 4 digits.
  514. $default_num = t('(Last 4) ') . substr($order->payment_details['cc_number'], -4);
  515. }
  516. $form['cc_number'] = array(
  517. '#type' => 'textfield',
  518. '#title' => t('Card number'),
  519. '#default_value' => $default_num,
  520. '#attributes' => array('autocomplete' => 'off'),
  521. '#size' => 20,
  522. '#maxlength' => 19,
  523. );
  524. if (variable_get('uc_credit_start_enabled', FALSE)) {
  525. $month = isset($order->payment_details['cc_start_month']) ? $order->payment_details['cc_start_month'] : NULL;
  526. $year = isset($order->payment_details['cc_start_year']) ? $order->payment_details['cc_start_year'] : NULL;
  527. $form['cc_start_month'] = uc_select_month(t('Start date'), $month, TRUE);
  528. $form['cc_start_year'] = uc_select_year(t('Start year'), $year, date('Y') - 10, date('Y'), TRUE);
  529. $form['cc_start_year']['#field_suffix'] = t('(if present)');
  530. }
  531. $month = isset($order->payment_details['cc_exp_month']) ? $order->payment_details['cc_exp_month'] : 1;
  532. $year = isset($order->payment_details['cc_exp_year']) ? $order->payment_details['cc_exp_year'] : date('Y');
  533. $form['cc_exp_month'] = uc_select_month(t('Expiration date'), $month);
  534. $form['cc_exp_year'] = uc_select_year(t('Expiration year'), $year);
  535. if (variable_get('uc_credit_issue_enabled', FALSE)) {
  536. // Set up the default Issue Number on the credit card form.
  537. if (empty($order->payment_details['cc_issue'])) {
  538. $default_card_issue = NULL;
  539. }
  540. elseif (!_uc_credit_valid_card_issue($order->payment_details['cc_issue'])) {
  541. // Display the Issue Number as is if it does not validate so it can be
  542. // corrected.
  543. $default_card_issue = $order->payment_details['cc_issue'];
  544. }
  545. else {
  546. // Otherwise mask it with dashes.
  547. $default_card_issue = str_repeat('-', strlen($order->payment_details['cc_issue']));
  548. }
  549. $form['cc_issue'] = array(
  550. '#type' => 'textfield',
  551. '#title' => t('Issue number'),
  552. '#default_value' => $default_card_issue,
  553. '#attributes' => array('autocomplete' => 'off'),
  554. '#size' => 2,
  555. '#maxlength' => 2,
  556. '#field_suffix' => t('(if present)'),
  557. );
  558. }
  559. if (variable_get('uc_credit_cvv_enabled', TRUE)) {
  560. // Set up the default CVV on the credit card form.
  561. if (isset($_SESSION['clear_cc']) || empty($order->payment_details['cc_cvv'])) {
  562. $default_cvv = NULL;
  563. }
  564. elseif (!_uc_credit_valid_cvv($order->payment_details['cc_cvv'])) {
  565. // Display the CVV as is if it does not validate so it can be corrected.
  566. $default_cvv = $order->payment_details['cc_cvv'];
  567. }
  568. else {
  569. // Otherwise mask it with dashes.
  570. $default_cvv = str_repeat('-', strlen($order->payment_details['cc_cvv']));
  571. }
  572. $form['cc_cvv'] = array(
  573. '#type' => 'textfield',
  574. '#title' => t('CVV'),
  575. '#default_value' => $default_cvv,
  576. '#attributes' => array('autocomplete' => 'off'),
  577. '#size' => variable_get('uc_credit_amex', TRUE) ? 4 : 3,
  578. '#maxlength' => variable_get('uc_credit_amex', TRUE) ? 4 : 3,
  579. '#field_suffix' => theme('uc_credit_cvv_help'),
  580. );
  581. }
  582. if (variable_get('uc_credit_bank_enabled', FALSE)) {
  583. $form['cc_bank'] = array(
  584. '#type' => 'textfield',
  585. '#title' => t('Issuing bank'),
  586. '#default_value' => isset($order->payment_details['cc_bank']) ? $order->payment_details['cc_bank'] : '',
  587. '#attributes' => array('autocomplete' => 'off'),
  588. '#size' => 32,
  589. '#maxlength' => 64,
  590. );
  591. }
  592. unset($_SESSION['clear_cc']);
  593. return $form;
  594. }
  595. /**
  596. * Builds the "Process Card" button on the order view.
  597. *
  598. * @see uc_credit_order_view_form_submit()
  599. */
  600. function uc_credit_order_view_form($form, &$form_state, $order_id) {
  601. $form['order_id'] = array(
  602. '#type' => 'hidden',
  603. '#value' => $order_id,
  604. );
  605. $form['actions'] = array('#type' => 'actions');
  606. $form['actions']['submit'] = array(
  607. '#type' => 'submit',
  608. '#value' => t('Process card'),
  609. );
  610. return $form;
  611. }
  612. /**
  613. * Submit handler for order view form.
  614. *
  615. * @see uc_credit_order_view_form()
  616. */
  617. function uc_credit_order_view_form_submit($form, &$form_state) {
  618. $form_state['redirect'] = 'admin/store/orders/' . $form_state['values']['order_id'] . '/credit';
  619. }
  620. /**
  621. * Returns a credit card number with appropriate masking.
  622. */
  623. function uc_credit_display_number($number) {
  624. if (strlen($number) == 4) {
  625. return t('(Last 4) ') . $number;
  626. }
  627. return str_repeat('-', 12) . substr($number, -4);
  628. }
  629. /**
  630. * Caches CC details on a pageload for use in various functions.
  631. *
  632. * @param string $op
  633. * The cache operation to perform; either 'save', 'load', or 'clear'.
  634. * @param string $data
  635. * The encrypted, serialized string containing the CC data.
  636. *
  637. * @return array
  638. * An array of credit card details.
  639. */
  640. function uc_credit_cache($op, $data = NULL, $encrypted = TRUE) {
  641. // The CC data will be stored in this static variable.
  642. static $cc_cache = array();
  643. if ($op == 'save') {
  644. if ($encrypted) {
  645. // Initialize the encryption key and class.
  646. $key = uc_credit_encryption_key();
  647. $crypt = new UbercartEncryption();
  648. // Save the unencrypted CC details for the duration of this request.
  649. // In recent versions, we base64_encode() the payment details before
  650. // encrypting. We can detect encoded data by the lack of colons,
  651. // due to base64's limited character set.
  652. $data = $crypt->decrypt($key, $data);
  653. if (strpos($data, ':') === FALSE) {
  654. $data = base64_decode($data);
  655. }
  656. $cc_cache = @unserialize($data);
  657. }
  658. else {
  659. $cc_cache = $data;
  660. }
  661. }
  662. elseif ($op == 'clear') {
  663. $cc_cache = array();
  664. }
  665. return $cc_cache;
  666. }
  667. /**
  668. * Caches the encrypted CC data on the review order form for processing.
  669. */
  670. function uc_credit_cart_review_back_submit($form, &$form_state) {
  671. $session_card_data = base64_decode($_POST['sescrd']);
  672. $_SESSION['sescrd'] = $session_card_data;
  673. uc_credit_cache('save', $session_card_data);
  674. }
  675. /**
  676. * Caches the encrypted CC data on the review order form for processing.
  677. */
  678. function uc_credit_cart_review_pre_form_submit($form, &$form_state) {
  679. $session_card_data = base64_decode($_POST['sescrd']);
  680. $_SESSION['sescrd'] = $session_card_data;
  681. uc_credit_cache('save', $session_card_data);
  682. }
  683. /**
  684. * Clears the temporary CC data if the review order form submits.
  685. */
  686. function uc_credit_cart_review_post_form_submit($form, &$form_state) {
  687. if (!empty($_SESSION['uc_checkout'][$_SESSION['cart_order']]['do_complete'])) {
  688. // Otherwise stuff it back in the session for the next pageload.
  689. unset($_SESSION['sescrd']);
  690. }
  691. }
  692. /**
  693. * Validates a CVV number during checkout.
  694. */
  695. function _uc_credit_valid_cvv($cvv) {
  696. $digits = array();
  697. if (variable_get('uc_credit_visa', TRUE) ||
  698. variable_get('uc_credit_mastercard', TRUE) ||
  699. variable_get('uc_credit_discover', TRUE)) {
  700. $digits[] = 3;
  701. }
  702. if (variable_get('uc_credit_amex', TRUE)) {
  703. $digits[] = 4;
  704. }
  705. // Fail validation if it's non-numeric or an incorrect length.
  706. if (!is_numeric($cvv) || (count($digits) > 0 && !in_array(strlen($cvv), $digits))) {
  707. return FALSE;
  708. }
  709. return TRUE;
  710. }
  711. /**
  712. * Validates a credit card number during checkout.
  713. *
  714. * @param string $number
  715. * Credit card number as a string.
  716. *
  717. * @return bool
  718. * TRUE if card number is valid according to the Luhn algorithm.
  719. *
  720. * @see https://en.wikipedia.org/wiki/Luhn_algorithm
  721. */
  722. function _uc_credit_valid_card_number($number) {
  723. $id = substr($number, 0, 1);
  724. if (($id == 3 && !variable_get('uc_credit_amex', TRUE)) ||
  725. ($id == 4 && !variable_get('uc_credit_visa', TRUE)) ||
  726. ($id == 5 && !variable_get('uc_credit_mastercard', TRUE)) ||
  727. ($id == 6 && !variable_get('uc_credit_discover', TRUE)) ||
  728. !ctype_digit($number)) {
  729. return FALSE;
  730. }
  731. $total = 0;
  732. for ($i = 0; $i < strlen($number); $i++) {
  733. $digit = substr($number, $i, 1);
  734. if ((strlen($number) - $i - 1) % 2) {
  735. $digit *= 2;
  736. if ($digit > 9) {
  737. $digit -= 9;
  738. }
  739. }
  740. $total += $digit;
  741. }
  742. if ($total % 10 != 0) {
  743. return FALSE;
  744. }
  745. return TRUE;
  746. }
  747. /**
  748. * Validates a start date on a card.
  749. *
  750. * @param int $month
  751. * The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12.
  752. * @param int $year
  753. * The 4-digit numeric representation of the year, i.e. 2008.
  754. *
  755. * @return bool
  756. * TRUE for cards whose start date is blank (both month and year) or in the
  757. * past, FALSE otherwise.
  758. */
  759. function _uc_credit_valid_card_start($month, $year) {
  760. if (empty($month) && empty($year)) {
  761. return TRUE;
  762. }
  763. if (empty($month) || empty($year)) {
  764. return FALSE;
  765. }
  766. if ($year > date('Y')) {
  767. return FALSE;
  768. }
  769. elseif ($year == date('Y')) {
  770. if ($month > date('n')) {
  771. return FALSE;
  772. }
  773. }
  774. return TRUE;
  775. }
  776. /**
  777. * Validates an expiration date on a card.
  778. *
  779. * @param int $month
  780. * The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12.
  781. * @param int $year
  782. * The 4-digit numeric representation of the year, i.e. 2008.
  783. *
  784. * @return bool
  785. * TRUE for non-expired cards, FALSE for expired.
  786. */
  787. function _uc_credit_valid_card_expiration($month, $year) {
  788. if ($year < date('Y')) {
  789. return FALSE;
  790. }
  791. elseif ($year == date('Y')) {
  792. if ($month < date('n')) {
  793. return FALSE;
  794. }
  795. }
  796. return TRUE;
  797. }
  798. /**
  799. * Validates an issue number on a card; returns TRUE or FALSE.
  800. *
  801. * @param string $issue
  802. * The issue number.
  803. *
  804. * @return bool
  805. * TRUE if the issue number if valid, FALSE otherwise.
  806. */
  807. function _uc_credit_valid_card_issue($issue) {
  808. if (empty($issue) || (is_numeric($issue) && $issue > 0)) {
  809. return TRUE;
  810. }
  811. return FALSE;
  812. }
  813. /**
  814. * Loads the key for CC number encryption from a file.
  815. *
  816. * Path to key file is stored in system variable 'uc_credit_encryption_path'.
  817. * Key file name is stored in constant UC_CREDIT_KEYFILE_NAME.
  818. *
  819. * @return string|false
  820. * Key, or FALSE if no encryption key is found.
  821. */
  822. function uc_credit_encryption_key() {
  823. static $key = FALSE;
  824. if (empty($key)) {
  825. $key_file = variable_get('uc_credit_encryption_path', '') . '/' . UC_CREDIT_KEYFILE_NAME;
  826. $contents = @file_get_contents($key_file);
  827. if (strlen($contents) == 32) {
  828. $key = $contents;
  829. }
  830. }
  831. return $key;
  832. }
  833. /**
  834. * Saves a CC data array to an order's data array.
  835. */
  836. function _uc_credit_save_cc_data_to_order($cc_data, $order_id) {
  837. // Save only some limited, PCI compliant data.
  838. $cc_data['cc_number'] = substr($cc_data['cc_number'], -4);
  839. unset($cc_data['cc_cvv']);
  840. // Load up the existing data array.
  841. $data = db_query("SELECT data FROM {uc_orders} WHERE order_id = :id", array(':id' => $order_id))->fetchField();
  842. $data = unserialize($data);
  843. // Stuff the serialized and encrypted CC details into the array.
  844. $crypt = new UbercartEncryption();
  845. $data['cc_data'] = $crypt->encrypt(uc_credit_encryption_key(), base64_encode(serialize($cc_data)));
  846. uc_store_encryption_errors($crypt, 'uc_credit');
  847. // Save it again.
  848. db_update('uc_orders')
  849. ->fields(array('data' => serialize($data)))
  850. ->condition('order_id', $order_id)
  851. ->execute();
  852. }
  853. /**
  854. * Returns an array of default credit card transaction types.
  855. *
  856. * @return array
  857. * Associative array of transaction types, keyed by defined constant value.
  858. */
  859. function uc_credit_transaction_types() {
  860. $types = array(
  861. UC_CREDIT_AUTH_ONLY => t('Authorization only'),
  862. UC_CREDIT_PRIOR_AUTH_CAPTURE => t('Prior authorization capture'),
  863. UC_CREDIT_AUTH_CAPTURE => t('Authorize and capture immediately'),
  864. UC_CREDIT_REFERENCE_TXN => t('Reference transaction'),
  865. );
  866. return $types;
  867. }
  868. /**
  869. * Retrieves the ID of the default credit card gateway.
  870. *
  871. * @return string|false
  872. * A string containing the ID of the default gateway or FALSE if none exists
  873. * or none have valid credit callbacks.
  874. */
  875. function uc_credit_default_gateway() {
  876. // Get an array of enabled payment gateways available for the payment method.
  877. $gateways = _uc_payment_gateway_list('credit', TRUE);
  878. // Return FALSE if we found no gateways.
  879. if (empty($gateways)) {
  880. return FALSE;
  881. }
  882. // Find the default gateway, or otherwise choose the first available.
  883. $default = variable_get('uc_payment_credit_gateway', 'none');
  884. $gateway = isset($gateways[$default]) ? $gateways[$default] : reset($gateways);
  885. // Return FALSE if the credit callback does not exist.
  886. return function_exists($gateway['credit']) ? $gateway['id'] : FALSE;
  887. }
  888. /**
  889. * Stores a credit card authorization to an order's data array.
  890. *
  891. * @param int $order_id
  892. * The order associated with the credit card authorization.
  893. * @param string $auth_id
  894. * The payment service's ID for the authorization.
  895. * @param float $amount
  896. * The amount that was authorized on the card.
  897. *
  898. * @return array
  899. * The entire updated data array for the order.
  900. */
  901. function uc_credit_log_authorization($order_id, $auth_id, $amount) {
  902. // Load the existing order data array.
  903. $data = db_query("SELECT data FROM {uc_orders} WHERE order_id = :id", array(':id' => $order_id))->fetchField();
  904. $data = unserialize($data);
  905. // Add the authorization to the cc_txns.
  906. $data['cc_txns']['authorizations'][$auth_id] = array(
  907. 'amount' => $amount,
  908. 'authorized' => REQUEST_TIME,
  909. );
  910. // Save the updated data array to the database.
  911. db_update('uc_orders')
  912. ->fields(array('data' => serialize($data)))
  913. ->condition('order_id', $order_id)
  914. ->execute();
  915. return $data;
  916. }
  917. /**
  918. * Logs the capture of a prior authorization to an order's data array.
  919. *
  920. * @param int $order_id
  921. * The order associated with the credit card capture.
  922. * @param string $auth_id
  923. * The payment service's ID for the authorization that was captured.
  924. *
  925. * @return array|false
  926. * The entire updated data array for the order or FALSE to indicate the
  927. * specified authorization was not found.
  928. */
  929. function uc_credit_log_prior_auth_capture($order_id, $auth_id) {
  930. // Load the existing order data array.
  931. $data = db_query("SELECT data FROM {uc_orders} WHERE order_id = :id", array(':id' => $order_id))->fetchField();
  932. $data = unserialize($data);
  933. // Return FALSE if we can't find the authorization.
  934. if (empty($data['cc_txns']['authorizations'][$auth_id])) {
  935. return FALSE;
  936. }
  937. // Otherwise log the capture timestamp to the authorization.
  938. $data['cc_txns']['authorizations'][$auth_id]['captured'] = REQUEST_TIME;
  939. // Save the updated data array to the database.
  940. db_update('uc_orders')
  941. ->fields(array('data' => serialize($data)))
  942. ->condition('order_id', $order_id)
  943. ->execute();
  944. return $data;
  945. }
  946. /**
  947. * Logs a credit card reference to an order's data array.
  948. *
  949. * @param int $order_id
  950. * The order associated with the credit card details.
  951. * @param string $ref_id
  952. * The payment service's ID for the reference that may be used to charge the
  953. * same credit card at a later date.
  954. * @param string $cc_number
  955. * The credit card number associated with this reference. Only the last 4
  956. * digits will be stored.
  957. *
  958. * @return array
  959. * The entire updated data array for the order.
  960. */
  961. function uc_credit_log_reference($order_id, $ref_id, $cc_number) {
  962. // Load the existing order data array.
  963. $data = db_query("SELECT data FROM {uc_orders} WHERE order_id = :id", array(':id' => $order_id))->fetchField();
  964. $data = unserialize($data);
  965. $data['cc_txns']['references'][$ref_id] = array(
  966. 'card' => substr($cc_number, -4),
  967. 'created' => REQUEST_TIME,
  968. );
  969. // Save the updated data array to the database.
  970. db_update('uc_orders')
  971. ->fields(array('data' => serialize($data)))
  972. ->condition('order_id', $order_id)
  973. ->execute();
  974. return $data;
  975. }
  976. /**
  977. * Returns the credit transaction types available for a payment gateway.
  978. */
  979. function uc_credit_gateway_txn_types($gateway) {
  980. $types = array();
  981. // Get the transaction types associated with this gateway.
  982. $types = _uc_payment_gateway_data($gateway, 'credit_txn_types');
  983. // Default to authorization plus capture if none are specified.
  984. if (empty($types)) {
  985. if (!is_null(_uc_payment_gateway_data($gateway, 'credit'))) {
  986. $types = array(UC_CREDIT_AUTH_CAPTURE);
  987. }
  988. else {
  989. // Or an empty array if the gateway doesn't even handle credit payments.
  990. $types = array();
  991. }
  992. }
  993. return $types;
  994. }
  995. /**
  996. * Title callback for admin/store/orders/%uc_order/credit.
  997. */
  998. function uc_credit_terminal_title($order) {
  999. return t('Credit card terminal: Order @order_id', array('@order_id' => $order->order_id));
  1000. }