/includes/wc-order-functions.php

  1. <?php 
  2. /** 
  3. * WooCommerce Order Functions 
  4. * 
  5. * Functions for order specific things. 
  6. * 
  7. * @author WooThemes 
  8. * @category Core 
  9. * @package WooCommerce/Functions 
  10. */ 
  11. if ( ! defined( 'ABSPATH' ) ) { 
  12. exit; 
  13.  
  14. /** 
  15. * Wrapper for get_posts specific to orders. 
  16. * 
  17. * This function should be used for order retrieval so that when we move to 
  18. * custom tables, functions still work. 
  19. * 
  20. * Args: 
  21. * status array|string List of order statuses to find 
  22. * type array|string Order type, e.g. shop_order or shop_order_refund 
  23. * parent int post/order parent 
  24. * customer int|string|array User ID or billing email to limit orders to a 
  25. * particular user. Accepts array of values. Array of values is OR'ed. If array of array is passed, each array will be AND'ed. 
  26. * e.g. test@test.com, 1, array( 1, 2, 3 ), array( array( 1, 'test@test.com' ), 2, 3 ) 
  27. * limit int Maximum of orders to retrieve. 
  28. * offset int Offset of orders to retrieve. 
  29. * page int Page of orders to retrieve. Ignored when using the 'offset' arg. 
  30. * date_before string Get orders before a certain date ( strtotime() compatibile string ) 
  31. * date_after string Get orders after a certain date ( strtotime() compatibile string ) 
  32. * exclude array Order IDs to exclude from the query. 
  33. * orderby string Order by date, title, id, modified, rand etc 
  34. * order string ASC or DESC 
  35. * return string Type of data to return. Allowed values: 
  36. * ids array of order ids 
  37. * objects array of order objects (default) 
  38. * paginate bool If true, the return value will be an array with values: 
  39. * 'orders' => array of data (return value above),  
  40. * 'total' => total number of orders matching the query 
  41. * 'max_num_pages' => max number of pages found 
  42. * 
  43. * @since 2.6.0 
  44. * @param array $args Array of args (above) 
  45. * @return array|stdClass Number of pages and an array of order objects if 
  46. * paginate is true, or just an array of values. 
  47. */ 
  48. function wc_get_orders( $args ) { 
  49. $args = wp_parse_args( $args, array( 
  50. 'status' => array_keys( wc_get_order_statuses() ),  
  51. 'type' => wc_get_order_types( 'view-orders' ),  
  52. 'parent' => null,  
  53. 'customer' => null,  
  54. 'email' => '',  
  55. 'limit' => get_option( 'posts_per_page' ),  
  56. 'offset' => null,  
  57. 'page' => 1,  
  58. 'exclude' => array(),  
  59. 'orderby' => 'date',  
  60. 'order' => 'DESC',  
  61. 'return' => 'objects',  
  62. 'paginate' => false,  
  63. 'date_before' => '',  
  64. 'date_after' => '',  
  65. ) ); 
  66.  
  67. // Handle some BW compatibility arg names where wp_query args differ in naming. 
  68. $map_legacy = array( 
  69. 'numberposts' => 'limit',  
  70. 'post_type' => 'type',  
  71. 'post_status' => 'status',  
  72. 'post_parent' => 'parent',  
  73. 'author' => 'customer',  
  74. 'posts_per_page' => 'limit',  
  75. 'paged' => 'page',  
  76. ); 
  77.  
  78. foreach ( $map_legacy as $from => $to ) { 
  79. if ( isset( $args[ $from ] ) ) { 
  80. $args[ $to ] = $args[ $from ]; 
  81.  
  82. return WC_Data_Store::load( 'order' )->get_orders( $args ); 
  83.  
  84. /** 
  85. * Main function for returning orders, uses the WC_Order_Factory class. 
  86. * 
  87. * @since 2.2 
  88. * @param mixed $the_order Post object or post ID of the order. 
  89. * @return WC_Order|WC_Refund 
  90. */ 
  91. function wc_get_order( $the_order = false ) { 
  92. if ( ! did_action( 'woocommerce_after_register_post_type' ) ) { 
  93. wc_doing_it_wrong( __FUNCTION__, __( 'wc_get_order should not be called before post types are registered (woocommerce_after_register_post_type action).', 'woocommerce' ), '2.5' ); 
  94. return false; 
  95. return WC()->order_factory->get_order( $the_order ); 
  96.  
  97. /** 
  98. * Get all order statuses. 
  99. * 
  100. * @since 2.2 
  101. * @used-by WC_Order::set_status 
  102. * @return array 
  103. */ 
  104. function wc_get_order_statuses() { 
  105. $order_statuses = array( 
  106. 'wc-pending' => _x( 'Pending payment', 'Order status', 'woocommerce' ),  
  107. 'wc-processing' => _x( 'Processing', 'Order status', 'woocommerce' ),  
  108. 'wc-on-hold' => _x( 'On hold', 'Order status', 'woocommerce' ),  
  109. 'wc-completed' => _x( 'Completed', 'Order status', 'woocommerce' ),  
  110. 'wc-cancelled' => _x( 'Cancelled', 'Order status', 'woocommerce' ),  
  111. 'wc-refunded' => _x( 'Refunded', 'Order status', 'woocommerce' ),  
  112. 'wc-failed' => _x( 'Failed', 'Order status', 'woocommerce' ),  
  113. ); 
  114. return apply_filters( 'wc_order_statuses', $order_statuses ); 
  115.  
  116. /** 
  117. * See if a string is an order status. 
  118. * @param string $maybe_status Status, including any wc- prefix 
  119. * @return bool 
  120. */ 
  121. function wc_is_order_status( $maybe_status ) { 
  122. $order_statuses = wc_get_order_statuses(); 
  123. return isset( $order_statuses[ $maybe_status ] ); 
  124.  
  125. /** 
  126. * Get list of statuses which are consider 'paid'. 
  127. * @since 3.0.0 
  128. * @return array 
  129. */ 
  130. function wc_get_is_paid_statuses() { 
  131. return apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ); 
  132.  
  133. /** 
  134. * Get the nice name for an order status. 
  135. * 
  136. * @since 2.2 
  137. * @param string $status 
  138. * @return string 
  139. */ 
  140. function wc_get_order_status_name( $status ) { 
  141. $statuses = wc_get_order_statuses(); 
  142. $status = 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status; 
  143. $status = isset( $statuses[ 'wc-' . $status ] ) ? $statuses[ 'wc-' . $status ] : $status; 
  144. return $status; 
  145.  
  146. /** 
  147. * Finds an Order ID based on an order key. 
  148. * 
  149. * @param string $order_key An order key has generated by 
  150. * @return int The ID of an order, or 0 if the order could not be found 
  151. */ 
  152. function wc_get_order_id_by_order_key( $order_key ) { 
  153. $data_store = WC_Data_Store::load( 'order' ); 
  154. return $data_store->get_order_id_by_order_key( $order_key ); 
  155.  
  156. /** 
  157. * Get all registered order types. 
  158. * 
  159. * $for optionally define what you are getting order types for so only relevant types are returned. 
  160. * 
  161. * e.g. for 'order-meta-boxes', 'order-count' 
  162. * 
  163. * @since 2.2 
  164. * @param string $for 
  165. * @return array 
  166. */ 
  167. function wc_get_order_types( $for = '' ) { 
  168. global $wc_order_types; 
  169.  
  170. if ( ! is_array( $wc_order_types ) ) { 
  171. $wc_order_types = array(); 
  172.  
  173. $order_types = array(); 
  174.  
  175. switch ( $for ) { 
  176. case 'order-count' : 
  177. foreach ( $wc_order_types as $type => $args ) { 
  178. if ( ! $args['exclude_from_order_count'] ) { 
  179. $order_types[] = $type; 
  180. break; 
  181. case 'order-meta-boxes' : 
  182. foreach ( $wc_order_types as $type => $args ) { 
  183. if ( $args['add_order_meta_boxes'] ) { 
  184. $order_types[] = $type; 
  185. break; 
  186. case 'view-orders' : 
  187. foreach ( $wc_order_types as $type => $args ) { 
  188. if ( ! $args['exclude_from_order_views'] ) { 
  189. $order_types[] = $type; 
  190. break; 
  191. case 'reports' : 
  192. foreach ( $wc_order_types as $type => $args ) { 
  193. if ( ! $args['exclude_from_order_reports'] ) { 
  194. $order_types[] = $type; 
  195. break; 
  196. case 'sales-reports' : 
  197. foreach ( $wc_order_types as $type => $args ) { 
  198. if ( ! $args['exclude_from_order_sales_reports'] ) { 
  199. $order_types[] = $type; 
  200. break; 
  201. case 'order-webhooks' : 
  202. foreach ( $wc_order_types as $type => $args ) { 
  203. if ( ! $args['exclude_from_order_webhooks'] ) { 
  204. $order_types[] = $type; 
  205. break; 
  206. default : 
  207. $order_types = array_keys( $wc_order_types ); 
  208. break; 
  209.  
  210. return apply_filters( 'wc_order_types', $order_types, $for ); 
  211.  
  212. /** 
  213. * Get an order type by post type name. 
  214. * @param string post type name 
  215. * @return bool|array of datails about the order type 
  216. */ 
  217. function wc_get_order_type( $type ) { 
  218. global $wc_order_types; 
  219.  
  220. if ( isset( $wc_order_types[ $type ] ) ) { 
  221. return $wc_order_types[ $type ]; 
  222. } else { 
  223. return false; 
  224.  
  225. /** 
  226. * Register order type. Do not use before init. 
  227. * 
  228. * Wrapper for register post type, as well as a method of telling WC which. 
  229. * post types are types of orders, and having them treated as such. 
  230. * 
  231. * $args are passed to register_post_type, but there are a few specific to this function: 
  232. * - exclude_from_orders_screen (bool) Whether or not this order type also get shown in the main. 
  233. * orders screen. 
  234. * - add_order_meta_boxes (bool) Whether or not the order type gets shop_order meta boxes. 
  235. * - exclude_from_order_count (bool) Whether or not this order type is excluded from counts. 
  236. * - exclude_from_order_views (bool) Whether or not this order type is visible by customers when. 
  237. * viewing orders e.g. on the my account page. 
  238. * - exclude_from_order_reports (bool) Whether or not to exclude this type from core reports. 
  239. * - exclude_from_order_sales_reports (bool) Whether or not to exclude this type from core sales reports. 
  240. * 
  241. * @since 2.2 
  242. * @see register_post_type for $args used in that function 
  243. * @param string $type Post type. (max. 20 characters, can not contain capital letters or spaces) 
  244. * @param array $args An array of arguments. 
  245. * @return bool Success or failure 
  246. */ 
  247. function wc_register_order_type( $type, $args = array() ) { 
  248. if ( post_type_exists( $type ) ) { 
  249. return false; 
  250.  
  251. global $wc_order_types; 
  252.  
  253. if ( ! is_array( $wc_order_types ) ) { 
  254. $wc_order_types = array(); 
  255.  
  256. // Register as a post type 
  257. if ( is_wp_error( register_post_type( $type, $args ) ) ) { 
  258. return false; 
  259.  
  260. // Register for WC usage 
  261. $order_type_args = array( 
  262. 'exclude_from_orders_screen' => false,  
  263. 'add_order_meta_boxes' => true,  
  264. 'exclude_from_order_count' => false,  
  265. 'exclude_from_order_views' => false,  
  266. 'exclude_from_order_webhooks' => false,  
  267. 'exclude_from_order_reports' => false,  
  268. 'exclude_from_order_sales_reports' => false,  
  269. 'class_name' => 'WC_Order',  
  270. ); 
  271.  
  272. $args = array_intersect_key( $args, $order_type_args ); 
  273. $args = wp_parse_args( $args, $order_type_args ); 
  274. $wc_order_types[ $type ] = $args; 
  275.  
  276. return true; 
  277.  
  278. /** 
  279. * Return the count of processing orders. 
  280. * 
  281. * @access public 
  282. * @return int 
  283. */ 
  284. function wc_processing_order_count() { 
  285. return wc_orders_count( 'processing' ); 
  286.  
  287. /** 
  288. * Return the orders count of a specific order status. 
  289. * 
  290. * @param string $status 
  291. * @return int 
  292. */ 
  293. function wc_orders_count( $status ) { 
  294. $count = 0; 
  295. $status = 'wc-' . $status; 
  296. $order_statuses = array_keys( wc_get_order_statuses() ); 
  297.  
  298. if ( ! in_array( $status, $order_statuses ) ) { 
  299. return 0; 
  300.  
  301. $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . $status; 
  302. $cached_count = wp_cache_get( $cache_key, 'counts' ); 
  303.  
  304. if ( false !== $cached_count ) { 
  305. return $cached_count; 
  306.  
  307. foreach ( wc_get_order_types( 'order-count' ) as $type ) { 
  308. $data_store = WC_Data_Store::load( 'shop_order' === $type ? 'order' : $type ); 
  309. if ( $data_store ) { 
  310. $count += $data_store->get_order_count( $status ); 
  311.  
  312. wp_cache_set( $cache_key, $count, 'counts' ); 
  313.  
  314. return $count; 
  315.  
  316. /** 
  317. * Grant downloadable product access to the file identified by $download_id. 
  318. * 
  319. * @param string $download_id file identifier 
  320. * @param int|WC_Product $product 
  321. * @param WC_Order $order the order 
  322. * @param int $qty purchased 
  323. * @return int|bool insert id or false on failure 
  324. */ 
  325. function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1 ) { 
  326. if ( is_numeric( $product ) ) { 
  327. $product = wc_get_product( $product ); 
  328. $download = new WC_Customer_Download(); 
  329. $download->set_download_id( $download_id ); 
  330. $download->set_product_id( $product->get_id() ); 
  331. $download->set_user_id( $order->get_customer_id() ); 
  332. $download->set_order_id( $order->get_id() ); 
  333. $download->set_user_email( $order->get_billing_email() ); 
  334. $download->set_order_key( $order->get_order_key() ); 
  335. $download->set_downloads_remaining( 0 > $product->get_download_limit() ? '' : $product->get_download_limit() * $qty ); 
  336. $download->set_access_granted( current_time( 'timestamp', true ) ); 
  337. $download->set_download_count( 0 ); 
  338.  
  339. $expiry = $product->get_download_expiry(); 
  340.  
  341. if ( $expiry > 0 ) { 
  342. $from_date = $order->get_date_completed() ? $order->get_date_completed()->format( 'Y-m-d' ) : current_time( 'mysql', true ); 
  343. $download->set_access_expires( strtotime( $from_date . ' + ' . $expiry . ' DAY' ) ); 
  344.  
  345. return $download->save(); 
  346.  
  347. /** 
  348. * Order Status completed - GIVE DOWNLOADABLE PRODUCT ACCESS TO CUSTOMER. 
  349. * 
  350. * @param int $order_id 
  351. */ 
  352. function wc_downloadable_product_permissions( $order_id, $force = false ) { 
  353. $order = wc_get_order( $order_id ); 
  354.  
  355. if ( ! $order || ( $order->get_data_store()->get_download_permissions_granted( $order ) && ! $force ) ) { 
  356. return; 
  357.  
  358. if ( $order->has_status( 'processing' ) && 'no' === get_option( 'woocommerce_downloads_grant_access_after_payment' ) ) { 
  359. return; 
  360.  
  361. if ( sizeof( $order->get_items() ) > 0 ) { 
  362. foreach ( $order->get_items() as $item ) { 
  363. $product = $item->get_product(); 
  364.  
  365. if ( $product && $product->exists() && $product->is_downloadable() ) { 
  366. $downloads = $product->get_downloads(); 
  367.  
  368. foreach ( array_keys( $downloads ) as $download_id ) { 
  369. wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity() ); 
  370.  
  371. $order->get_data_store()->set_download_permissions_granted( $order, true ); 
  372. do_action( 'woocommerce_grant_product_download_permissions', $order_id ); 
  373. add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' ); 
  374. add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' ); 
  375.  
  376. /** 
  377. * Clear all transients cache for order data. 
  378. * 
  379. * @param int|WC_Order $order 
  380. */ 
  381. function wc_delete_shop_order_transients( $order = 0 ) { 
  382. if ( is_numeric( $order ) ) { 
  383. $order = wc_get_order( $order ); 
  384. $reports = WC_Admin_Reports::get_reports(); 
  385. $transients_to_clear = array( 
  386. 'wc_admin_report' 
  387. ); 
  388.  
  389. foreach ( $reports as $report_group ) { 
  390. foreach ( $report_group['reports'] as $report_key => $report ) { 
  391. $transients_to_clear[] = 'wc_report_' . $report_key; 
  392.  
  393. foreach ( $transients_to_clear as $transient ) { 
  394. delete_transient( $transient ); 
  395.  
  396. // Clear money spent for user associated with order 
  397. if ( is_a( $order, 'WC_Order' ) ) { 
  398. $order_id = $order->get_id(); 
  399. delete_user_meta( $order->get_customer_id(), '_money_spent' ); 
  400. delete_user_meta( $order->get_customer_id(), '_order_count' ); 
  401. } else { 
  402. $order_id = 0; 
  403.  
  404. // Increments the transient version to invalidate cache 
  405. WC_Cache_Helper::get_transient_version( 'orders', true ); 
  406.  
  407. // Do the same for regular cache 
  408. WC_Cache_Helper::incr_cache_prefix( 'orders' ); 
  409.  
  410. do_action( 'woocommerce_delete_shop_order_transients', $order_id ); 
  411.  
  412. /** 
  413. * See if we only ship to billing addresses. 
  414. * @return bool 
  415. */ 
  416. function wc_ship_to_billing_address_only() { 
  417. return 'billing_only' === get_option( 'woocommerce_ship_to_destination' ); 
  418.  
  419. /** 
  420. * Create a new order refund programmatically. 
  421. * 
  422. * Returns a new refund object on success which can then be used to add additional data. 
  423. * 
  424. * @since 2.2 
  425. * @param array $args 
  426. * @return WC_Order_Refund|WP_Error 
  427. */ 
  428. function wc_create_refund( $args = array() ) { 
  429. $default_args = array( 
  430. 'amount' => 0,  
  431. 'reason' => null,  
  432. 'order_id' => 0,  
  433. 'refund_id' => 0,  
  434. 'line_items' => array(),  
  435. 'refund_payment' => false,  
  436. 'restock_items' => false,  
  437. ); 
  438.  
  439. try { 
  440. $args = wp_parse_args( $args, $default_args ); 
  441.  
  442. if ( ! $order = wc_get_order( $args['order_id'] ) ) { 
  443. throw new Exception( __( 'Invalid order ID.', 'woocommerce' ) ); 
  444.  
  445. $remaining_refund_amount = $order->get_remaining_refund_amount(); 
  446. $remaining_refund_items = $order->get_remaining_refund_items(); 
  447. $refund_item_count = 0; 
  448. $refund = new WC_Order_Refund( $args['refund_id'] ); 
  449.  
  450. if ( 0 > $args['amount'] || $args['amount'] > $remaining_refund_amount ) { 
  451. throw new Exception( __( 'Invalid refund amount.', 'woocommerce' ) ); 
  452.  
  453. $refund->set_currency( $order->get_currency() ); 
  454. $refund->set_amount( $args['amount'] ); 
  455. $refund->set_parent_id( absint( $args['order_id'] ) ); 
  456. $refund->set_refunded_by( get_current_user_id() ? get_current_user_id() : 1 ); 
  457.  
  458. if ( ! is_null( $args['reason'] ) ) { 
  459. $refund->set_reason( $args['reason'] ); 
  460.  
  461. // Negative line items 
  462. if ( sizeof( $args['line_items'] ) > 0 ) { 
  463. $items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) ); 
  464.  
  465. foreach ( $items as $item_id => $item ) { 
  466. if ( ! isset( $args['line_items'][ $item_id ] ) ) { 
  467. continue; 
  468.  
  469. $qty = isset( $args['line_items'][ $item_id ]['qty'] ) ? $args['line_items'][ $item_id ]['qty'] : 0; 
  470. $refund_total = $args['line_items'][ $item_id ]['refund_total']; 
  471. $refund_tax = isset( $args['line_items'][ $item_id ]['refund_tax'] ) ? array_filter( (array) $args['line_items'][ $item_id ]['refund_tax'] ) : array(); 
  472.  
  473. if ( empty( $qty ) && empty( $refund_total ) && empty( $args['line_items'][ $item_id ]['refund_tax'] ) ) { 
  474. continue; 
  475.  
  476. $class = get_class( $item ); 
  477. $refunded_item = new $class( $item ); 
  478. $refunded_item->set_id( 0 ); 
  479. $refunded_item->add_meta_data( '_refunded_item_id', $item_id, true ); 
  480. $refunded_item->set_total( wc_format_refund_total( $refund_total ) ); 
  481. $refunded_item->set_taxes( array( 'total' => array_map( 'wc_format_refund_total', $refund_tax ), 'subtotal' => array_map( 'wc_format_refund_total', $refund_tax ) ) ); 
  482.  
  483. if ( is_callable( array( $refunded_item, 'set_subtotal' ) ) ) { 
  484. $refunded_item->set_subtotal( wc_format_refund_total( $refund_total ) ); 
  485.  
  486. if ( is_callable( array( $refunded_item, 'set_quantity' ) ) ) { 
  487. $refunded_item->set_quantity( $qty * -1 ); 
  488.  
  489. $refund->add_item( $refunded_item ); 
  490. $refund_item_count += $qty; 
  491.  
  492. $refund->update_taxes(); 
  493. $refund->calculate_totals( false ); 
  494. $refund->set_total( $args['amount'] * -1 ); 
  495.  
  496. /** 
  497. * Action hook to adjust refund before save. 
  498. * @since 3.0.0 
  499. */ 
  500. do_action( 'woocommerce_create_refund', $refund, $args ); 
  501.  
  502. if ( $refund->save() ) { 
  503. if ( $args['refund_payment'] ) { 
  504. $result = wc_refund_payment( $order, $refund->get_amount(), $refund->get_reason() ); 
  505.  
  506. if ( is_wp_error( $result ) ) { 
  507. $refund->delete(); 
  508. return $result; 
  509.  
  510. if ( $args['restock_items'] ) { 
  511. wc_restock_refunded_items( $order, $args['line_items'] ); 
  512.  
  513. // Trigger notification emails 
  514. if ( ( $remaining_refund_amount - $args['amount'] ) > 0 || ( $order->has_free_item() && ( $remaining_refund_items - $refund_item_count ) > 0 ) ) { 
  515. do_action( 'woocommerce_order_partially_refunded', $order->get_id(), $refund->get_id() ); 
  516. } else { 
  517. do_action( 'woocommerce_order_fully_refunded', $order->get_id(), $refund->get_id() ); 
  518.  
  519. $parent_status = apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order->get_id(), $refund->get_id() ); 
  520.  
  521. if ( $parent_status ) { 
  522. $order->update_status( $parent_status ); 
  523.  
  524. do_action( 'woocommerce_refund_created', $refund->get_id(), $args ); 
  525. do_action( 'woocommerce_order_refunded', $order->get_id(), $refund->get_id() ); 
  526.  
  527. } catch ( Exception $e ) { 
  528. return new WP_Error( 'error', $e->getMessage() ); 
  529.  
  530. return $refund; 
  531.  
  532. /** 
  533. * Try to refund the payment for an order via the gateway. 
  534. * 
  535. * @since 3.0.0 
  536. * @param WC_Order $order 
  537. * @param string $amount 
  538. * @param string $reason 
  539. * @return bool|WP_Error 
  540. */ 
  541. function wc_refund_payment( $order, $amount, $reason = '' ) { 
  542. try { 
  543. if ( ! is_a( $order, 'WC_Order' ) ) { 
  544. throw new Exception( __( 'Invalid order.', 'woocommerce' ) ); 
  545.  
  546. $gateway_controller = WC_Payment_Gateways::instance(); 
  547. $all_gateways = $gateway_controller->payment_gateways(); 
  548. $payment_method = $order->get_payment_method(); 
  549. $gateway = isset( $all_gateways[ $payment_method ] ) ? $all_gateways[ $payment_method ] : false; 
  550.  
  551. if ( ! $gateway ) { 
  552. throw new Exception( __( 'The payment gateway for this order does not exist.', 'woocommerce' ) ); 
  553.  
  554. if ( ! $gateway->supports( 'refunds' ) ) { 
  555. throw new Exception( __( 'The payment gateway for this order does not support automatic refunds.', 'woocommerce' ) ); 
  556.  
  557. $result = $gateway->process_refund( $order->get_id(), $amount, $reason ); 
  558.  
  559. if ( ! $result ) { 
  560. throw new Exception( __( 'An error occurred while attempting to create the refund using the payment gateway API.', 'woocommerce' ) ); 
  561.  
  562. if ( is_wp_error( $result ) ) { 
  563. throw new Exception( $result->get_error_message() ); 
  564.  
  565. return true; 
  566.  
  567. } catch ( Exception $e ) { 
  568. return new WP_Error( 'error', $e->getMessage() ); 
  569.  
  570. /** 
  571. * Restock items during refund. 
  572. * 
  573. * @since 3.0.0 
  574. * @param WC_Order $order 
  575. * @param array $refunded_line_items 
  576. */ 
  577. function wc_restock_refunded_items( $order, $refunded_line_items ) { 
  578. $line_items = $order->get_items(); 
  579.  
  580. foreach ( $line_items as $item_id => $item ) { 
  581. if ( ! isset( $refunded_line_items[ $item_id ], $refunded_line_items[ $item_id ]['qty'] ) ) { 
  582. continue; 
  583. $product = $item->get_product(); 
  584.  
  585. if ( $product && $product->managing_stock() ) { 
  586. $old_stock = $product->get_stock_quantity(); 
  587. $new_stock = wc_update_product_stock( $product, $refunded_line_items[ $item_id ]['qty'], 'increase' ); 
  588.  
  589. $order->add_order_note( sprintf( __( 'Item #%1$s stock increased from %2$s to %3$s.', 'woocommerce' ), $product->get_id(), $old_stock, $new_stock ) ); 
  590.  
  591. do_action( 'woocommerce_restock_refunded_item', $product->get_id(), $old_stock, $new_stock, $order, $product ); 
  592.  
  593. /** 
  594. * Get tax class by tax id. 
  595. * 
  596. * @since 2.2 
  597. * @param int $tax_id 
  598. * @return string 
  599. */ 
  600. function wc_get_tax_class_by_tax_id( $tax_id ) { 
  601. global $wpdb; 
  602. return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) ); 
  603.  
  604. /** 
  605. * Get payment gateway class by order data. 
  606. * 
  607. * @since 2.2 
  608. * @param int|WC_Order $order 
  609. * @return WC_Payment_Gateway|bool 
  610. */ 
  611. function wc_get_payment_gateway_by_order( $order ) { 
  612. if ( WC()->payment_gateways() ) { 
  613. $payment_gateways = WC()->payment_gateways->payment_gateways(); 
  614. } else { 
  615. $payment_gateways = array(); 
  616.  
  617. if ( ! is_object( $order ) ) { 
  618. $order_id = absint( $order ); 
  619. $order = wc_get_order( $order_id ); 
  620.  
  621. return isset( $payment_gateways[ $order->get_payment_method() ] ) ? $payment_gateways[ $order->get_payment_method() ] : false; 
  622.  
  623. /** 
  624. * When refunding an order, create a refund line item if the partial refunds do not match order total. 
  625. * 
  626. * This is manual; no gateway refund will be performed. 
  627. * 
  628. * @since 2.4 
  629. * @param int $order_id 
  630. */ 
  631. function wc_order_fully_refunded( $order_id ) { 
  632. $order = wc_get_order( $order_id ); 
  633. $max_refund = wc_format_decimal( $order->get_total() - $order->get_total_refunded() ); 
  634.  
  635. if ( ! $max_refund ) { 
  636. return; 
  637.  
  638. // Create the refund object 
  639. wc_create_refund( array( 
  640. 'amount' => $max_refund,  
  641. 'reason' => __( 'Order fully refunded', 'woocommerce' ),  
  642. 'order_id' => $order_id,  
  643. 'line_items' => array(),  
  644. ) ); 
  645. add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' ); 
  646.  
  647. /** 
  648. * Search orders. 
  649. * 
  650. * @since 2.6.0 
  651. * @param string $term Term to search. 
  652. * @return array List of orders ID. 
  653. */ 
  654. function wc_order_search( $term ) { 
  655. $data_store = WC_Data_Store::load( 'order' ); 
  656. return $data_store->search_orders( str_replace( 'Order #', '', wc_clean( $term ) ) ); 
  657.  
  658. /** 
  659. * Update total sales amount for each product within a paid order. 
  660. * 
  661. * @since 3.0.0 
  662. * @param int $order_id 
  663. */ 
  664. function wc_update_total_sales_counts( $order_id ) { 
  665. $order = wc_get_order( $order_id ); 
  666.  
  667. if ( ! $order || $order->get_data_store()->get_recorded_sales( $order ) ) { 
  668. return; 
  669.  
  670. if ( sizeof( $order->get_items() ) > 0 ) { 
  671. foreach ( $order->get_items() as $item ) { 
  672. if ( $product_id = $item->get_product_id() ) { 
  673. $data_store = WC_Data_Store::load( 'product' ); 
  674. $data_store->update_product_sales( $product_id, absint( $item['qty'] ), 'increase' ); 
  675.  
  676. $order->get_data_store()->set_recorded_sales( $order, true ); 
  677.  
  678. /** 
  679. * Called when sales for an order are recorded 
  680. * 
  681. * @param int $order_id order id 
  682. */ 
  683. do_action( 'woocommerce_recorded_sales', $order_id ); 
  684. add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' ); 
  685. add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' ); 
  686. add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' ); 
  687.  
  688. /** 
  689. * Update used coupon amount for each coupon within an order. 
  690. * 
  691. * @since 3.0.0 
  692. * @param int $order_id 
  693. */ 
  694. function wc_update_coupon_usage_counts( $order_id ) { 
  695. if ( ! $order = wc_get_order( $order_id ) ) { 
  696. return; 
  697.  
  698. $has_recorded = $order->get_data_store()->get_recorded_coupon_usage_counts( $order ); 
  699.  
  700. if ( $order->has_status( 'cancelled' ) && $has_recorded ) { 
  701. $action = 'reduce'; 
  702. $order->get_data_store()->set_recorded_coupon_usage_counts( $order, false ); 
  703. } elseif ( ! $order->has_status( 'cancelled' ) && ! $has_recorded ) { 
  704. $action = 'increase'; 
  705. $order->get_data_store()->set_recorded_coupon_usage_counts( $order, true ); 
  706. } else { 
  707. return; 
  708.  
  709. if ( sizeof( $order->get_used_coupons() ) > 0 ) { 
  710. foreach ( $order->get_used_coupons() as $code ) { 
  711. if ( ! $code ) { 
  712. continue; 
  713.  
  714. $coupon = new WC_Coupon( $code ); 
  715.  
  716. if ( ! $used_by = $order->get_user_id() ) { 
  717. $used_by = $order->get_billing_email(); 
  718.  
  719. switch ( $action ) { 
  720. case 'reduce' : 
  721. $coupon->decrease_usage_count( $used_by ); 
  722. break; 
  723. case 'increase' : 
  724. $coupon->increase_usage_count( $used_by ); 
  725. break; 
  726. add_action( 'woocommerce_order_status_pending', 'wc_update_coupon_usage_counts' ); 
  727. add_action( 'woocommerce_order_status_completed', 'wc_update_coupon_usage_counts' ); 
  728. add_action( 'woocommerce_order_status_processing', 'wc_update_coupon_usage_counts' ); 
  729. add_action( 'woocommerce_order_status_on-hold', 'wc_update_coupon_usage_counts' ); 
  730. add_action( 'woocommerce_order_status_cancelled', 'wc_update_coupon_usage_counts' ); 
  731.  
  732. /** 
  733. * Cancel all unpaid orders after held duration to prevent stock lock for those products. 
  734. */ 
  735. function wc_cancel_unpaid_orders() { 
  736. $held_duration = get_option( 'woocommerce_hold_stock_minutes' ); 
  737.  
  738. if ( $held_duration < 1 || 'yes' !== get_option( 'woocommerce_manage_stock' ) ) { 
  739. return; 
  740.  
  741. $data_store = WC_Data_Store::load( 'order' ); 
  742. $unpaid_orders = $data_store->get_unpaid_orders( strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) ); 
  743.  
  744. if ( $unpaid_orders ) { 
  745. foreach ( $unpaid_orders as $unpaid_order ) { 
  746. $order = wc_get_order( $unpaid_order ); 
  747.  
  748. if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === $order->get_created_via(), $order ) ) { 
  749. $order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) ); 
  750. wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' ); 
  751. wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' ); 
  752. add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' ); 
.