uc_credit.module 37 KB

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