uc_paypal.pages.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. /**
  3. * @file
  4. * Paypal administration menu items.
  5. */
  6. /**
  7. * Processes Instant Payment Notifiations from PayPal.
  8. */
  9. function uc_paypal_ipn() {
  10. if (!isset($_POST['invoice'])) {
  11. watchdog('uc_paypal', 'IPN attempted with invalid order ID.', array(), WATCHDOG_ERROR);
  12. return;
  13. }
  14. if (strpos($_POST['invoice'], '-') > 0) {
  15. list($order_id, $cart_id) = explode('-', $_POST['invoice']);
  16. // Sanitize order ID and cart ID
  17. $order_id = intval($order_id);
  18. $cart_id = check_plain($cart_id);
  19. if (!empty($cart_id)) {
  20. // Needed later by uc_complete_sale to empty the correct cart
  21. $_SESSION['uc_cart_id'] = $cart_id;
  22. }
  23. }
  24. else {
  25. $order_id = intval($_POST['invoice']);
  26. }
  27. watchdog('uc_paypal', 'Receiving IPN at URL for order @order_id. <pre>@debug</pre>', array('@order_id' => $order_id, '@debug' => variable_get('uc_paypal_wps_debug_ipn', FALSE) ? print_r($_POST, TRUE) : ''));
  28. $order = uc_order_load($order_id);
  29. if ($order == FALSE) {
  30. watchdog('uc_paypal', 'IPN attempted for non-existent order @order_id.', array('@order_id' => $order_id), WATCHDOG_ERROR);
  31. return;
  32. }
  33. // Assign posted variables to local variables
  34. $payment_status = check_plain($_POST['payment_status']);
  35. $payment_amount = check_plain($_POST['mc_gross']);
  36. $payment_currency = check_plain($_POST['mc_currency']);
  37. $receiver_email = check_plain($_POST['business']);
  38. if ($receiver_email == '') {
  39. $receiver_email = check_plain($_POST['receiver_email']);
  40. }
  41. $txn_id = check_plain($_POST['txn_id']);
  42. $txn_type = check_plain($_POST['txn_type']);
  43. $payer_email = check_plain($_POST['payer_email']);
  44. // Express Checkout IPNs may not have the WPS email stored. But if it is,
  45. // make sure that the right account is being paid.
  46. $uc_paypal_wps_email = trim(variable_get('uc_paypal_wps_email', ''));
  47. if (!empty($uc_paypal_wps_email) && drupal_strtolower($receiver_email) != drupal_strtolower($uc_paypal_wps_email)) {
  48. watchdog('uc_paypal', 'IPN for a different PayPal account attempted.', array(), WATCHDOG_ERROR);
  49. return;
  50. }
  51. foreach ($_POST as $key => $value) {
  52. $post_fields[] = $key . '=' . urlencode(stripslashes($value));
  53. }
  54. $post_fields[] = 'cmd=_notify-validate';
  55. if (variable_get('uc_paypal_wpp_server', '') == 'https://api-3t.paypal.com/nvp') {
  56. $host = 'https://ipnpb.paypal.com/cgi-bin/webscr';
  57. }
  58. else {
  59. $host = variable_get('uc_paypal_wps_server', 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr');
  60. }
  61. // Setup the cURL request.
  62. $ch = curl_init();
  63. curl_setopt($ch, CURLOPT_URL, $host);
  64. curl_setopt($ch, CURLOPT_VERBOSE, 0);
  65. curl_setopt($ch, CURLOPT_POST, 1);
  66. curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $post_fields));
  67. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  68. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  69. curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
  70. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  71. $response = curl_exec($ch);
  72. // Log any errors to the watchdog.
  73. if ($error = curl_error($ch)) {
  74. watchdog('uc_paypal', 'IPN failed with cURL error: @error', array('@error' => $error), WATCHDOG_ERROR);
  75. return;
  76. }
  77. curl_close($ch);
  78. if (strcmp($response, 'VERIFIED') == 0) {
  79. watchdog('uc_paypal', 'IPN transaction verified.');
  80. $duplicate = (bool) db_query_range('SELECT 1 FROM {uc_payment_paypal_ipn} WHERE txn_id = :id AND status <> :status', 0, 1, array(':id' => $txn_id, ':status' => 'Pending'))->fetchField();
  81. if ($duplicate) {
  82. if ($order->payment_method != 'credit') {
  83. watchdog('uc_paypal', 'IPN transaction ID has been processed before.', array(), WATCHDOG_NOTICE);
  84. }
  85. return;
  86. }
  87. db_insert('uc_payment_paypal_ipn')
  88. ->fields(array(
  89. 'order_id' => $order_id,
  90. 'txn_id' => $txn_id,
  91. 'txn_type' => $txn_type,
  92. 'mc_gross' => $payment_amount,
  93. 'status' => $payment_status,
  94. 'receiver_email' => $receiver_email,
  95. 'payer_email' => $payer_email,
  96. 'received' => REQUEST_TIME,
  97. ))
  98. ->execute();
  99. switch ($payment_status) {
  100. case 'Canceled_Reversal':
  101. uc_order_comment_save($order_id, 0, t('PayPal has canceled the reversal and returned !amount !currency to your account.', array('!amount' => uc_currency_format($payment_amount, FALSE), '!currency' => $payment_currency)), 'admin');
  102. break;
  103. case 'Completed':
  104. if (abs($payment_amount - $order->order_total) > 0.01) {
  105. watchdog('uc_paypal', 'Payment @txn_id for order @order_id did not equal the order total.', array('@txn_id' => $txn_id, '@order_id' => $order->order_id), WATCHDOG_WARNING, l(t('view'), 'admin/store/orders/' . $order->order_id));
  106. }
  107. $comment = t('PayPal transaction ID: @txn_id', array('@txn_id' => $txn_id));
  108. uc_payment_enter($order_id, 'paypal_wps', $payment_amount, $order->uid, NULL, $comment);
  109. uc_cart_complete_sale($order);
  110. uc_order_comment_save($order_id, 0, t('PayPal IPN reported a payment of @amount @currency.', array('@amount' => uc_currency_format($payment_amount, FALSE), '@currency' => $payment_currency)));
  111. break;
  112. case 'Denied':
  113. uc_order_comment_save($order_id, 0, t("You have denied the customer's payment."), 'admin');
  114. break;
  115. case 'Expired':
  116. uc_order_comment_save($order_id, 0, t('The authorization has failed and cannot be captured.'), 'admin');
  117. break;
  118. case 'Failed':
  119. uc_order_comment_save($order_id, 0, t("The customer's attempted payment from a bank account failed."), 'admin');
  120. break;
  121. case 'Pending':
  122. uc_order_update_status($order_id, 'paypal_pending');
  123. uc_order_comment_save($order_id, 0, t('Payment is pending at PayPal: @reason', array('@reason' => _uc_paypal_pending_message(check_plain($_POST['pending_reason'])))), 'admin');
  124. break;
  125. // You, the merchant, refunded the payment.
  126. case 'Refunded':
  127. $comment = t('PayPal transaction ID: @txn_id', array('@txn_id' => $txn_id));
  128. uc_payment_enter($order_id, 'paypal_wps', $payment_amount, $order->uid, NULL, $comment);
  129. break;
  130. case 'Reversed':
  131. watchdog('uc_paypal', 'PayPal has reversed a payment!', array(), WATCHDOG_ERROR);
  132. uc_order_comment_save($order_id, 0, t('Payment has been reversed by PayPal: @reason', array('@reason' => _uc_paypal_reversal_message(check_plain($_POST['reason_code'])))), 'admin');
  133. break;
  134. case 'Processed':
  135. uc_order_comment_save($order_id, 0, t('A payment has been accepted.'), 'admin');
  136. break;
  137. case 'Voided':
  138. uc_order_comment_save($order_id, 0, t('The authorization has been voided.'), 'admin');
  139. break;
  140. }
  141. }
  142. elseif (strcmp($response, 'INVALID') == 0) {
  143. watchdog('uc_paypal', 'IPN transaction failed verification.', array(), WATCHDOG_ERROR);
  144. uc_order_comment_save($order_id, 0, t('An IPN transaction failed verification for this order.'), 'admin');
  145. }
  146. }
  147. /**
  148. * Handles the review page for Express Checkout Mark Flow.
  149. */
  150. function uc_paypal_ec_review_redirect() {
  151. if (!isset($_SESSION['TOKEN']) || ($order = uc_order_load($_SESSION['cart_order'])) == FALSE) {
  152. unset($_SESSION['cart_order']);
  153. unset($_SESSION['have_details']);
  154. unset($_SESSION['TOKEN'], $_SESSION['PAYERID']);
  155. drupal_set_message(t('An error has occurred in your PayPal payment. Please review your cart and try again.'));
  156. drupal_goto('cart');
  157. }
  158. $nvp_request = array(
  159. 'METHOD' => 'GetExpressCheckoutDetails',
  160. 'TOKEN' => $_SESSION['TOKEN'],
  161. );
  162. $nvp_response = uc_paypal_api_request($nvp_request, variable_get('uc_paypal_wpp_server', 'https://api-3t.sandbox.paypal.com/nvp'));
  163. $_SESSION['PAYERID'] = $nvp_response['PAYERID'];
  164. drupal_goto('cart/checkout/review');
  165. }
  166. /**
  167. * Handles the review page for Express Checkout Shortcut Flow.
  168. */
  169. function uc_paypal_ec_review() {
  170. if (!isset($_SESSION['TOKEN']) || ($order = uc_order_load($_SESSION['cart_order'])) == FALSE) {
  171. unset($_SESSION['cart_order']);
  172. unset($_SESSION['have_details']);
  173. unset($_SESSION['TOKEN'], $_SESSION['PAYERID']);
  174. drupal_set_message(t('An error has occurred in your PayPal payment. Please review your cart and try again.'));
  175. drupal_goto('cart');
  176. }
  177. if (!isset($_SESSION['have_details'][$order->order_id])) {
  178. $nvp_request = array(
  179. 'METHOD' => 'GetExpressCheckoutDetails',
  180. 'TOKEN' => $_SESSION['TOKEN'],
  181. );
  182. $nvp_response = uc_paypal_api_request($nvp_request, variable_get('uc_paypal_wpp_server', 'https://api-3t.sandbox.paypal.com/nvp'));
  183. $_SESSION['PAYERID'] = $nvp_response['PAYERID'];
  184. $shipname = check_plain($nvp_response['SHIPTONAME']);
  185. if (strpos($shipname, ' ') > 0) {
  186. $order->delivery_first_name = substr($shipname, 0, strrpos(trim($shipname), ' '));
  187. $order->delivery_last_name = substr($shipname, strrpos(trim($shipname), ' ') + 1);
  188. }
  189. else {
  190. $order->delivery_first_name = $shipname;
  191. $order->delivery_last_name = '';
  192. }
  193. $country_id = db_query("SELECT country_id FROM {uc_countries} WHERE country_iso_code_2 = :code", array(':code' => $nvp_response['SHIPTOCOUNTRYCODE']))->fetchField();
  194. $zone_id = 0;
  195. if (!empty($country_id) && isset($nvp_response['SHIPTOSTATE'])) {
  196. $zone = $nvp_response['SHIPTOSTATE'];
  197. $zone_id = db_query("SELECT zone_id FROM {uc_zones} WHERE zone_country_id = :id AND (zone_code = :code OR zone_name = :name)", array(':id' => $country_id, ':code' => $zone, ':name' => $zone))->fetchField();
  198. }
  199. $order->delivery_street1 = check_plain($nvp_response['SHIPTOSTREET']);
  200. $order->delivery_street2 = isset($nvp_response['SHIPTOSTREET2']) ? check_plain($nvp_response['SHIPTOSTREET2']) : '';
  201. $order->delivery_city = check_plain($nvp_response['SHIPTOCITY']);
  202. $order->delivery_zone = !empty($zone_id) ? $zone_id : 0;
  203. $order->delivery_postal_code = check_plain($nvp_response['SHIPTOZIP']);
  204. $order->delivery_country = !empty($country_id) ? $country_id : 840;
  205. $order->billing_first_name = check_plain($nvp_response['FIRSTNAME']);
  206. $order->billing_last_name = check_plain($nvp_response['LASTNAME']);
  207. $order->billing_street1 = check_plain($nvp_response['EMAIL']);
  208. if (empty($order->primary_email)) {
  209. $order->primary_email = $nvp_response['EMAIL'];
  210. }
  211. $order->payment_method = 'paypal_ec';
  212. uc_order_save($order);
  213. $_SESSION['have_details'][$order->order_id] = TRUE;
  214. }
  215. $build['instructions'] = array('#markup' => t("Your order is almost complete! Please fill in the following details and click 'Continue checkout' to finalize the purchase."));
  216. $build['form'] = drupal_get_form('uc_paypal_ec_review_form', $order);
  217. return $build;
  218. }
  219. /**
  220. * Returns the form for the custom Review Payment screen for Express Checkout.
  221. */
  222. function uc_paypal_ec_review_form($form, &$form_state, $order) {
  223. if (module_exists('uc_quote') && variable_get('uc_paypal_ec_review_shipping', TRUE) && uc_order_is_shippable($order)) {
  224. uc_checkout_pane_quotes('prepare', $order, NULL);
  225. $order->line_items = uc_order_load_line_items($order);
  226. uc_order_save($order);
  227. $result = uc_checkout_pane_quotes('view', $order, NULL);
  228. $form['panes']['quotes'] = array(
  229. '#type' => 'fieldset',
  230. '#title' => t('Shipping cost'),
  231. '#collapsible' => FALSE,
  232. );
  233. $form['panes']['quotes'] += $result['contents'];
  234. unset($form['panes']['quotes']['quote_button']);
  235. $form['shippable'] = array('#type' => 'value', '#value' => 'true');
  236. }
  237. if (variable_get('uc_paypal_ec_review_company', TRUE)) {
  238. $form['delivery_company'] = array(
  239. '#type' => 'textfield',
  240. '#title' => uc_get_field_name('company'),
  241. '#description' => uc_order_is_shippable($order) ? t('Leave blank if shipping to a residence.') : '',
  242. '#default_value' => $order->delivery_company,
  243. );
  244. }
  245. if (variable_get('uc_paypal_ec_review_phone', TRUE)) {
  246. $form['delivery_phone'] = array(
  247. '#type' => 'textfield',
  248. '#title' => t('Contact phone number'),
  249. '#default_value' => $order->delivery_phone,
  250. '#size' => 24,
  251. );
  252. }
  253. if (variable_get('uc_paypal_ec_review_comment', TRUE)) {
  254. $form['order_comments'] = array(
  255. '#type' => 'textarea',
  256. '#title' => t('Order comments'),
  257. '#description' => t('Special instructions or notes regarding your order.'),
  258. );
  259. }
  260. if (empty($form)) {
  261. drupal_goto('cart/echeckout/submit');
  262. }
  263. $form['actions'] = array('#type' => 'actions');
  264. $form['actions']['submit'] = array(
  265. '#type' => 'submit',
  266. '#value' => t('Continue checkout'),
  267. );
  268. return $form;
  269. }
  270. function uc_paypal_ec_review_form_validate($form, &$form_state) {
  271. if (!empty($form_state['values']['shippable']) && empty($form_state['values']['quotes']['quote_option'])) {
  272. form_set_error('shipping', t('You must calculate and select a shipping option.'));
  273. }
  274. }
  275. function uc_paypal_ec_review_form_submit($form, &$form_state) {
  276. $order = uc_order_load($_SESSION['cart_order']);
  277. if (!empty($form_state['values']['shippable'])) {
  278. $quote_option = explode('---', $form_state['values']['quotes']['quote_option']);
  279. $order->quote['method'] = $quote_option[0];
  280. $order->quote['accessorials'] = $quote_option[1];
  281. $methods = uc_quote_methods();
  282. $method = $methods[$quote_option[0]];
  283. $label = $method['quote']['accessorials'][$quote_option[1]];
  284. $quote_option = $form_state['values']['quotes']['quote_option'];
  285. $order->quote['rate'] = $form_state['values']['quotes'][$quote_option]['rate'];
  286. $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", array(':id' => $order->order_id, ':type' => 'shipping'));
  287. if ($lid = $result->fetchField()) {
  288. uc_order_update_line_item($lid, $label, $order->quote['rate']);
  289. }
  290. else {
  291. uc_order_line_item_add($order->order_id, 'shipping', $label, $order->quote['rate']);
  292. }
  293. }
  294. if (variable_get('uc_paypal_ec_review_company', TRUE)) {
  295. $order->delivery_company = $form_state['values']['delivery_company'];
  296. }
  297. if (variable_get('uc_paypal_ec_review_phone', TRUE)) {
  298. $order->delivery_phone = $form_state['values']['delivery_phone'];
  299. }
  300. if (variable_get('uc_paypal_ec_review_comment', TRUE)) {
  301. db_delete('uc_order_comments')
  302. ->condition('order_id', $order->order_id)
  303. ->execute();
  304. uc_order_comment_save($order->order_id, 0, $form_state['values']['order_comments'], 'order');
  305. }
  306. uc_order_save($order);
  307. $form_state['redirect'] = 'cart/echeckout/submit';
  308. }
  309. /**
  310. * Presents the final total to the user for checkout!
  311. */
  312. function uc_paypal_ec_submit() {
  313. if (!isset($_SESSION['TOKEN']) || ($order = uc_order_load($_SESSION['cart_order'])) == FALSE) {
  314. unset($_SESSION['cart_order'], $_SESSION['have_details']);
  315. unset($_SESSION['TOKEN'], $_SESSION['PAYERID']);
  316. drupal_set_message(t('An error has occurred in your PayPal payment. Please review your cart and try again.'));
  317. drupal_goto('cart');
  318. }
  319. drupal_add_css(drupal_get_path('module', 'uc_cart') . '/uc_cart.css');
  320. $build['review'] = array(
  321. '#theme' => 'uc_cart_review_table',
  322. '#items' => $order->products,
  323. '#show_subtotal' => FALSE,
  324. );
  325. $build['line_items'] = uc_order_pane_line_items('customer', $order);
  326. $build['instructions'] = array('#markup' => '<p>' . t("Your order is not complete until you click the 'Submit order' button below. Your PayPal account will be charged for the amount shown above once your order is placed. You will receive confirmation once your payment is complete.") . '</p>');
  327. $build['submit_form'] = drupal_get_form('uc_paypal_ec_submit_form');
  328. return $build;
  329. }
  330. /**
  331. * Submits an order, calling the NVP API to send the order total to PayPal.
  332. */
  333. function uc_paypal_ec_submit_form($form, &$form_state) {
  334. $form['actions'] = array('#type' => 'actions');
  335. $form['actions']['submit'] = array(
  336. '#type' => 'submit',
  337. '#value' => t('Submit order'),
  338. );
  339. return $form;
  340. }
  341. /**
  342. * Handles a complete Website Payments Standard sale.
  343. */
  344. function uc_paypal_complete($order) {
  345. // If the order ID specified in the return URL is not the same as the one in
  346. // the user's session, we need to assume this is either a spoof or that the
  347. // user tried to adjust the order on this side while at PayPal. If it was a
  348. // legitimate checkout, the IPN will still come in from PayPal so the order
  349. // gets processed correctly. We'll leave an ambiguous message just in case.
  350. if (!isset($_SESSION['cart_order']) || intval($_SESSION['cart_order']) != $order->order_id) {
  351. drupal_set_message(t('Thank you for your order! PayPal will notify us once your payment has been processed.'));
  352. drupal_goto('cart');
  353. }
  354. // Ensure the payment method is PayPal WPS.
  355. if ($order->payment_method != 'paypal_wps') {
  356. drupal_goto('cart');
  357. }
  358. // This lets us know it's a legitimate access of the complete page.
  359. $_SESSION['uc_checkout'][$_SESSION['cart_order']]['do_complete'] = TRUE;
  360. drupal_goto('cart/checkout/complete');
  361. }
  362. /**
  363. * Handles a canceled Website Payments Standard sale.
  364. */
  365. function uc_paypal_cancel() {
  366. unset($_SESSION['cart_order']);
  367. drupal_set_message(t('Your PayPal payment was canceled. Please feel free to continue shopping or contact us for assistance.'));
  368. drupal_goto(variable_get('uc_paypal_wps_cancel_return_url', 'cart'));
  369. }