/includes/gateways/paypal/includes/class-wc-gateway-paypal-request.php

  1. <?php 
  2.  
  3. if ( ! defined( 'ABSPATH' ) ) { 
  4. exit; 
  5.  
  6. /** 
  7. * Generates requests to send to PayPal. 
  8. */ 
  9. class WC_Gateway_Paypal_Request { 
  10.  
  11. /** 
  12. * Stores line items to send to PayPal. 
  13. * @var array 
  14. */ 
  15. protected $line_items = array(); 
  16.  
  17. /** 
  18. * Pointer to gateway making the request. 
  19. * @var WC_Gateway_Paypal 
  20. */ 
  21. protected $gateway; 
  22.  
  23. /** 
  24. * Endpoint for requests from PayPal. 
  25. * @var string 
  26. */ 
  27. protected $notify_url; 
  28.  
  29. /** 
  30. * Constructor. 
  31. * @param WC_Gateway_Paypal $gateway 
  32. */ 
  33. public function __construct( $gateway ) { 
  34. $this->gateway = $gateway; 
  35. $this->notify_url = WC()->api_request_url( 'WC_Gateway_Paypal' ); 
  36.  
  37. /** 
  38. * Get the PayPal request URL for an order. 
  39. * @param WC_Order $order 
  40. * @param bool $sandbox 
  41. * @return string 
  42. */ 
  43. public function get_request_url( $order, $sandbox = false ) { 
  44. $paypal_args = http_build_query( $this->get_paypal_args( $order ), '', '&' ); 
  45.  
  46. WC_Gateway_Paypal::log( 'PayPal Request Args for order ' . $order->get_order_number() . ': ' . wc_print_r( $paypal_args, true ) ); 
  47.  
  48. if ( $sandbox ) { 
  49. return 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' . $paypal_args; 
  50. } else { 
  51. return 'https://www.paypal.com/cgi-bin/webscr?' . $paypal_args; 
  52.  
  53. /** 
  54. * Limit length of an arg. 
  55. * 
  56. * @param string $string 
  57. * @param integer $limit 
  58. * @return string 
  59. */ 
  60. protected function limit_length( $string, $limit = 127 ) { 
  61. if ( strlen( $string ) > $limit ) { 
  62. $string = substr( $string, 0, $limit - 3 ) . '...'; 
  63. return $string; 
  64.  
  65. /** 
  66. * Get PayPal Args for passing to PP. 
  67. * @param WC_Order $order 
  68. * @return array 
  69. */ 
  70. protected function get_paypal_args( $order ) { 
  71. WC_Gateway_Paypal::log( 'Generating payment form for order ' . $order->get_order_number() . '. Notify URL: ' . $this->notify_url ); 
  72.  
  73. return apply_filters( 'woocommerce_paypal_args', array_merge( 
  74. array( 
  75. 'cmd' => '_cart',  
  76. 'business' => $this->gateway->get_option( 'email' ),  
  77. 'no_note' => 1,  
  78. 'currency_code' => get_woocommerce_currency(),  
  79. 'charset' => 'utf-8',  
  80. 'rm' => is_ssl() ? 2 : 1,  
  81. 'upload' => 1,  
  82. 'return' => esc_url_raw( add_query_arg( 'utm_nooverride', '1', $this->gateway->get_return_url( $order ) ) ),  
  83. 'cancel_return' => esc_url_raw( $order->get_cancel_order_url_raw() ),  
  84. 'page_style' => $this->gateway->get_option( 'page_style' ),  
  85. 'image_url' => esc_url_raw( $this->gateway->get_option( 'image_url' ) ),  
  86. 'paymentaction' => $this->gateway->get_option( 'paymentaction' ),  
  87. 'bn' => 'WooThemes_Cart',  
  88. 'invoice' => $this->limit_length( $this->gateway->get_option( 'invoice_prefix' ) . $order->get_order_number(), 127 ),  
  89. 'custom' => json_encode( array( 'order_id' => $order->get_id(), 'order_key' => $order->get_order_key() ) ),  
  90. 'notify_url' => $this->limit_length( $this->notify_url, 255 ),  
  91. 'first_name' => $this->limit_length( $order->get_billing_first_name(), 32 ),  
  92. 'last_name' => $this->limit_length( $order->get_billing_last_name(), 64 ),  
  93. 'address1' => $this->limit_length( $order->get_billing_address_1(), 100 ),  
  94. 'address2' => $this->limit_length( $order->get_billing_address_2(), 100 ),  
  95. 'city' => $this->limit_length( $order->get_billing_city(), 40 ),  
  96. 'state' => $this->get_paypal_state( $order->get_billing_country(), $order->get_billing_state() ),  
  97. 'zip' => $this->limit_length( wc_format_postcode( $order->get_billing_postcode(), $order->get_billing_country() ), 32 ),  
  98. 'country' => $this->limit_length( $order->get_billing_country(), 2 ),  
  99. 'email' => $this->limit_length( $order->get_billing_email() ),  
  100. ),  
  101. $this->get_phone_number_args( $order ),  
  102. $this->get_shipping_args( $order ),  
  103. $this->get_line_item_args( $order ) 
  104. ), $order ); 
  105.  
  106. /** 
  107. * Get phone number args for paypal request. 
  108. * @param WC_Order $order 
  109. * @return array 
  110. */ 
  111. protected function get_phone_number_args( $order ) { 
  112. if ( in_array( $order->get_billing_country(), array( 'US', 'CA' ) ) ) { 
  113. $phone_number = str_replace( array( '(', '-', ' ', ')', '.' ), '', $order->get_billing_phone() ); 
  114. $phone_number = ltrim( $phone_number, '+1' ); 
  115. $phone_args = array( 
  116. 'night_phone_a' => substr( $phone_number, 0, 3 ),  
  117. 'night_phone_b' => substr( $phone_number, 3, 3 ),  
  118. 'night_phone_c' => substr( $phone_number, 6, 4 ),  
  119. ); 
  120. } else { 
  121. $phone_args = array( 
  122. 'night_phone_b' => $order->get_billing_phone(),  
  123. ); 
  124. return $phone_args; 
  125.  
  126. /** 
  127. * Get shipping args for paypal request. 
  128. * @param WC_Order $order 
  129. * @return array 
  130. */ 
  131. protected function get_shipping_args( $order ) { 
  132. $shipping_args = array(); 
  133.  
  134. if ( 'yes' == $this->gateway->get_option( 'send_shipping' ) ) { 
  135. $shipping_args['address_override'] = $this->gateway->get_option( 'address_override' ) === 'yes' ? 1 : 0; 
  136. $shipping_args['no_shipping'] = 0; 
  137.  
  138. // If we are sending shipping, send shipping address instead of billing 
  139. $shipping_args['first_name'] = $this->limit_length( $order->get_shipping_first_name(), 32 ); 
  140. $shipping_args['last_name'] = $this->limit_length( $order->get_shipping_last_name(), 64 ); 
  141. $shipping_args['address1'] = $this->limit_length( $order->get_shipping_address_1(), 100 ); 
  142. $shipping_args['address2'] = $this->limit_length( $order->get_shipping_address_2(), 100 ); 
  143. $shipping_args['city'] = $this->limit_length( $order->get_shipping_city(), 40 ); 
  144. $shipping_args['state'] = $this->get_paypal_state( $order->get_shipping_country(), $order->get_shipping_state() ); 
  145. $shipping_args['country'] = $this->limit_length( $order->get_shipping_country(), 2 ); 
  146. $shipping_args['zip'] = $this->limit_length( wc_format_postcode( $order->get_shipping_postcode(), $order->get_shipping_country() ), 32 ); 
  147. } else { 
  148. $shipping_args['no_shipping'] = 1; 
  149.  
  150. return $shipping_args; 
  151.  
  152. /** 
  153. * Get line item args for paypal request. 
  154. * @param WC_Order $order 
  155. * @return array 
  156. */ 
  157. protected function get_line_item_args( $order ) { 
  158.  
  159. /** 
  160. * Try passing a line item per product if supported. 
  161. */ 
  162. if ( ( ! wc_tax_enabled() || ! wc_prices_include_tax() ) && $this->prepare_line_items( $order ) ) { 
  163.  
  164. $line_item_args = array(); 
  165. $line_item_args['tax_cart'] = $this->number_format( $order->get_total_tax(), $order ); 
  166.  
  167. if ( $order->get_total_discount() > 0 ) { 
  168. $line_item_args['discount_amount_cart'] = $this->number_format( $this->round( $order->get_total_discount(), $order ), $order ); 
  169.  
  170. // Add shipping costs. Paypal ignores anything over 5 digits (999.99 is the max). 
  171. // We also check that shipping is not the **only** cost as PayPal won't allow payment 
  172. // if the items have no cost. 
  173. if ( $order->get_shipping_total() > 0 && $order->get_shipping_total() < 999.99 && $this->number_format( $order->get_shipping_total() + $order->get_shipping_tax(), $order ) !== $this->number_format( $order->get_total(), $order ) ) { 
  174. $line_item_args['shipping_1'] = $this->number_format( $order->get_shipping_total(), $order ); 
  175. } elseif ( $order->get_shipping_total() > 0 ) { 
  176. $this->add_line_item( sprintf( __( 'Shipping via %s', 'woocommerce' ), $order->get_shipping_method() ), 1, $this->number_format( $order->get_shipping_total(), $order ) ); 
  177.  
  178. $line_item_args = array_merge( $line_item_args, $this->get_line_items() ); 
  179.  
  180. /** 
  181. * Send order as a single item. 
  182. * 
  183. * For shipping, we longer use shipping_1 because paypal ignores it if *any* shipping rules are within paypal, and paypal ignores anything over 5 digits (999.99 is the max). 
  184. */ 
  185. } else { 
  186.  
  187. $this->delete_line_items(); 
  188.  
  189. $line_item_args = array(); 
  190. $all_items_name = $this->get_order_item_names( $order ); 
  191. $this->add_line_item( $all_items_name ? $all_items_name : __( 'Order', 'woocommerce' ), 1, $this->number_format( $order->get_total() - $this->round( $order->get_shipping_total() + $order->get_shipping_tax(), $order ), $order ), $order->get_order_number() ); 
  192.  
  193. // Add shipping costs. Paypal ignores anything over 5 digits (999.99 is the max). 
  194. // We also check that shipping is not the **only** cost as PayPal won't allow payment 
  195. // if the items have no cost. 
  196. if ( $order->get_shipping_total() > 0 && $order->get_shipping_total() < 999.99 && $this->number_format( $order->get_shipping_total() + $order->get_shipping_tax(), $order ) !== $this->number_format( $order->get_total(), $order ) ) { 
  197. $line_item_args['shipping_1'] = $this->number_format( $order->get_shipping_total() + $order->get_shipping_tax(), $order ); 
  198. } elseif ( $order->get_shipping_total() > 0 ) { 
  199. $this->add_line_item( sprintf( __( 'Shipping via %s', 'woocommerce' ), $order->get_shipping_method() ), 1, $this->number_format( $order->get_shipping_total() + $order->get_shipping_tax(), $order ) ); 
  200.  
  201. $line_item_args = array_merge( $line_item_args, $this->get_line_items() ); 
  202.  
  203. return $line_item_args; 
  204.  
  205. /** 
  206. * Get order item names as a string. 
  207. * @param WC_Order $order 
  208. * @return string 
  209. */ 
  210. protected function get_order_item_names( $order ) { 
  211. $item_names = array(); 
  212.  
  213. foreach ( $order->get_items() as $item ) { 
  214. $item_name = $item->get_name(); 
  215. $item_meta = strip_tags( wc_display_item_meta( $item, array( 
  216. 'before' => "",  
  217. 'separator' => ", ",  
  218. 'after' => "",  
  219. 'echo' => false,  
  220. 'autop' => false,  
  221. ) ) ); 
  222.  
  223. if ( $item_meta ) { 
  224. $item_name .= ' (' . $item_meta . ')'; 
  225.  
  226. $item_names[] = $item_name . ' x ' . $item->get_quantity(); 
  227.  
  228. return apply_filters( 'woocommerce_paypal_get_order_item_names', implode( ', ', $item_names ), $order ); 
  229.  
  230. /** 
  231. * Get order item names as a string. 
  232. * @param WC_Order $order 
  233. * @param array $item 
  234. * @return string 
  235. */ 
  236. protected function get_order_item_name( $order, $item ) { 
  237. $item_name = $item->get_name(); 
  238. $item_meta = strip_tags( wc_display_item_meta( $item, array( 
  239. 'before' => "",  
  240. 'separator' => ", ",  
  241. 'after' => "",  
  242. 'echo' => false,  
  243. 'autop' => false,  
  244. ) ) ); 
  245.  
  246. if ( $item_meta ) { 
  247. $item_name .= ' (' . $item_meta . ')'; 
  248.  
  249. return apply_filters( 'woocommerce_paypal_get_order_item_name', $item_name, $order, $item ); 
  250.  
  251. /** 
  252. * Return all line items. 
  253. */ 
  254. protected function get_line_items() { 
  255. return $this->line_items; 
  256.  
  257. /** 
  258. * Remove all line items. 
  259. */ 
  260. protected function delete_line_items() { 
  261. $this->line_items = array(); 
  262.  
  263. /** 
  264. * Get line items to send to paypal. 
  265. * @param WC_Order $order 
  266. * @return bool 
  267. */ 
  268. protected function prepare_line_items( $order ) { 
  269. $this->delete_line_items(); 
  270. $calculated_total = 0; 
  271.  
  272. // Products 
  273. foreach ( $order->get_items( array( 'line_item', 'fee' ) ) as $item ) { 
  274. if ( 'fee' === $item['type'] ) { 
  275. $item_line_total = $this->number_format( $item['line_total'], $order ); 
  276. $line_item = $this->add_line_item( $item->get_name(), 1, $item_line_total ); 
  277. $calculated_total += $item_line_total; 
  278. } else { 
  279. $product = $item->get_product(); 
  280. $sku = $product ? $product->get_sku() : ''; 
  281. $item_line_total = $this->number_format( $order->get_item_subtotal( $item, false ), $order ); 
  282. $line_item = $this->add_line_item( $this->get_order_item_name( $order, $item ), $item->get_quantity(), $item_line_total, $sku ); 
  283. $calculated_total += $item_line_total * $item->get_quantity(); 
  284.  
  285. if ( ! $line_item ) { 
  286. return false; 
  287.  
  288. // Check for mismatched totals. 
  289. if ( $this->number_format( $calculated_total + $order->get_total_tax() + $this->round( $order->get_shipping_total(), $order ) - $this->round( $order->get_total_discount(), $order ), $order ) != $this->number_format( $order->get_total(), $order ) ) { 
  290. return false; 
  291.  
  292. return true; 
  293.  
  294. /** 
  295. * Add PayPal Line Item. 
  296. * @param string $item_name 
  297. * @param int $quantity 
  298. * @param float $amount 
  299. * @param string $item_number 
  300. * @return bool successfully added or not 
  301. */ 
  302. protected function add_line_item( $item_name, $quantity = 1, $amount = 0, $item_number = '' ) { 
  303. $index = ( sizeof( $this->line_items ) / 4 ) + 1; 
  304.  
  305. if ( $amount < 0 || $index > 9 ) { 
  306. return false; 
  307.  
  308. $item = apply_filters( 'woocommerce_paypal_line_item', array( 
  309. 'item_name' => html_entity_decode( wc_trim_string( $item_name ? $item_name : __( 'Item', 'woocommerce' ), 127 ), ENT_NOQUOTES, 'UTF-8' ),  
  310. 'quantity' => (int) $quantity,  
  311. 'amount' => (float) $amount,  
  312. 'item_number' => $item_number,  
  313. ), $item_name, $quantity, $amount, $item_number ); 
  314.  
  315. $this->line_items[ 'item_name_' . $index ] = $this->limit_length( $item['item_name'], 127 ); 
  316. $this->line_items[ 'quantity_' . $index ] = $item['quantity']; 
  317. $this->line_items[ 'amount_' . $index ] = $item['amount']; 
  318. $this->line_items[ 'item_number_' . $index ] = $this->limit_length( $item['item_number'], 127 ); 
  319.  
  320. return true; 
  321.  
  322. /** 
  323. * Get the state to send to paypal. 
  324. * @param string $cc 
  325. * @param string $state 
  326. * @return string 
  327. */ 
  328. protected function get_paypal_state( $cc, $state ) { 
  329. if ( 'US' === $cc ) { 
  330. return $state; 
  331.  
  332. $states = WC()->countries->get_states( $cc ); 
  333.  
  334. if ( isset( $states[ $state ] ) ) { 
  335. return $states[ $state ]; 
  336.  
  337. return $state; 
  338.  
  339. /** 
  340. * Check if currency has decimals. 
  341. * @param string $currency 
  342. * @return bool 
  343. */ 
  344. protected function currency_has_decimals( $currency ) { 
  345. if ( in_array( $currency, array( 'HUF', 'JPY', 'TWD' ) ) ) { 
  346. return false; 
  347.  
  348. return true; 
  349.  
  350. /** 
  351. * Round prices. 
  352. * @param double $price 
  353. * @param WC_Order $order 
  354. * @return double 
  355. */ 
  356. protected function round( $price, $order ) { 
  357. $precision = 2; 
  358.  
  359. if ( ! $this->currency_has_decimals( $order->get_currency() ) ) { 
  360. $precision = 0; 
  361.  
  362. return round( $price, $precision ); 
  363.  
  364. /** 
  365. * Format prices. 
  366. * @param float|int $price 
  367. * @param WC_Order $order 
  368. * @return string 
  369. */ 
  370. protected function number_format( $price, $order ) { 
  371. $decimals = 2; 
  372.  
  373. if ( ! $this->currency_has_decimals( $order->get_currency() ) ) { 
  374. $decimals = 0; 
  375.  
  376. return number_format( $price, $decimals, '.', '' ); 
.