uc_payment.module 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. <?php
  2. /**
  3. * @file
  4. * Defines the payment API that lets payment modules interact with Ubercart.
  5. *
  6. * The payment system in Ubercart relies on hooks to let the main program know
  7. * what payment modules are installed and what their current settings are. The
  8. * customer can choose a payment type at checkout, and the proper information
  9. * will be collected to complete the purchase.
  10. */
  11. require_once dirname(__FILE__) . '/uc_payment_checkout_pane.inc';
  12. require_once dirname(__FILE__) . '/uc_payment_order_pane.inc';
  13. /**
  14. * Implements hook_menu().
  15. */
  16. function uc_payment_menu() {
  17. $items['admin/store/settings/payment'] = array(
  18. 'title' => 'Payment methods',
  19. 'description' => 'Choose and configure available payment methods.',
  20. 'page callback' => 'drupal_get_form',
  21. 'page arguments' => array('uc_payment_methods_form'),
  22. 'access arguments' => array('administer store'),
  23. 'file' => 'uc_payment.admin.inc',
  24. );
  25. $items['admin/store/settings/payment/method/%'] = array(
  26. 'title callback' => 'uc_payment_method_title',
  27. 'title arguments' => array(5),
  28. 'description' => 'Configures a specific payment method.',
  29. 'page callback' => 'drupal_get_form',
  30. 'page arguments' => array('uc_payment_method_settings_form', 5),
  31. 'access arguments' => array('administer store'),
  32. 'file' => 'uc_payment.admin.inc',
  33. );
  34. $items += rules_ui()->config_menu('admin/store/settings/payment');
  35. $items['admin/store/orders/%uc_order/payments'] = array(
  36. 'title' => 'Payments',
  37. 'description' => 'Show payments made against an order.',
  38. 'page callback' => 'drupal_get_form',
  39. 'page arguments' => array('uc_payment_by_order_form', 3),
  40. 'access arguments' => array('view payments'),
  41. 'weight' => 5,
  42. 'type' => MENU_LOCAL_TASK,
  43. 'file' => 'uc_payment.admin.inc',
  44. );
  45. $items['admin/store/orders/%uc_order/payments/%uc_payment/delete'] = array(
  46. 'title' => 'Delete payment?',
  47. 'description' => 'Delete payment?',
  48. 'page callback' => 'drupal_get_form',
  49. 'page arguments' => array('uc_payment_delete_confirm_form', 3, 5),
  50. 'access arguments' => array('delete payments'),
  51. 'type' => MENU_CALLBACK,
  52. 'file' => 'uc_payment.admin.inc',
  53. );
  54. return $items;
  55. }
  56. /**
  57. * Title callback for payment method settings.
  58. */
  59. function uc_payment_method_title($method_id) {
  60. return t('!method settings', array('!method' => _uc_payment_method_data($method_id, 'name')));
  61. }
  62. /**
  63. * Implements hook_permission().
  64. */
  65. function uc_payment_permission() {
  66. return array(
  67. 'view payments' => array(
  68. 'title' => t('View payments'),
  69. ),
  70. 'manual payments' => array(
  71. 'title' => t('Manual payments'),
  72. 'description' => t('Enter payments manually.'),
  73. ),
  74. 'delete payments' => array(
  75. 'title' => t('Delete payments'),
  76. ),
  77. );
  78. }
  79. /**
  80. * Implements hook_theme().
  81. */
  82. function uc_payment_theme() {
  83. return array(
  84. 'uc_payment_totals' => array(
  85. 'variables' => array('order' => NULL),
  86. 'file' => 'uc_payment.theme.inc',
  87. ),
  88. 'uc_payment_method_table' => array(
  89. 'render element' => 'form',
  90. 'file' => 'uc_payment.admin.inc',
  91. ),
  92. );
  93. }
  94. /**
  95. * Implements hook_form_FORM_ID_alter() for uc_cart_view_form().
  96. *
  97. * Adds express buttons for enabled payment modules directly to the cart page.
  98. */
  99. function uc_payment_form_uc_cart_view_form_alter(&$form, &$form_state) {
  100. $methods = _uc_payment_method_list();
  101. foreach ($methods as $id => $method) {
  102. if ($method['checkout'] && isset($method['express']) && $express = $method['express'](array(), $form_state)) {
  103. $form['actions']['checkout'][$id] = $express;
  104. }
  105. }
  106. }
  107. /**
  108. * Implements hook_form_FORM_ID_alter() for uc_cart_checkout_review_form().
  109. *
  110. * If a payment method redirects off-site, add the required form to the review
  111. * page.
  112. */
  113. function uc_payment_form_uc_cart_checkout_review_form_alter(&$form, &$form_state) {
  114. $order = $form_state['uc_order'];
  115. if ($redirect = _uc_payment_method_data($order->payment_method, 'redirect')) {
  116. unset($form['actions']['submit']);
  117. $suffix = drupal_get_form($redirect, $order);
  118. $form['#suffix'] = drupal_render($suffix);
  119. }
  120. }
  121. /**
  122. * Implements hook_uc_order().
  123. */
  124. function uc_payment_uc_order($op, $order) {
  125. if (!isset($order->payment_method)) {
  126. $order->payment_method = '';
  127. }
  128. switch ($op) {
  129. case 'submit':
  130. $func = _uc_payment_method_data($order->payment_method, 'callback');
  131. if (function_exists($func)) {
  132. return $func('order-submit', $order);
  133. }
  134. break;
  135. case 'load':
  136. $func = _uc_payment_method_data($order->payment_method, 'callback');
  137. if (function_exists($func)) {
  138. $func('order-load', $order);
  139. }
  140. break;
  141. case 'save':
  142. $func = _uc_payment_method_data($order->payment_method, 'callback');
  143. if (function_exists($func)) {
  144. $func('order-save', $order);
  145. }
  146. break;
  147. case 'can_delete':
  148. if (uc_payment_load_payments($order->order_id) !== FALSE) {
  149. return FALSE;
  150. }
  151. break;
  152. case 'delete':
  153. db_delete('uc_payment_receipts')
  154. ->condition('order_id', $order->order_id)
  155. ->execute();
  156. // Call each payment method to delete method-specific data from the
  157. // database.
  158. $methods = _uc_payment_method_list();
  159. foreach ($methods as $method) {
  160. $func = $method['callback'];
  161. if (function_exists($func)) {
  162. $func('order-delete', $order);
  163. }
  164. }
  165. break;
  166. }
  167. }
  168. /**
  169. * Implements hook_uc_checkout_pane().
  170. */
  171. function uc_payment_uc_checkout_pane() {
  172. $panes['payment'] = array(
  173. 'title' => t('Payment method'),
  174. 'desc' => t('Select a payment method from the enabled payment modules.'),
  175. 'callback' => 'uc_checkout_pane_payment',
  176. 'weight' => 6,
  177. );
  178. return $panes;
  179. }
  180. /**
  181. * Implements hook_uc_order_pane().
  182. */
  183. function uc_payment_uc_order_pane() {
  184. $panes['payment'] = array(
  185. 'callback' => 'uc_order_pane_payment',
  186. 'title' => t('Payment'),
  187. 'desc' => t('Specify and collect payment for an order.'),
  188. 'class' => 'pos-left',
  189. 'weight' => 4,
  190. 'show' => array('view', 'edit', 'customer'),
  191. );
  192. return $panes;
  193. }
  194. /**
  195. * Implements hook_uc_order_state().
  196. */
  197. function uc_payment_uc_order_state() {
  198. $states['payment_received'] = array(
  199. 'title' => t('Payment received'),
  200. 'weight' => 10,
  201. 'scope' => 'general',
  202. );
  203. return $states;
  204. }
  205. /**
  206. * Implements hook_uc_payment_method().
  207. */
  208. function uc_payment_uc_payment_method() {
  209. $methods['free_order'] = array(
  210. 'name' => t('Free order'),
  211. 'title' => t('No payment required'),
  212. 'desc' => t('Allow customers with !zero order totals to checkout without paying.', array('!zero' => uc_currency_format(0))),
  213. 'callback' => 'uc_payment_method_free_order',
  214. 'checkout' => TRUE,
  215. 'no_gateway' => TRUE,
  216. 'weight' => 0,
  217. );
  218. return $methods;
  219. }
  220. /**
  221. * Implements hook_uc_payment_method_checkout_alter().
  222. */
  223. function uc_payment_uc_payment_method_checkout_alter(&$methods, $order) {
  224. if (isset($methods['free_order'])) {
  225. if ($order->order_total < 0.01) {
  226. // Unset all other payment methods if this is a free order.
  227. foreach (array_keys($methods) as $key) {
  228. if ($key != 'free_order') {
  229. unset($methods[$key]);
  230. }
  231. }
  232. }
  233. else {
  234. // Disallow this payment method if the order is not free.
  235. unset($methods['free_order']);
  236. }
  237. }
  238. }
  239. /**
  240. * Payment method callback for free orders.
  241. *
  242. * @see uc_payment_uc_payment_method()
  243. */
  244. function uc_payment_method_free_order($op, &$order) {
  245. switch ($op) {
  246. case 'cart-details':
  247. return array(
  248. '#markup' => t('Continue with checkout to complete your order.'),
  249. );
  250. case 'order-submit':
  251. if ($order->order_total >= 0.01) {
  252. return array(array(
  253. 'pass' => FALSE,
  254. 'message' => t('We cannot process your order without payment.'),
  255. ));
  256. }
  257. uc_payment_enter($order->order_id, 'free_order', 0, 0, NULL, t('Checkout completed for a free order.'));
  258. }
  259. }
  260. /**
  261. * Returns a formatted list of line items for an order total preview.
  262. *
  263. * @param $return
  264. * TRUE or FALSE to specify whether or not to return the results instead of
  265. * printing them and exiting.
  266. * @param $order
  267. * Optionally pass in a full order object to use instead of finding it in the
  268. * $_POST data.
  269. *
  270. * @return
  271. * The formatted HTML of the order total preview if $return is set to TRUE.
  272. */
  273. function uc_payment_get_totals($form, $form_state) {
  274. $commands[] = ajax_command_replace('#line-items-div', trim(drupal_render($form['panes']['payment']['line_items'])));
  275. return array('#type' => 'ajax', '#commands' => $commands);
  276. }
  277. /**
  278. * TAPIr table definition for uc_payments_table.
  279. */
  280. function uc_payments_table() {
  281. $table = array(
  282. '#type' => 'tapir_table',
  283. '#tree' => TRUE,
  284. '#columns' => array(
  285. 'received' => array(
  286. 'cell' => t('Received'),
  287. 'weight' => 0,
  288. ),
  289. 'user' => array(
  290. 'cell' => t('User'),
  291. 'weight' => 1,
  292. ),
  293. 'method' => array(
  294. 'cell' => t('Method'),
  295. 'weight' => 2,
  296. ),
  297. 'amount' => array(
  298. 'cell' => t('Amount'),
  299. 'weight' => 3,
  300. ),
  301. 'balance' => array(
  302. 'cell' => t('Balance'),
  303. 'weight' => 4,
  304. ),
  305. 'comment' => array(
  306. 'cell' => t('Comment'),
  307. 'weight' => 5,
  308. ),
  309. 'action' => array(
  310. 'cell' => t('Action'),
  311. 'weight' => 6,
  312. ),
  313. ),
  314. '#rows' => array(),
  315. );
  316. return $table;
  317. }
  318. /**
  319. * Processes a payment through an enabled payment gateway.
  320. *
  321. * @param $method
  322. * The ID of the payment method to use to process the payment.
  323. * @param $order_id
  324. * The ID of the order associated with this payment.
  325. * @param $amount
  326. * The amount of the payment we're attempting to collect.
  327. * @param $data
  328. * An array of data passed on to the payment gateway module used to process
  329. * the payment for the specified payment method.
  330. * @param $default
  331. * TRUE or FALSE to indicate we're forcing the use of the default gateway for
  332. * the specified payment method. When TRUE, admin messages related to the
  333. * payment will be hidden from display so customers don't see them.
  334. * @param $selected
  335. * The ID of a payment gateway to use to process the payment; normally comes
  336. * from the payment gateway select form.
  337. * @param $redirect
  338. * TRUE or FALSE to indicate whether or not to redirect back to the admin
  339. * order view page for the order referenced in $order_id.
  340. *
  341. * @return
  342. * TRUE or FALSE indicating whether or not the payment was processed.
  343. */
  344. function uc_payment_process_payment($method, $order_id, $amount, $data = NULL, $default = FALSE, $selected = NULL, $redirect = TRUE) {
  345. $result = array();
  346. // Get an array of enabled payment gateways available for the payment method.
  347. $gateways = _uc_payment_gateway_list($method, TRUE);
  348. // Fail if no gateways were found for the specified method.
  349. if (empty($gateways)) {
  350. // Display an error message if messages weren't silenced.
  351. if (!$default) {
  352. drupal_set_message(t('You are not able to process %type payments.', array('%type' => _uc_payment_method_data($method, 'name'))));
  353. }
  354. return FALSE;
  355. }
  356. // Find the default gateway if requested.
  357. if ($default) {
  358. $default = variable_get('uc_payment_' . $method . '_gateway', '');
  359. }
  360. // If we only found one gateway for this payment method...
  361. if (count($gateways) == 1) {
  362. $gateway = reset($gateways);
  363. }
  364. elseif ($default && isset($gateways[$default])) {
  365. // The default gateway was forced.
  366. $gateway = $gateways[$default];
  367. }
  368. elseif ($selected && isset($gateways[$selected])) {
  369. // A specific gateway was selected.
  370. $gateway = $gateways[$selected];
  371. }
  372. else {
  373. // No gateway available.
  374. $gateway = array($method => '');
  375. }
  376. // Check to see if the function exists and process the payment.
  377. if (function_exists($gateway[$method])) {
  378. // Reset the entity cache, so the latest data saved in the credit card cache
  379. // is guaranteed to be available in the charge function.
  380. uc_order_load($order_id, TRUE);
  381. $result = $gateway[$method]($order_id, $amount, $data);
  382. }
  383. else {
  384. // Otherwise display an error message to administrators.
  385. $result['success'] = FALSE;
  386. $result['message'] = t('An error has occurred with your payment gateway. The charge function could not be found.');
  387. if (user_access('administer store')) {
  388. drupal_set_message($result['message']);
  389. }
  390. }
  391. // If the payment processed successfully...
  392. if ($result['success'] === TRUE) {
  393. // Log the payment to the order if not disabled.
  394. if (!isset($result['log_payment']) || $result['log_payment'] !== FALSE) {
  395. uc_payment_enter($order_id, $method, $amount, empty($result['uid']) ? 0 : $result['uid'], empty($result['data']) ? '' : $result['data'], empty($result['comment']) ? '' : $result['comment']);
  396. }
  397. }
  398. else {
  399. // Otherwise display the failure message in the logs.
  400. watchdog('uc_payment', 'Payment failed for order @order_id: @message', array('@order_id' => $order_id, '@message' => $result['message']), WATCHDOG_WARNING, l(t('view order'), 'admin/store/orders/' . $order_id));
  401. }
  402. // If we have a message for display and aren't simply charging with the
  403. // default gateway for a customer...
  404. if (!empty($result['message']) && !$default) {
  405. drupal_set_message($result['message']);
  406. }
  407. // Head back to the order if a redirect was specified.
  408. if ($redirect) {
  409. drupal_goto('admin/store/orders/' . $order_id);
  410. }
  411. return $result['success'];
  412. }
  413. /**
  414. * Enters a payment for an order.
  415. *
  416. * @param $order_id
  417. * The order ID to apply the payment to.
  418. * @param $method
  419. * The payment method ID.
  420. * @param $amount
  421. * The amount of the payment.
  422. * @param $uid
  423. * (optional) The user ID of the person logging the payment, or 0 if the
  424. * payment was processed automatically.
  425. * @param $data
  426. * (optional) Any data that should be serialized and stored with the
  427. * payment.
  428. * @param $comment
  429. * (optional) The comment to enter in the payment log.
  430. * @param $received
  431. * (optional) The timestamp at which the payment was received.
  432. *
  433. * @return
  434. * A unique ID identifying the payment.
  435. */
  436. function uc_payment_enter($order_id, $method, $amount, $uid = 0, $data = NULL, $comment = '', $received = REQUEST_TIME) {
  437. $method_name = _uc_payment_method_data($method, 'review');
  438. if (empty($method_name)) {
  439. $method_name = _uc_payment_method_data($method, 'name');
  440. }
  441. if (is_null($method_name)) {
  442. $method_name = t('Other');
  443. }
  444. if (is_array($data)) {
  445. $data = serialize($data);
  446. }
  447. $log_message = t('@method payment for @amount entered.', array('@method' => $method_name, '@amount' => uc_currency_format($amount)));
  448. uc_order_log_changes($order_id, array($log_message));
  449. $receipt_id = db_insert('uc_payment_receipts')
  450. ->fields(array(
  451. 'order_id' => $order_id,
  452. 'method' => $method_name,
  453. 'amount' => $amount,
  454. 'uid' => $uid,
  455. 'data' => $data,
  456. 'comment' => $comment,
  457. 'received' => $received,
  458. ))
  459. ->execute();
  460. $order = uc_order_load($order_id, TRUE);
  461. $account = user_load($uid);
  462. // Ensure user has an account before payment is made.
  463. if (module_exists('uc_cart')) {
  464. uc_cart_complete_sale($order);
  465. }
  466. module_invoke_all('uc_payment_entered', $order, $method, $amount, $account, $data, $comment);
  467. rules_invoke_event('uc_payment_entered', $order, $account);
  468. return $receipt_id;
  469. }
  470. /**
  471. * Deletes a payment from the database.
  472. */
  473. function uc_payment_delete($receipt_id) {
  474. if (!is_numeric($receipt_id)) {
  475. return FALSE;
  476. }
  477. $payment = uc_payment_load($receipt_id);
  478. $log_message = t('@method payment for @amount deleted.', array('@method' => $payment->method, '@amount' => uc_currency_format($payment->amount)));
  479. uc_order_log_changes($payment->order_id, array($log_message));
  480. db_delete('uc_payment_receipts')
  481. ->condition('receipt_id', $receipt_id)
  482. ->execute();
  483. }
  484. /**
  485. * Returns the balance of payments on an order.
  486. */
  487. function uc_payment_balance($order) {
  488. $total = $order->order_total;
  489. $payments = uc_payment_load_payments($order->order_id);
  490. if ($payments === FALSE) {
  491. return $total;
  492. }
  493. foreach ($payments as $payment) {
  494. $total -= $payment->amount;
  495. }
  496. return $total;
  497. }
  498. /**
  499. * Loads a single payment from the database by receipt_id.
  500. */
  501. function uc_payment_load($receipt_id) {
  502. if (!is_numeric($receipt_id)) {
  503. return FALSE;
  504. }
  505. $payment = db_query("SELECT * FROM {uc_payment_receipts} WHERE receipt_id = :id", array(':id' => $receipt_id))->fetchObject();
  506. return $payment;
  507. }
  508. /**
  509. * Loads an array of all the payments for an order.
  510. *
  511. * @param $order_id
  512. * The order's id.
  513. *
  514. * @return
  515. * Array of payment objects or FALSE if there are none.
  516. */
  517. function uc_payment_load_payments($order_id) {
  518. $payments = db_query("SELECT * FROM {uc_payment_receipts} WHERE order_id = :id ORDER BY received ASC", array(':id' => $order_id))->fetchAll();
  519. if (count($payments) == 0) {
  520. $payments = FALSE;
  521. }
  522. return $payments;
  523. }
  524. /**
  525. * Builds a list of payment methods defined in the enabled modules.
  526. */
  527. function _uc_payment_method_list($action = NULL) {
  528. static $methods = array();
  529. if (count($methods) > 0 && $action !== 'rebuild') {
  530. return $methods;
  531. }
  532. foreach (module_invoke_all('uc_payment_method') as $id => $method) {
  533. // Preserve backward compatibility for methods with no key specified.
  534. if (is_numeric($id)) {
  535. $id = $method['id'];
  536. }
  537. $methods[$id] = array_merge($method, array(
  538. 'id' => $id,
  539. 'checkout' => variable_get('uc_payment_method_' . $id . '_checkout', $method['checkout']),
  540. 'weight' => variable_get('uc_payment_method_' . $id . '_weight', $method['weight']),
  541. ));
  542. }
  543. // Allow other modules to alter the payment methods.
  544. drupal_alter('uc_payment_method', $methods);
  545. uasort($methods, 'uc_weight_sort');
  546. return $methods;
  547. }
  548. /**
  549. * Returns data from a payment method by method ID and the array key.
  550. */
  551. function _uc_payment_method_data($method_id, $key) {
  552. $methods = _uc_payment_method_list();
  553. return isset($methods[$method_id][$key]) ? $methods[$method_id][$key] : NULL;
  554. }
  555. /**
  556. * Builds a list of payment gateways defined in the enabled modules.
  557. */
  558. function _uc_payment_gateway_list($filter = NULL, $enabled_only = FALSE) {
  559. $gateways = array();
  560. foreach (module_invoke_all('uc_payment_gateway') as $id => $gateway) {
  561. // Preserve backward compatibility for gateways with no key specified.
  562. if (is_numeric($id)) {
  563. $id = $gateway['id'];
  564. }
  565. $gateways[$id] = array_merge($gateway, array(
  566. 'id' => $id,
  567. 'enabled' => variable_get('uc_pg_' . $id . '_enabled', TRUE),
  568. ));
  569. }
  570. // Allow other modules to alter the payment gateways.
  571. drupal_alter('uc_payment_gateway', $gateways);
  572. foreach ($gateways as $id => $gateway) {
  573. if ($filter && (!isset($gateway[$filter]) || !function_exists($gateway[$filter]))) {
  574. unset($gateways[$id]);
  575. continue;
  576. }
  577. if ($enabled_only && !$gateway['enabled']) {
  578. unset($gateways[$id]);
  579. }
  580. }
  581. return $gateways;
  582. }
  583. /**
  584. * Returns data from a payment gateway by gateway ID and the array key.
  585. *
  586. * @param $gateway_id
  587. * The ID of the payment gateway to query.
  588. * @param $key
  589. * The key of the data being requested.
  590. *
  591. * @return
  592. * The requested data.
  593. */
  594. function _uc_payment_gateway_data($gateway_id, $key) {
  595. $gateways = _uc_payment_gateway_list();
  596. return isset($gateways[$gateway_id][$key]) ? $gateways[$gateway_id][$key] : NULL;
  597. }
  598. /**
  599. * Returns an option list of payment methods.
  600. */
  601. function uc_payment_method_options_list() {
  602. $options = array();
  603. foreach (_uc_payment_method_list() as $id => $method) {
  604. $options[$id] = $method['name'];
  605. }
  606. return $options;
  607. }
  608. /**
  609. * Implements hook_views_api().
  610. */
  611. function uc_payment_views_api() {
  612. return array(
  613. 'api' => '2.0',
  614. 'path' => drupal_get_path('module', 'uc_payment') . '/views',
  615. );
  616. }