WC_API_Orders

The WooCommerce WC API Orders class.

Defined (3)

The class is defined in the following location(s).

/includes/api/legacy/v1/class-wc-api-orders.php  
  1. class WC_API_Orders extends WC_API_Resource { 
  2.  
  3. /** @var string $base the route base */ 
  4. protected $base = '/orders'; 
  5.  
  6. /** 
  7. * Register the routes for this class 
  8. * GET /orders 
  9. * GET /orders/count 
  10. * GET|PUT /orders/<id> 
  11. * GET /orders/<id>/notes 
  12. * @since 2.1 
  13. * @param array $routes 
  14. * @return array 
  15. */ 
  16. public function register_routes( $routes ) { 
  17.  
  18. # GET /orders 
  19. $routes[ $this->base ] = array( 
  20. array( array( $this, 'get_orders' ), WC_API_Server::READABLE ),  
  21. ); 
  22.  
  23. # GET /orders/count 
  24. $routes[ $this->base . '/count' ] = array( 
  25. array( array( $this, 'get_orders_count' ), WC_API_Server::READABLE ),  
  26. ); 
  27.  
  28. # GET|PUT /orders/<id> 
  29. $routes[ $this->base . '/(?P<id>\d+)' ] = array( 
  30. array( array( $this, 'get_order' ), WC_API_Server::READABLE ),  
  31. array( array( $this, 'edit_order' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  32. ); 
  33.  
  34. # GET /orders/<id>/notes 
  35. $routes[ $this->base . '/(?P<id>\d+)/notes' ] = array( 
  36. array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ),  
  37. ); 
  38.  
  39. return $routes; 
  40.  
  41. /** 
  42. * Get all orders 
  43. * @since 2.1 
  44. * @param string $fields 
  45. * @param array $filter 
  46. * @param string $status 
  47. * @param int $page 
  48. * @return array 
  49. */ 
  50. public function get_orders( $fields = null, $filter = array(), $status = null, $page = 1 ) { 
  51.  
  52. if ( ! empty( $status ) ) 
  53. $filter['status'] = $status; 
  54.  
  55. $filter['page'] = $page; 
  56.  
  57. $query = $this->query_orders( $filter ); 
  58.  
  59. $orders = array(); 
  60.  
  61. foreach ( $query->posts as $order_id ) { 
  62.  
  63. if ( ! $this->is_readable( $order_id ) ) 
  64. continue; 
  65.  
  66. $orders[] = current( $this->get_order( $order_id, $fields ) ); 
  67.  
  68. $this->server->add_pagination_headers( $query ); 
  69.  
  70. return array( 'orders' => $orders ); 
  71.  
  72.  
  73. /** 
  74. * Get the order for the given ID 
  75. * @since 2.1 
  76. * @param int $id the order ID 
  77. * @param array $fields 
  78. * @return array 
  79. */ 
  80. public function get_order( $id, $fields = null ) { 
  81.  
  82. // ensure order ID is valid & user has permission to read 
  83. $id = $this->validate_request( $id, 'shop_order', 'read' ); 
  84.  
  85. if ( is_wp_error( $id ) ) { 
  86. return $id; 
  87.  
  88. $order = wc_get_order( $id ); 
  89. $order_data = array( 
  90. 'id' => $order->get_id(),  
  91. 'order_number' => $order->get_order_number(),  
  92. 'created_at' => $this->server->format_datetime( $order->get_date_created() ? $order->get_date_created()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  93. 'updated_at' => $this->server->format_datetime( $order->get_date_modified() ? $order->get_date_modified()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  94. 'completed_at' => $this->server->format_datetime( $order->get_date_completed() ? $order->get_date_completed()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  95. 'status' => $order->get_status(),  
  96. 'currency' => $order->get_currency(),  
  97. 'total' => wc_format_decimal( $order->get_total(), 2 ),  
  98. 'subtotal' => wc_format_decimal( $this->get_order_subtotal( $order ), 2 ),  
  99. 'total_line_items_quantity' => $order->get_item_count(),  
  100. 'total_tax' => wc_format_decimal( $order->get_total_tax(), 2 ),  
  101. 'total_shipping' => wc_format_decimal( $order->get_shipping_total(), 2 ),  
  102. 'cart_tax' => wc_format_decimal( $order->get_cart_tax(), 2 ),  
  103. 'shipping_tax' => wc_format_decimal( $order->get_shipping_tax(), 2 ),  
  104. 'total_discount' => wc_format_decimal( $order->get_total_discount(), 2 ),  
  105. 'cart_discount' => wc_format_decimal( 0, 2 ),  
  106. 'order_discount' => wc_format_decimal( 0, 2 ),  
  107. 'shipping_methods' => $order->get_shipping_method(),  
  108. 'payment_details' => array( 
  109. 'method_id' => $order->get_payment_method(),  
  110. 'method_title' => $order->get_payment_method_title(),  
  111. 'paid' => ! is_null( $order->get_date_paid() ),  
  112. ),  
  113. 'billing_address' => array( 
  114. 'first_name' => $order->get_billing_first_name(),  
  115. 'last_name' => $order->get_billing_last_name(),  
  116. 'company' => $order->get_billing_company(),  
  117. 'address_1' => $order->get_billing_address_1(),  
  118. 'address_2' => $order->get_billing_address_2(),  
  119. 'city' => $order->get_billing_city(),  
  120. 'state' => $order->get_billing_state(),  
  121. 'postcode' => $order->get_billing_postcode(),  
  122. 'country' => $order->get_billing_country(),  
  123. 'email' => $order->get_billing_email(),  
  124. 'phone' => $order->get_billing_phone(),  
  125. ),  
  126. 'shipping_address' => array( 
  127. 'first_name' => $order->get_shipping_first_name(),  
  128. 'last_name' => $order->get_shipping_last_name(),  
  129. 'company' => $order->get_shipping_company(),  
  130. 'address_1' => $order->get_shipping_address_1(),  
  131. 'address_2' => $order->get_shipping_address_2(),  
  132. 'city' => $order->get_shipping_city(),  
  133. 'state' => $order->get_shipping_state(),  
  134. 'postcode' => $order->get_shipping_postcode(),  
  135. 'country' => $order->get_shipping_country(),  
  136. ),  
  137. 'note' => $order->get_customer_note(),  
  138. 'customer_ip' => $order->get_customer_ip_address(),  
  139. 'customer_user_agent' => $order->get_customer_user_agent(),  
  140. 'customer_id' => $order->get_user_id(),  
  141. 'view_order_url' => $order->get_view_order_url(),  
  142. 'line_items' => array(),  
  143. 'shipping_lines' => array(),  
  144. 'tax_lines' => array(),  
  145. 'fee_lines' => array(),  
  146. 'coupon_lines' => array(),  
  147. ); 
  148.  
  149. // add line items 
  150. foreach ( $order->get_items() as $item_id => $item ) { 
  151. $product = $item->get_product(); 
  152. $order_data['line_items'][] = array( 
  153. 'id' => $item_id,  
  154. 'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item ), 2 ),  
  155. 'total' => wc_format_decimal( $order->get_line_total( $item ), 2 ),  
  156. 'total_tax' => wc_format_decimal( $order->get_line_tax( $item ), 2 ),  
  157. 'price' => wc_format_decimal( $order->get_item_total( $item ), 2 ),  
  158. 'quantity' => $item->get_quantity(),  
  159. 'tax_class' => $item->get_tax_class(),  
  160. 'name' => $item->get_name(),  
  161. 'product_id' => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(),  
  162. 'sku' => is_object( $product ) ? $product->get_sku() : null,  
  163. ); 
  164.  
  165. // add shipping 
  166. foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) { 
  167. $order_data['shipping_lines'][] = array( 
  168. 'id' => $shipping_item_id,  
  169. 'method_id' => $shipping_item->get_method_id(),  
  170. 'method_title' => $shipping_item->get_name(),  
  171. 'total' => wc_format_decimal( $shipping_item->get_total(), 2 ),  
  172. ); 
  173.  
  174. // add taxes 
  175. foreach ( $order->get_tax_totals() as $tax_code => $tax ) { 
  176. $order_data['tax_lines'][] = array( 
  177. 'code' => $tax_code,  
  178. 'title' => $tax->label,  
  179. 'total' => wc_format_decimal( $tax->amount, 2 ),  
  180. 'compound' => (bool) $tax->is_compound,  
  181. ); 
  182.  
  183. // add fees 
  184. foreach ( $order->get_fees() as $fee_item_id => $fee_item ) { 
  185. $order_data['fee_lines'][] = array( 
  186. 'id' => $fee_item_id,  
  187. 'title' => $fee_item->get_name(),  
  188. 'tax_class' => $fee_item->get_tax_class(),  
  189. 'total' => wc_format_decimal( $order->get_line_total( $fee_item ), 2 ),  
  190. 'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), 2 ),  
  191. ); 
  192.  
  193. // add coupons 
  194. foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) { 
  195. $order_data['coupon_lines'][] = array( 
  196. 'id' => $coupon_item_id,  
  197. 'code' => $coupon_item->get_code(),  
  198. 'amount' => wc_format_decimal( $coupon_item->get_discount(), 2 ),  
  199. ); 
  200.  
  201. return array( 'order' => apply_filters( 'woocommerce_api_order_response', $order_data, $order, $fields, $this->server ) ); 
  202.  
  203. /** 
  204. * Get the total number of orders 
  205. * @since 2.1 
  206. * @param string $status 
  207. * @param array $filter 
  208. * @return array 
  209. */ 
  210. public function get_orders_count( $status = null, $filter = array() ) { 
  211.  
  212. if ( ! empty( $status ) ) 
  213. $filter['status'] = $status; 
  214.  
  215. $query = $this->query_orders( $filter ); 
  216.  
  217. if ( ! current_user_can( 'read_private_shop_orders' ) ) 
  218. return new WP_Error( 'woocommerce_api_user_cannot_read_orders_count', __( 'You do not have permission to read the orders count', 'woocommerce' ), array( 'status' => 401 ) ); 
  219.  
  220. return array( 'count' => (int) $query->found_posts ); 
  221.  
  222. /** 
  223. * Edit an order 
  224. * API v1 only allows updating the status of an order 
  225. * @since 2.1 
  226. * @param int $id the order ID 
  227. * @param array $data 
  228. * @return array 
  229. */ 
  230. public function edit_order( $id, $data ) { 
  231.  
  232. $id = $this->validate_request( $id, 'shop_order', 'edit' ); 
  233.  
  234. if ( is_wp_error( $id ) ) 
  235. return $id; 
  236.  
  237. $order = wc_get_order( $id ); 
  238.  
  239. if ( ! empty( $data['status'] ) ) { 
  240.  
  241. $order->update_status( $data['status'], isset( $data['note'] ) ? $data['note'] : '' ); 
  242.  
  243. return $this->get_order( $id ); 
  244.  
  245. /** 
  246. * Delete an order 
  247. * @param int $id the order ID 
  248. * @param bool $force true to permanently delete order, false to move to trash 
  249. * @return array 
  250. */ 
  251. public function delete_order( $id, $force = false ) { 
  252.  
  253. $id = $this->validate_request( $id, 'shop_order', 'delete' ); 
  254.  
  255. return $this->delete( $id, 'order', ( 'true' === $force ) ); 
  256.  
  257. /** 
  258. * Get the admin order notes for an order 
  259. * @since 2.1 
  260. * @param int $id the order ID 
  261. * @param string $fields fields to include in response 
  262. * @return array 
  263. */ 
  264. public function get_order_notes( $id, $fields = null ) { 
  265.  
  266. // ensure ID is valid order ID 
  267. $id = $this->validate_request( $id, 'shop_order', 'read' ); 
  268.  
  269. if ( is_wp_error( $id ) ) 
  270. return $id; 
  271.  
  272. $args = array( 
  273. 'post_id' => $id,  
  274. 'approve' => 'approve',  
  275. 'type' => 'order_note',  
  276. ); 
  277.  
  278. remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); 
  279.  
  280. $notes = get_comments( $args ); 
  281.  
  282. add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); 
  283.  
  284. $order_notes = array(); 
  285.  
  286. foreach ( $notes as $note ) { 
  287.  
  288. $order_notes[] = array( 
  289. 'id' => $note->comment_ID,  
  290. 'created_at' => $this->server->format_datetime( $note->comment_date_gmt ),  
  291. 'note' => $note->comment_content,  
  292. 'customer_note' => get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ? true : false,  
  293. ); 
  294.  
  295. return array( 'order_notes' => apply_filters( 'woocommerce_api_order_notes_response', $order_notes, $id, $fields, $notes, $this->server ) ); 
  296.  
  297. /** 
  298. * Helper method to get order post objects 
  299. * @since 2.1 
  300. * @param array $args request arguments for filtering query 
  301. * @return WP_Query 
  302. */ 
  303. private function query_orders( $args ) { 
  304.  
  305. // set base query arguments 
  306. $query_args = array( 
  307. 'fields' => 'ids',  
  308. 'post_type' => 'shop_order',  
  309. 'post_status' => array_keys( wc_get_order_statuses() ),  
  310. ); 
  311.  
  312. // add status argument 
  313. if ( ! empty( $args['status'] ) ) { 
  314.  
  315. $statuses = 'wc-' . str_replace( ', ', ', wc-', $args['status'] ); 
  316. $statuses = explode( ', ', $statuses ); 
  317. $query_args['post_status'] = $statuses; 
  318.  
  319. unset( $args['status'] ); 
  320.  
  321.  
  322. $query_args = $this->merge_query_args( $query_args, $args ); 
  323.  
  324. return new WP_Query( $query_args ); 
  325.  
  326. /** 
  327. * Helper method to get the order subtotal 
  328. * @since 2.1 
  329. * @param WC_Order $order 
  330. * @return float 
  331. */ 
  332. private function get_order_subtotal( $order ) { 
  333. $subtotal = 0; 
  334.  
  335. // subtotal 
  336. foreach ( $order->get_items() as $item ) { 
  337. $subtotal += $item->get_subtotal(); 
  338.  
  339. return $subtotal; 
/includes/api/legacy/v2/class-wc-api-orders.php  
  1. class WC_API_Orders extends WC_API_Resource { 
  2.  
  3. /** @var string $base the route base */ 
  4. protected $base = '/orders'; 
  5.  
  6. /** @var string $post_type the custom post type */ 
  7. protected $post_type = 'shop_order'; 
  8.  
  9. /** 
  10. * Register the routes for this class 
  11. * GET|POST /orders 
  12. * GET /orders/count 
  13. * GET|PUT|DELETE /orders/<id> 
  14. * GET /orders/<id>/notes 
  15. * @since 2.1 
  16. * @param array $routes 
  17. * @return array 
  18. */ 
  19. public function register_routes( $routes ) { 
  20.  
  21. # GET|POST /orders 
  22. $routes[ $this->base ] = array( 
  23. array( array( $this, 'get_orders' ), WC_API_Server::READABLE ),  
  24. array( array( $this, 'create_order' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  25. ); 
  26.  
  27. # GET /orders/count 
  28. $routes[ $this->base . '/count' ] = array( 
  29. array( array( $this, 'get_orders_count' ), WC_API_Server::READABLE ),  
  30. ); 
  31.  
  32. # GET /orders/statuses 
  33. $routes[ $this->base . '/statuses' ] = array( 
  34. array( array( $this, 'get_order_statuses' ), WC_API_Server::READABLE ),  
  35. ); 
  36.  
  37. # GET|PUT|DELETE /orders/<id> 
  38. $routes[ $this->base . '/(?P<id>\d+)' ] = array( 
  39. array( array( $this, 'get_order' ), WC_API_Server::READABLE ),  
  40. array( array( $this, 'edit_order' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  41. array( array( $this, 'delete_order' ), WC_API_Server::DELETABLE ),  
  42. ); 
  43.  
  44. # GET|POST /orders/<id>/notes 
  45. $routes[ $this->base . '/(?P<order_id>\d+)/notes' ] = array( 
  46. array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ),  
  47. array( array( $this, 'create_order_note' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  48. ); 
  49.  
  50. # GET|PUT|DELETE /orders/<order_id>/notes/<id> 
  51. $routes[ $this->base . '/(?P<order_id>\d+)/notes/(?P<id>\d+)' ] = array( 
  52. array( array( $this, 'get_order_note' ), WC_API_Server::READABLE ),  
  53. array( array( $this, 'edit_order_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  54. array( array( $this, 'delete_order_note' ), WC_API_SERVER::DELETABLE ),  
  55. ); 
  56.  
  57. # GET|POST /orders/<order_id>/refunds 
  58. $routes[ $this->base . '/(?P<order_id>\d+)/refunds' ] = array( 
  59. array( array( $this, 'get_order_refunds' ), WC_API_Server::READABLE ),  
  60. array( array( $this, 'create_order_refund' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  61. ); 
  62.  
  63. # GET|PUT|DELETE /orders/<order_id>/refunds/<id> 
  64. $routes[ $this->base . '/(?P<order_id>\d+)/refunds/(?P<id>\d+)' ] = array( 
  65. array( array( $this, 'get_order_refund' ), WC_API_Server::READABLE ),  
  66. array( array( $this, 'edit_order_refund' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  67. array( array( $this, 'delete_order_refund' ), WC_API_SERVER::DELETABLE ),  
  68. ); 
  69.  
  70. # POST|PUT /orders/bulk 
  71. $routes[ $this->base . '/bulk' ] = array( 
  72. array( array( $this, 'bulk' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  73. ); 
  74.  
  75. return $routes; 
  76.  
  77. /** 
  78. * Get all orders 
  79. * @since 2.1 
  80. * @param string $fields 
  81. * @param array $filter 
  82. * @param string $status 
  83. * @param int $page 
  84. * @return array 
  85. */ 
  86. public function get_orders( $fields = null, $filter = array(), $status = null, $page = 1 ) { 
  87.  
  88. if ( ! empty( $status ) ) { 
  89. $filter['status'] = $status; 
  90.  
  91. $filter['page'] = $page; 
  92.  
  93. $query = $this->query_orders( $filter ); 
  94.  
  95. $orders = array(); 
  96.  
  97. foreach ( $query->posts as $order_id ) { 
  98.  
  99. if ( ! $this->is_readable( $order_id ) ) { 
  100. continue; 
  101.  
  102. $orders[] = current( $this->get_order( $order_id, $fields, $filter ) ); 
  103.  
  104. $this->server->add_pagination_headers( $query ); 
  105.  
  106. return array( 'orders' => $orders ); 
  107.  
  108.  
  109. /** 
  110. * Get the order for the given ID 
  111. * @since 2.1 
  112. * @param int $id the order ID 
  113. * @param array $fields 
  114. * @param array $filter 
  115. * @return array 
  116. */ 
  117. public function get_order( $id, $fields = null, $filter = array() ) { 
  118.  
  119. // ensure order ID is valid & user has permission to read 
  120. $id = $this->validate_request( $id, $this->post_type, 'read' ); 
  121.  
  122. if ( is_wp_error( $id ) ) { 
  123. return $id; 
  124.  
  125. // Get the decimal precession 
  126. $dp = ( isset( $filter['dp'] ) ? intval( $filter['dp'] ) : 2 ); 
  127. $order = wc_get_order( $id ); 
  128. $order_data = array( 
  129. 'id' => $order->get_id(),  
  130. 'order_number' => $order->get_order_number(),  
  131. 'created_at' => $this->server->format_datetime( $order->get_date_created() ? $order->get_date_created()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  132. 'updated_at' => $this->server->format_datetime( $order->get_date_modified() ? $order->get_date_modified()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  133. 'completed_at' => $this->server->format_datetime( $order->get_date_completed() ? $order->get_date_completed()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  134. 'status' => $order->get_status(),  
  135. 'currency' => $order->get_currency(),  
  136. 'total' => wc_format_decimal( $order->get_total(), $dp ),  
  137. 'subtotal' => wc_format_decimal( $order->get_subtotal(), $dp ),  
  138. 'total_line_items_quantity' => $order->get_item_count(),  
  139. 'total_tax' => wc_format_decimal( $order->get_total_tax(), $dp ),  
  140. 'total_shipping' => wc_format_decimal( $order->get_shipping_total(), $dp ),  
  141. 'cart_tax' => wc_format_decimal( $order->get_cart_tax(), $dp ),  
  142. 'shipping_tax' => wc_format_decimal( $order->get_shipping_tax(), $dp ),  
  143. 'total_discount' => wc_format_decimal( $order->get_total_discount(), $dp ),  
  144. 'shipping_methods' => $order->get_shipping_method(),  
  145. 'payment_details' => array( 
  146. 'method_id' => $order->get_payment_method(),  
  147. 'method_title' => $order->get_payment_method_title(),  
  148. 'paid' => ! is_null( $order->get_date_paid() ),  
  149. ),  
  150. 'billing_address' => array( 
  151. 'first_name' => $order->get_billing_first_name(),  
  152. 'last_name' => $order->get_billing_last_name(),  
  153. 'company' => $order->get_billing_company(),  
  154. 'address_1' => $order->get_billing_address_1(),  
  155. 'address_2' => $order->get_billing_address_2(),  
  156. 'city' => $order->get_billing_city(),  
  157. 'state' => $order->get_billing_state(),  
  158. 'postcode' => $order->get_billing_postcode(),  
  159. 'country' => $order->get_billing_country(),  
  160. 'email' => $order->get_billing_email(),  
  161. 'phone' => $order->get_billing_phone(),  
  162. ),  
  163. 'shipping_address' => array( 
  164. 'first_name' => $order->get_shipping_first_name(),  
  165. 'last_name' => $order->get_shipping_last_name(),  
  166. 'company' => $order->get_shipping_company(),  
  167. 'address_1' => $order->get_shipping_address_1(),  
  168. 'address_2' => $order->get_shipping_address_2(),  
  169. 'city' => $order->get_shipping_city(),  
  170. 'state' => $order->get_shipping_state(),  
  171. 'postcode' => $order->get_shipping_postcode(),  
  172. 'country' => $order->get_shipping_country(),  
  173. ),  
  174. 'note' => $order->get_customer_note(),  
  175. 'customer_ip' => $order->get_customer_ip_address(),  
  176. 'customer_user_agent' => $order->get_customer_user_agent(),  
  177. 'customer_id' => $order->get_user_id(),  
  178. 'view_order_url' => $order->get_view_order_url(),  
  179. 'line_items' => array(),  
  180. 'shipping_lines' => array(),  
  181. 'tax_lines' => array(),  
  182. 'fee_lines' => array(),  
  183. 'coupon_lines' => array(),  
  184. ); 
  185.  
  186. // add line items 
  187. foreach ( $order->get_items() as $item_id => $item ) { 
  188. $product = $item->get_product(); 
  189. $hideprefix = ( isset( $filter['all_item_meta'] ) && 'true' === $filter['all_item_meta'] ) ? null : '_'; 
  190. $item_meta = $item->get_formatted_meta_data( $hideprefix ); 
  191.  
  192. foreach ( $item_meta as $key => $values ) { 
  193. $item_meta[ $key ]->label = $values->display_key; 
  194. unset( $item_meta[ $key ]->display_key ); 
  195. unset( $item_meta[ $key ]->display_value ); 
  196.  
  197. $order_data['line_items'][] = array( 
  198. 'id' => $item_id,  
  199. 'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item, false, false ), $dp ),  
  200. 'subtotal_tax' => wc_format_decimal( $item->get_subtotal_tax(), $dp ),  
  201. 'total' => wc_format_decimal( $order->get_line_total( $item, false, false ), $dp ),  
  202. 'total_tax' => wc_format_decimal( $item->get_total_tax(), $dp ),  
  203. 'price' => wc_format_decimal( $order->get_item_total( $item, false, false ), $dp ),  
  204. 'quantity' => $item->get_quantity(),  
  205. 'tax_class' => $item->get_tax_class(),  
  206. 'name' => $item->get_name(),  
  207. 'product_id' => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(),  
  208. 'sku' => is_object( $product ) ? $product->get_sku() : null,  
  209. 'meta' => array_values( $item_meta ),  
  210. ); 
  211.  
  212. // add shipping 
  213. foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) { 
  214. $order_data['shipping_lines'][] = array( 
  215. 'id' => $shipping_item_id,  
  216. 'method_id' => $shipping_item->get_method_id(),  
  217. 'method_title' => $shipping_item->get_name(),  
  218. 'total' => wc_format_decimal( $shipping_item->get_total(), $dp ),  
  219. ); 
  220.  
  221. // add taxes 
  222. foreach ( $order->get_tax_totals() as $tax_code => $tax ) { 
  223. $order_data['tax_lines'][] = array( 
  224. 'id' => $tax->id,  
  225. 'rate_id' => $tax->rate_id,  
  226. 'code' => $tax_code,  
  227. 'title' => $tax->label,  
  228. 'total' => wc_format_decimal( $tax->amount, $dp ),  
  229. 'compound' => (bool) $tax->is_compound,  
  230. ); 
  231.  
  232. // add fees 
  233. foreach ( $order->get_fees() as $fee_item_id => $fee_item ) { 
  234. $order_data['fee_lines'][] = array( 
  235. 'id' => $fee_item_id,  
  236. 'title' => $fee_item->get_name(),  
  237. 'tax_class' => $fee_item->get_tax_class(),  
  238. 'total' => wc_format_decimal( $order->get_line_total( $fee_item ), $dp ),  
  239. 'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), $dp ),  
  240. ); 
  241.  
  242. // add coupons 
  243. foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) { 
  244. $order_data['coupon_lines'][] = array( 
  245. 'id' => $coupon_item_id,  
  246. 'code' => $coupon_item->get_code(),  
  247. 'amount' => wc_format_decimal( $coupon_item->get_discount(), $dp ),  
  248. ); 
  249.  
  250. return array( 'order' => apply_filters( 'woocommerce_api_order_response', $order_data, $order, $fields, $this->server ) ); 
  251.  
  252. /** 
  253. * Get the total number of orders 
  254. * @since 2.4 
  255. * @param string $status 
  256. * @param array $filter 
  257. * @return array 
  258. */ 
  259. public function get_orders_count( $status = null, $filter = array() ) { 
  260.  
  261. try { 
  262. if ( ! current_user_can( 'read_private_shop_orders' ) ) { 
  263. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_orders_count', __( 'You do not have permission to read the orders count', 'woocommerce' ), 401 ); 
  264.  
  265. if ( ! empty( $status ) ) { 
  266.  
  267. if ( 'any' === $status ) { 
  268.  
  269. $order_statuses = array(); 
  270.  
  271. foreach ( wc_get_order_statuses() as $slug => $name ) { 
  272. $filter['status'] = str_replace( 'wc-', '', $slug ); 
  273. $query = $this->query_orders( $filter ); 
  274. $order_statuses[ str_replace( 'wc-', '', $slug ) ] = (int) $query->found_posts; 
  275.  
  276. return array( 'count' => $order_statuses ); 
  277.  
  278. } else { 
  279. $filter['status'] = $status; 
  280.  
  281. $query = $this->query_orders( $filter ); 
  282.  
  283. return array( 'count' => (int) $query->found_posts ); 
  284.  
  285. } catch ( WC_API_Exception $e ) { 
  286. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  287.  
  288. /** 
  289. * Get a list of valid order statuses 
  290. * Note this requires no specific permissions other than being an authenticated 
  291. * API user. Order statuses (particularly custom statuses) could be considered 
  292. * private information which is why it's not in the API index. 
  293. * @since 2.1 
  294. * @return array 
  295. */ 
  296. public function get_order_statuses() { 
  297.  
  298. $order_statuses = array(); 
  299.  
  300. foreach ( wc_get_order_statuses() as $slug => $name ) { 
  301. $order_statuses[ str_replace( 'wc-', '', $slug ) ] = $name; 
  302.  
  303. return array( 'order_statuses' => apply_filters( 'woocommerce_api_order_statuses_response', $order_statuses, $this ) ); 
  304.  
  305. /** 
  306. * Create an order 
  307. * @since 2.2 
  308. * @param array $data raw order data 
  309. * @return array 
  310. */ 
  311. public function create_order( $data ) { 
  312. global $wpdb; 
  313.  
  314. wc_transaction_query( 'start' ); 
  315.  
  316. try { 
  317. if ( ! isset( $data['order'] ) ) { 
  318. throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order' ), 400 ); 
  319.  
  320. $data = $data['order']; 
  321.  
  322. // permission check 
  323. if ( ! current_user_can( 'publish_shop_orders' ) ) { 
  324. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order', __( 'You do not have permission to create orders', 'woocommerce' ), 401 ); 
  325.  
  326. $data = apply_filters( 'woocommerce_api_create_order_data', $data, $this ); 
  327.  
  328. // default order args, note that status is checked for validity in wc_create_order() 
  329. $default_order_args = array( 
  330. 'status' => isset( $data['status'] ) ? $data['status'] : '',  
  331. 'customer_note' => isset( $data['note'] ) ? $data['note'] : null,  
  332. ); 
  333.  
  334. // if creating order for existing customer 
  335. if ( ! empty( $data['customer_id'] ) ) { 
  336.  
  337. // make sure customer exists 
  338. if ( false === get_user_by( 'id', $data['customer_id'] ) ) { 
  339. throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 ); 
  340.  
  341. $default_order_args['customer_id'] = $data['customer_id']; 
  342.  
  343. // create the pending order 
  344. $order = $this->create_base_order( $default_order_args, $data ); 
  345.  
  346. if ( is_wp_error( $order ) ) { 
  347. throw new WC_API_Exception( 'woocommerce_api_cannot_create_order', sprintf( __( 'Cannot create order: %s', 'woocommerce' ), implode( ', ', $order->get_error_messages() ) ), 400 ); 
  348.  
  349. // billing/shipping addresses 
  350. $this->set_order_addresses( $order, $data ); 
  351.  
  352. $lines = array( 
  353. 'line_item' => 'line_items',  
  354. 'shipping' => 'shipping_lines',  
  355. 'fee' => 'fee_lines',  
  356. 'coupon' => 'coupon_lines',  
  357. ); 
  358.  
  359. foreach ( $lines as $line_type => $line ) { 
  360.  
  361. if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) { 
  362.  
  363. $set_item = "set_{$line_type}"; 
  364.  
  365. foreach ( $data[ $line ] as $item ) { 
  366.  
  367. $this->$set_item( $order, $item, 'create' ); 
  368.  
  369. // calculate totals and set them 
  370. $order->calculate_totals(); 
  371.  
  372. // payment method (and payment_complete() if `paid` == true) 
  373. if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) { 
  374.  
  375. // method ID & title are required 
  376. if ( empty( $data['payment_details']['method_id'] ) || empty( $data['payment_details']['method_title'] ) ) { 
  377. throw new WC_API_Exception( 'woocommerce_invalid_payment_details', __( 'Payment method ID and title are required', 'woocommerce' ), 400 ); 
  378.  
  379. update_post_meta( $order->get_id(), '_payment_method', $data['payment_details']['method_id'] ); 
  380. update_post_meta( $order->get_id(), '_payment_method_title', $data['payment_details']['method_title'] ); 
  381.  
  382. // mark as paid if set 
  383. if ( isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) { 
  384. $order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' ); 
  385.  
  386. // set order currency 
  387. if ( isset( $data['currency'] ) ) { 
  388.  
  389. if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) { 
  390. throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid.', 'woocommerce' ), 400 ); 
  391.  
  392. update_post_meta( $order->get_id(), '_order_currency', $data['currency'] ); 
  393.  
  394. // set order meta 
  395. if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) { 
  396. $this->set_order_meta( $order->get_id(), $data['order_meta'] ); 
  397.  
  398. // HTTP 201 Created 
  399. $this->server->send_status( 201 ); 
  400.  
  401. wc_delete_shop_order_transients( $order ); 
  402.  
  403. do_action( 'woocommerce_api_create_order', $order->get_id(), $data, $this ); 
  404.  
  405. wc_transaction_query( 'commit' ); 
  406.  
  407. return $this->get_order( $order->get_id() ); 
  408.  
  409. } catch ( WC_Data_Exception $e ) { 
  410. wc_transaction_query( 'rollback' ); 
  411. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  412. } catch ( WC_API_Exception $e ) { 
  413. wc_transaction_query( 'rollback' ); 
  414. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  415.  
  416. /** 
  417. * Creates new WC_Order. 
  418. * Requires a separate function for classes that extend WC_API_Orders. 
  419. * @since 2.3 
  420. * @param $args array 
  421. * @return WC_Order 
  422. */ 
  423. protected function create_base_order( $args, $data ) { 
  424. return wc_create_order( $args ); 
  425.  
  426. /** 
  427. * Edit an order 
  428. * @since 2.2 
  429. * @param int $id the order ID 
  430. * @param array $data 
  431. * @return array 
  432. */ 
  433. public function edit_order( $id, $data ) { 
  434. try { 
  435. if ( ! isset( $data['order'] ) ) { 
  436. throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order' ), 400 ); 
  437.  
  438. $data = $data['order']; 
  439.  
  440. $update_totals = false; 
  441.  
  442. $id = $this->validate_request( $id, $this->post_type, 'edit' ); 
  443.  
  444. if ( is_wp_error( $id ) ) { 
  445. return $id; 
  446.  
  447. $data = apply_filters( 'woocommerce_api_edit_order_data', $data, $id, $this ); 
  448. $order = wc_get_order( $id ); 
  449.  
  450. if ( empty( $order ) ) { 
  451. throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 ); 
  452.  
  453. $order_args = array( 'order_id' => $order->get_id() ); 
  454.  
  455. // Customer note. 
  456. if ( isset( $data['note'] ) ) { 
  457. $order_args['customer_note'] = $data['note']; 
  458.  
  459. // Customer ID. 
  460. if ( isset( $data['customer_id'] ) && $data['customer_id'] != $order->get_user_id() ) { 
  461. // Make sure customer exists. 
  462. if ( false === get_user_by( 'id', $data['customer_id'] ) ) { 
  463. throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 ); 
  464.  
  465. update_post_meta( $order->get_id(), '_customer_user', $data['customer_id'] ); 
  466.  
  467. // Billing/shipping address. 
  468. $this->set_order_addresses( $order, $data ); 
  469.  
  470. $lines = array( 
  471. 'line_item' => 'line_items',  
  472. 'shipping' => 'shipping_lines',  
  473. 'fee' => 'fee_lines',  
  474. 'coupon' => 'coupon_lines',  
  475. ); 
  476.  
  477. foreach ( $lines as $line_type => $line ) { 
  478.  
  479. if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) { 
  480.  
  481. $update_totals = true; 
  482.  
  483. foreach ( $data[ $line ] as $item ) { 
  484.  
  485. // Item ID is always required. 
  486. if ( ! array_key_exists( 'id', $item ) ) { 
  487. $item['id'] = null; 
  488.  
  489. // Create item. 
  490. if ( is_null( $item['id'] ) ) { 
  491. $this->set_item( $order, $line_type, $item, 'create' ); 
  492. } elseif ( $this->item_is_null( $item ) ) { 
  493. // Delete item. 
  494. wc_delete_order_item( $item['id'] ); 
  495. } else { 
  496. // Update item. 
  497. $this->set_item( $order, $line_type, $item, 'update' ); 
  498.  
  499. // Payment method (and payment_complete() if `paid` == true and order needs payment). 
  500. if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) { 
  501.  
  502. // Method ID. 
  503. if ( isset( $data['payment_details']['method_id'] ) ) { 
  504. update_post_meta( $order->get_id(), '_payment_method', $data['payment_details']['method_id'] ); 
  505.  
  506. // Method title. 
  507. if ( isset( $data['payment_details']['method_title'] ) ) { 
  508. update_post_meta( $order->get_id(), '_payment_method_title', $data['payment_details']['method_title'] ); 
  509.  
  510. // Mark as paid if set. 
  511. if ( $order->needs_payment() && isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) { 
  512. $order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' ); 
  513.  
  514. // Set order currency. 
  515. if ( isset( $data['currency'] ) ) { 
  516. if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) { 
  517. throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid.', 'woocommerce' ), 400 ); 
  518.  
  519. update_post_meta( $order->get_id(), '_order_currency', $data['currency'] ); 
  520.  
  521. // If items have changed, recalculate order totals. 
  522. if ( $update_totals ) { 
  523. $order->calculate_totals(); 
  524.  
  525. // Update order meta. 
  526. if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) { 
  527. $this->set_order_meta( $order->get_id(), $data['order_meta'] ); 
  528.  
  529. // Update the order post to set customer note/modified date. 
  530. wc_update_order( $order_args ); 
  531.  
  532. // Order status. 
  533. if ( ! empty( $data['status'] ) ) { 
  534. // Refresh the order instance. 
  535. $order = wc_get_order( $order->get_id() ); 
  536. $order->update_status( $data['status'], isset( $data['status_note'] ) ? $data['status_note'] : '', true ); 
  537.  
  538. wc_delete_shop_order_transients( $order ); 
  539.  
  540. do_action( 'woocommerce_api_edit_order', $order->get_id(), $data, $this ); 
  541.  
  542. return $this->get_order( $id ); 
  543.  
  544. } catch ( WC_Data_Exception $e ) { 
  545. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  546. } catch ( WC_API_Exception $e ) { 
  547. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  548.  
  549. /** 
  550. * Delete an order 
  551. * @param int $id the order ID 
  552. * @param bool $force true to permanently delete order, false to move to trash 
  553. * @return array 
  554. */ 
  555. public function delete_order( $id, $force = false ) { 
  556.  
  557. $id = $this->validate_request( $id, $this->post_type, 'delete' ); 
  558.  
  559. if ( is_wp_error( $id ) ) { 
  560. return $id; 
  561.  
  562. wc_delete_shop_order_transients( $id ); 
  563.  
  564. do_action( 'woocommerce_api_delete_order', $id, $this ); 
  565.  
  566. return $this->delete( $id, 'order', ( 'true' === $force ) ); 
  567.  
  568. /** 
  569. * Helper method to get order post objects 
  570. * @since 2.1 
  571. * @param array $args request arguments for filtering query 
  572. * @return WP_Query 
  573. */ 
  574. protected function query_orders( $args ) { 
  575.  
  576. // set base query arguments 
  577. $query_args = array( 
  578. 'fields' => 'ids',  
  579. 'post_type' => $this->post_type,  
  580. 'post_status' => array_keys( wc_get_order_statuses() ),  
  581. ); 
  582.  
  583. // add status argument 
  584. if ( ! empty( $args['status'] ) ) { 
  585.  
  586. $statuses = 'wc-' . str_replace( ', ', ', wc-', $args['status'] ); 
  587. $statuses = explode( ', ', $statuses ); 
  588. $query_args['post_status'] = $statuses; 
  589.  
  590. unset( $args['status'] ); 
  591.  
  592.  
  593. $query_args = $this->merge_query_args( $query_args, $args ); 
  594.  
  595. return new WP_Query( $query_args ); 
  596.  
  597. /** 
  598. * Helper method to set/update the billing & shipping addresses for 
  599. * an order 
  600. * @since 2.1 
  601. * @param \WC_Order $order 
  602. * @param array $data 
  603. */ 
  604. protected function set_order_addresses( $order, $data ) { 
  605.  
  606. $address_fields = array( 
  607. 'first_name',  
  608. 'last_name',  
  609. 'company',  
  610. 'email',  
  611. 'phone',  
  612. 'address_1',  
  613. 'address_2',  
  614. 'city',  
  615. 'state',  
  616. 'postcode',  
  617. 'country',  
  618. ); 
  619.  
  620. $billing_address = $shipping_address = array(); 
  621.  
  622. // billing address 
  623. if ( isset( $data['billing_address'] ) && is_array( $data['billing_address'] ) ) { 
  624.  
  625. foreach ( $address_fields as $field ) { 
  626.  
  627. if ( isset( $data['billing_address'][ $field ] ) ) { 
  628. $billing_address[ $field ] = wc_clean( $data['billing_address'][ $field ] ); 
  629.  
  630. unset( $address_fields['email'] ); 
  631. unset( $address_fields['phone'] ); 
  632.  
  633. // shipping address 
  634. if ( isset( $data['shipping_address'] ) && is_array( $data['shipping_address'] ) ) { 
  635.  
  636. foreach ( $address_fields as $field ) { 
  637.  
  638. if ( isset( $data['shipping_address'][ $field ] ) ) { 
  639. $shipping_address[ $field ] = wc_clean( $data['shipping_address'][ $field ] ); 
  640.  
  641. $this->update_address( $order, $billing_address, 'billing' ); 
  642. $this->update_address( $order, $shipping_address, 'shipping' ); 
  643.  
  644. // update user meta 
  645. if ( $order->get_user_id() ) { 
  646. foreach ( $billing_address as $key => $value ) { 
  647. update_user_meta( $order->get_user_id(), 'billing_' . $key, $value ); 
  648. foreach ( $shipping_address as $key => $value ) { 
  649. update_user_meta( $order->get_user_id(), 'shipping_' . $key, $value ); 
  650.  
  651. /** 
  652. * Update address. 
  653. * @param WC_Order $order 
  654. * @param array $posted 
  655. * @param string $type 
  656. */ 
  657. protected function update_address( $order, $posted, $type = 'billing' ) { 
  658. foreach ( $posted as $key => $value ) { 
  659. if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) { 
  660. $order->{"set_{$type}_{$key}"}( $value ); 
  661.  
  662. /** 
  663. * Helper method to add/update order meta, with two restrictions: 
  664. * 1) Only non-protected meta (no leading underscore) can be set 
  665. * 2) Meta values must be scalar (int, string, bool) 
  666. * @since 2.2 
  667. * @param int $order_id valid order ID 
  668. * @param array $order_meta order meta in array( 'meta_key' => 'meta_value' ) format 
  669. */ 
  670. protected function set_order_meta( $order_id, $order_meta ) { 
  671.  
  672. foreach ( $order_meta as $meta_key => $meta_value ) { 
  673.  
  674. if ( is_string( $meta_key ) && ! is_protected_meta( $meta_key ) && is_scalar( $meta_value ) ) { 
  675. update_post_meta( $order_id, $meta_key, $meta_value ); 
  676.  
  677. /** 
  678. * Helper method to check if the resource ID associated with the provided item is null 
  679. * Items can be deleted by setting the resource ID to null 
  680. * @since 2.2 
  681. * @param array $item item provided in the request body 
  682. * @return bool true if the item resource ID is null, false otherwise 
  683. */ 
  684. protected function item_is_null( $item ) { 
  685.  
  686. $keys = array( 'product_id', 'method_id', 'title', 'code' ); 
  687.  
  688. foreach ( $keys as $key ) { 
  689. if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) { 
  690. return true; 
  691.  
  692. return false; 
  693.  
  694. /** 
  695. * Wrapper method to create/update order items 
  696. * When updating, the item ID provided is checked to ensure it is associated 
  697. * with the order. 
  698. * @since 2.2 
  699. * @param \WC_Order $order order 
  700. * @param string $item_type 
  701. * @param array $item item provided in the request body 
  702. * @param string $action either 'create' or 'update' 
  703. * @throws WC_API_Exception if item ID is not associated with order 
  704. */ 
  705. protected function set_item( $order, $item_type, $item, $action ) { 
  706. global $wpdb; 
  707.  
  708. $set_method = "set_{$item_type}"; 
  709.  
  710. // verify provided line item ID is associated with order 
  711. if ( 'update' === $action ) { 
  712.  
  713. $result = $wpdb->get_row( 
  714. $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d",  
  715. absint( $item['id'] ),  
  716. absint( $order->get_id() ) 
  717. ) ); 
  718.  
  719. if ( is_null( $result ) ) { 
  720. throw new WC_API_Exception( 'woocommerce_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 ); 
  721.  
  722. $this->$set_method( $order, $item, $action ); 
  723.  
  724. /** 
  725. * Create or update a line item 
  726. * @since 2.2 
  727. * @param \WC_Order $order 
  728. * @param array $item line item data 
  729. * @param string $action 'create' to add line item or 'update' to update it 
  730. * @throws WC_API_Exception invalid data, server error 
  731. */ 
  732. protected function set_line_item( $order, $item, $action ) { 
  733. $creating = ( 'create' === $action ); 
  734.  
  735. // product is always required 
  736. if ( ! isset( $item['product_id'] ) && ! isset( $item['sku'] ) ) { 
  737. throw new WC_API_Exception( 'woocommerce_api_invalid_product_id', __( 'Product ID or SKU is required', 'woocommerce' ), 400 ); 
  738.  
  739. // when updating, ensure product ID provided matches 
  740. if ( 'update' === $action ) { 
  741.  
  742. $item_product_id = wc_get_order_item_meta( $item['id'], '_product_id' ); 
  743. $item_variation_id = wc_get_order_item_meta( $item['id'], '_variation_id' ); 
  744.  
  745. if ( $item['product_id'] != $item_product_id && $item['product_id'] != $item_variation_id ) { 
  746. throw new WC_API_Exception( 'woocommerce_api_invalid_product_id', __( 'Product ID provided does not match this line item', 'woocommerce' ), 400 ); 
  747.  
  748. if ( isset( $item['product_id'] ) ) { 
  749. $product_id = $item['product_id']; 
  750. } elseif ( isset( $item['sku'] ) ) { 
  751. $product_id = wc_get_product_id_by_sku( $item['sku'] ); 
  752.  
  753. // variations must each have a key & value 
  754. $variation_id = 0; 
  755. if ( isset( $item['variations'] ) && is_array( $item['variations'] ) ) { 
  756. foreach ( $item['variations'] as $key => $value ) { 
  757. if ( ! $key || ! $value ) { 
  758. throw new WC_API_Exception( 'woocommerce_api_invalid_product_variation', __( 'The product variation is invalid', 'woocommerce' ), 400 ); 
  759. $variation_id = $this->get_variation_id( wc_get_product( $product_id ), $item['variations'] ); 
  760.  
  761. $product = wc_get_product( $variation_id ? $variation_id : $product_id ); 
  762.  
  763. // must be a valid WC_Product 
  764. if ( ! is_object( $product ) ) { 
  765. throw new WC_API_Exception( 'woocommerce_api_invalid_product', __( 'Product is invalid.', 'woocommerce' ), 400 ); 
  766.  
  767. // quantity must be positive float 
  768. if ( isset( $item['quantity'] ) && floatval( $item['quantity'] ) <= 0 ) { 
  769. throw new WC_API_Exception( 'woocommerce_api_invalid_product_quantity', __( 'Product quantity must be a positive float.', 'woocommerce' ), 400 ); 
  770.  
  771. // quantity is required when creating 
  772. if ( $creating && ! isset( $item['quantity'] ) ) { 
  773. throw new WC_API_Exception( 'woocommerce_api_invalid_product_quantity', __( 'Product quantity is required.', 'woocommerce' ), 400 ); 
  774.  
  775. if ( $creating ) { 
  776. $line_item = new WC_Order_Item_Product(); 
  777. } else { 
  778. $line_item = new WC_Order_Item_Product( $item['id'] ); 
  779.  
  780. $line_item->set_product( $product ); 
  781. $line_item->set_order_id( $order->get_id() ); 
  782.  
  783. if ( isset( $item['quantity'] ) ) { 
  784. $line_item->set_quantity( $item['quantity'] ); 
  785. if ( isset( $item['total'] ) ) { 
  786. $line_item->set_total( floatval( $item['total'] ) ); 
  787. } elseif ( $creating ) { 
  788. $total = wc_get_price_excluding_tax( $product, array( 'qty' => $line_item->get_quantity() ) ); 
  789. $line_item->set_total( $total ); 
  790. $line_item->set_subtotal( $total ); 
  791. if ( isset( $item['total_tax'] ) ) { 
  792. $line_item->set_total_tax( floatval( $item['total_tax'] ) ); 
  793. if ( isset( $item['subtotal'] ) ) { 
  794. $line_item->set_subtotal( floatval( $item['subtotal'] ) ); 
  795. if ( isset( $item['subtotal_tax'] ) ) { 
  796. $line_item->set_subtotal_tax( floatval( $item['subtotal_tax'] ) ); 
  797. if ( $variation_id ) { 
  798. $line_item->set_variation_id( $variation_id ); 
  799. $line_item->set_variation( $item['variations'] ); 
  800.  
  801. // Save or add to order. 
  802. if ( $creating ) { 
  803. $order->add_item( $line_item ); 
  804. } else { 
  805. $item_id = $line_item->save(); 
  806.  
  807. if ( ! $item_id ) { 
  808. throw new WC_API_Exception( 'woocommerce_cannot_create_line_item', __( 'Cannot create line item, try again.', 'woocommerce' ), 500 ); 
  809.  
  810. /** 
  811. * Given a product ID & API provided variations, find the correct variation ID to use for calculation 
  812. * We can't just trust input from the API to pass a variation_id manually, otherwise you could pass 
  813. * the cheapest variation ID but provide other information so we have to look up the variation ID. 
  814. * @param int $product_id main product ID 
  815. * @return int returns an ID if a valid variation was found for this product 
  816. */ 
  817. function get_variation_id( $product, $variations = array() ) { 
  818. $variation_id = null; 
  819. $variations_normalized = array(); 
  820.  
  821. if ( $product->is_type( 'variable' ) && $product->has_child() ) { 
  822. if ( isset( $variations ) && is_array( $variations ) ) { 
  823. // start by normalizing the passed variations 
  824. foreach ( $variations as $key => $value ) { 
  825. $key = str_replace( 'attribute_', '', str_replace( 'pa_', '', $key ) ); // from get_attributes in class-wc-api-products.php 
  826. $variations_normalized[ $key ] = strtolower( $value ); 
  827. // now search through each product child and see if our passed variations match anything 
  828. foreach ( $product->get_children() as $variation ) { 
  829. $meta = array(); 
  830. foreach ( get_post_meta( $variation ) as $key => $value ) { 
  831. $value = $value[0]; 
  832. $key = str_replace( 'attribute_', '', str_replace( 'pa_', '', $key ) ); 
  833. $meta[ $key ] = strtolower( $value ); 
  834. // if the variation array is a part of the $meta array, we found our match 
  835. if ( $this->array_contains( $variations_normalized, $meta ) ) { 
  836. $variation_id = $variation; 
  837. break; 
  838.  
  839. return $variation_id; 
  840.  
  841. /** 
  842. * Utility function to see if the meta array contains data from variations 
  843. */ 
  844. protected function array_contains( $needles, $haystack ) { 
  845. foreach ( $needles as $key => $value ) { 
  846. if ( $haystack[ $key ] !== $value ) { 
  847. return false; 
  848. return true; 
  849.  
  850. /** 
  851. * Create or update an order shipping method 
  852. * @since 2.2 
  853. * @param \WC_Order $order 
  854. * @param array $shipping item data 
  855. * @param string $action 'create' to add shipping or 'update' to update it 
  856. * @throws WC_API_Exception invalid data, server error 
  857. */ 
  858. protected function set_shipping( $order, $shipping, $action ) { 
  859.  
  860. // total must be a positive float 
  861. if ( isset( $shipping['total'] ) && floatval( $shipping['total'] ) < 0 ) { 
  862. throw new WC_API_Exception( 'woocommerce_invalid_shipping_total', __( 'Shipping total must be a positive amount.', 'woocommerce' ), 400 ); 
  863.  
  864. if ( 'create' === $action ) { 
  865.  
  866. // method ID is required 
  867. if ( ! isset( $shipping['method_id'] ) ) { 
  868. throw new WC_API_Exception( 'woocommerce_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 ); 
  869.  
  870. $rate = new WC_Shipping_Rate( $shipping['method_id'], isset( $shipping['method_title'] ) ? $shipping['method_title'] : '', isset( $shipping['total'] ) ? floatval( $shipping['total'] ) : 0, array(), $shipping['method_id'] ); 
  871. $item = new WC_Order_Item_Shipping(); 
  872. $item->set_order_id( $order->get_id() ); 
  873. $item->set_shipping_rate( $rate ); 
  874. $order->add_item( $item ); 
  875. } else { 
  876.  
  877. $item = new WC_Order_Item_Shipping( $shipping['id'] ); 
  878.  
  879. if ( isset( $shipping['method_id'] ) ) { 
  880. $item->set_method_id( $shipping['method_id'] ); 
  881.  
  882. if ( isset( $shipping['method_title'] ) ) { 
  883. $item->set_method_title( $shipping['method_title'] ); 
  884.  
  885. if ( isset( $shipping['total'] ) ) { 
  886. $item->set_total( floatval( $shipping['total'] ) ); 
  887.  
  888. $shipping_id = $item->save(); 
  889.  
  890. if ( ! $shipping_id ) { 
  891. throw new WC_API_Exception( 'woocommerce_cannot_update_shipping', __( 'Cannot update shipping method, try again.', 'woocommerce' ), 500 ); 
  892.  
  893. /** 
  894. * Create or update an order fee 
  895. * @since 2.2 
  896. * @param \WC_Order $order 
  897. * @param array $fee item data 
  898. * @param string $action 'create' to add fee or 'update' to update it 
  899. * @throws WC_API_Exception invalid data, server error 
  900. */ 
  901. protected function set_fee( $order, $fee, $action ) { 
  902.  
  903. if ( 'create' === $action ) { 
  904.  
  905. // fee title is required 
  906. if ( ! isset( $fee['title'] ) ) { 
  907. throw new WC_API_Exception( 'woocommerce_invalid_fee_item', __( 'Fee title is required', 'woocommerce' ), 400 ); 
  908.  
  909. $item = new WC_Order_Item_Fee(); 
  910. $item->set_order_id( $order->get_id() ); 
  911. $item->set_name( wc_clean( $fee['title'] ) ); 
  912. $item->set_total( isset( $fee['total'] ) ? floatval( $fee['total'] ) : 0 ); 
  913.  
  914. // if taxable, tax class and total are required 
  915. if ( ! empty( $fee['taxable'] ) ) { 
  916. if ( ! isset( $fee['tax_class'] ) ) { 
  917. throw new WC_API_Exception( 'woocommerce_invalid_fee_item', __( 'Fee tax class is required when fee is taxable.', 'woocommerce' ), 400 ); 
  918.  
  919. $item->set_tax_status( 'taxable' ); 
  920. $item->set_tax_class( $fee['tax_class'] ); 
  921.  
  922. if ( isset( $fee['total_tax'] ) ) { 
  923. $item->set_total_tax( isset( $fee['total_tax'] ) ? wc_format_refund_total( $fee['total_tax'] ) : 0 ); 
  924.  
  925. if ( isset( $fee['tax_data'] ) ) { 
  926. $item->set_total_tax( wc_format_refund_total( array_sum( $fee['tax_data'] ) ) ); 
  927. $item->set_taxes( array_map( 'wc_format_refund_total', $fee['tax_data'] ) ); 
  928.  
  929. $order->add_item( $item ); 
  930. } else { 
  931.  
  932. $item = new WC_Order_Item_Fee( $fee['id'] ); 
  933.  
  934. if ( isset( $fee['title'] ) ) { 
  935. $item->set_name( wc_clean( $fee['title'] ) ); 
  936.  
  937. if ( isset( $fee['tax_class'] ) ) { 
  938. $item->set_tax_class( $fee['tax_class'] ); 
  939.  
  940. if ( isset( $fee['total'] ) ) { 
  941. $item->set_total( floatval( $fee['total'] ) ); 
  942.  
  943. if ( isset( $fee['total_tax'] ) ) { 
  944. $item->set_total_tax( floatval( $fee['total_tax'] ) ); 
  945.  
  946. $fee_id = $item->save(); 
  947.  
  948. if ( ! $fee_id ) { 
  949. throw new WC_API_Exception( 'woocommerce_cannot_update_fee', __( 'Cannot update fee, try again.', 'woocommerce' ), 500 ); 
  950.  
  951. /** 
  952. * Create or update an order coupon 
  953. * @since 2.2 
  954. * @param \WC_Order $order 
  955. * @param array $coupon item data 
  956. * @param string $action 'create' to add coupon or 'update' to update it 
  957. * @throws WC_API_Exception invalid data, server error 
  958. */ 
  959. protected function set_coupon( $order, $coupon, $action ) { 
  960.  
  961. // coupon amount must be positive float 
  962. if ( isset( $coupon['amount'] ) && floatval( $coupon['amount'] ) < 0 ) { 
  963. throw new WC_API_Exception( 'woocommerce_invalid_coupon_total', __( 'Coupon discount total must be a positive amount.', 'woocommerce' ), 400 ); 
  964.  
  965. if ( 'create' === $action ) { 
  966.  
  967. // coupon code is required 
  968. if ( empty( $coupon['code'] ) ) { 
  969. throw new WC_API_Exception( 'woocommerce_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); 
  970.  
  971. $item = new WC_Order_Item_Coupon(); 
  972. $item->set_props( array( 
  973. 'code' => $coupon['code'],  
  974. 'discount' => isset( $coupon['amount'] ) ? floatval( $coupon['amount'] ) : 0,  
  975. 'discount_tax' => 0,  
  976. 'order_id' => $order->get_id(),  
  977. ) ); 
  978. $order->add_item( $item ); 
  979. } else { 
  980.  
  981. $item = new WC_Order_Item_Coupon( $coupon['id'] ); 
  982.  
  983. if ( isset( $coupon['code'] ) ) { 
  984. $item->set_code( $coupon['code'] ); 
  985.  
  986. if ( isset( $coupon['amount'] ) ) { 
  987. $item->set_discount( floatval( $coupon['amount'] ) ); 
  988.  
  989. $coupon_id = $item->save(); 
  990.  
  991. if ( ! $coupon_id ) { 
  992. throw new WC_API_Exception( 'woocommerce_cannot_update_order_coupon', __( 'Cannot update coupon, try again.', 'woocommerce' ), 500 ); 
  993.  
  994. /** 
  995. * Get the admin order notes for an order 
  996. * @since 2.1 
  997. * @param string $order_id order ID 
  998. * @param string|null $fields fields to include in response 
  999. * @return array 
  1000. */ 
  1001. public function get_order_notes( $order_id, $fields = null ) { 
  1002.  
  1003. // ensure ID is valid order ID 
  1004. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1005.  
  1006. if ( is_wp_error( $order_id ) ) { 
  1007. return $order_id; 
  1008.  
  1009. $args = array( 
  1010. 'post_id' => $order_id,  
  1011. 'approve' => 'approve',  
  1012. 'type' => 'order_note',  
  1013. ); 
  1014.  
  1015. remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); 
  1016.  
  1017. $notes = get_comments( $args ); 
  1018.  
  1019. add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); 
  1020.  
  1021. $order_notes = array(); 
  1022.  
  1023. foreach ( $notes as $note ) { 
  1024.  
  1025. $order_notes[] = current( $this->get_order_note( $order_id, $note->comment_ID, $fields ) ); 
  1026.  
  1027. return array( 'order_notes' => apply_filters( 'woocommerce_api_order_notes_response', $order_notes, $order_id, $fields, $notes, $this->server ) ); 
  1028.  
  1029. /** 
  1030. * Get an order note for the given order ID and ID 
  1031. * @since 2.2 
  1032. * @param string $order_id order ID 
  1033. * @param string $id order note ID 
  1034. * @param string|null $fields fields to limit response to 
  1035. * @return array 
  1036. */ 
  1037. public function get_order_note( $order_id, $id, $fields = null ) { 
  1038. try { 
  1039. // Validate order ID 
  1040. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1041.  
  1042. if ( is_wp_error( $order_id ) ) { 
  1043. return $order_id; 
  1044.  
  1045. $id = absint( $id ); 
  1046.  
  1047. if ( empty( $id ) ) { 
  1048. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 ); 
  1049.  
  1050. $note = get_comment( $id ); 
  1051.  
  1052. if ( is_null( $note ) ) { 
  1053. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1054.  
  1055. $order_note = array( 
  1056. 'id' => $note->comment_ID,  
  1057. 'created_at' => $this->server->format_datetime( $note->comment_date_gmt ),  
  1058. 'note' => $note->comment_content,  
  1059. 'customer_note' => get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ? true : false,  
  1060. ); 
  1061.  
  1062. return array( 'order_note' => apply_filters( 'woocommerce_api_order_note_response', $order_note, $id, $fields, $note, $order_id, $this ) ); 
  1063. } catch ( WC_API_Exception $e ) { 
  1064. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1065.  
  1066. /** 
  1067. * Create a new order note for the given order 
  1068. * @since 2.2 
  1069. * @param string $order_id order ID 
  1070. * @param array $data raw request data 
  1071. * @return WP_Error|array error or created note response data 
  1072. */ 
  1073. public function create_order_note( $order_id, $data ) { 
  1074. try { 
  1075. if ( ! isset( $data['order_note'] ) ) { 
  1076. throw new WC_API_Exception( 'woocommerce_api_missing_order_note_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order_note' ), 400 ); 
  1077.  
  1078. $data = $data['order_note']; 
  1079.  
  1080. // permission check 
  1081. if ( ! current_user_can( 'publish_shop_orders' ) ) { 
  1082. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order_note', __( 'You do not have permission to create order notes', 'woocommerce' ), 401 ); 
  1083.  
  1084. $order_id = $this->validate_request( $order_id, $this->post_type, 'edit' ); 
  1085.  
  1086. if ( is_wp_error( $order_id ) ) { 
  1087. return $order_id; 
  1088.  
  1089. $order = wc_get_order( $order_id ); 
  1090.  
  1091. $data = apply_filters( 'woocommerce_api_create_order_note_data', $data, $order_id, $this ); 
  1092.  
  1093. // note content is required 
  1094. if ( ! isset( $data['note'] ) ) { 
  1095. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note', __( 'Order note is required', 'woocommerce' ), 400 ); 
  1096.  
  1097. $is_customer_note = ( isset( $data['customer_note'] ) && true === $data['customer_note'] ); 
  1098.  
  1099. // create the note 
  1100. $note_id = $order->add_order_note( $data['note'], $is_customer_note ); 
  1101.  
  1102. if ( ! $note_id ) { 
  1103. throw new WC_API_Exception( 'woocommerce_api_cannot_create_order_note', __( 'Cannot create order note, please try again.', 'woocommerce' ), 500 ); 
  1104.  
  1105. // HTTP 201 Created 
  1106. $this->server->send_status( 201 ); 
  1107.  
  1108. do_action( 'woocommerce_api_create_order_note', $note_id, $order_id, $this ); 
  1109.  
  1110. return $this->get_order_note( $order->get_id(), $note_id ); 
  1111. } catch ( WC_Data_Exception $e ) { 
  1112. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1113. } catch ( WC_API_Exception $e ) { 
  1114. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1115.  
  1116. /** 
  1117. * Edit the order note 
  1118. * @since 2.2 
  1119. * @param string $order_id order ID 
  1120. * @param string $id note ID 
  1121. * @param array $data parsed request data 
  1122. * @return WP_Error|array error or edited note response data 
  1123. */ 
  1124. public function edit_order_note( $order_id, $id, $data ) { 
  1125. try { 
  1126. if ( ! isset( $data['order_note'] ) ) { 
  1127. throw new WC_API_Exception( 'woocommerce_api_missing_order_note_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order_note' ), 400 ); 
  1128.  
  1129. $data = $data['order_note']; 
  1130.  
  1131. // Validate order ID 
  1132. $order_id = $this->validate_request( $order_id, $this->post_type, 'edit' ); 
  1133.  
  1134. if ( is_wp_error( $order_id ) ) { 
  1135. return $order_id; 
  1136.  
  1137. $order = wc_get_order( $order_id ); 
  1138.  
  1139. // Validate note ID 
  1140. $id = absint( $id ); 
  1141.  
  1142. if ( empty( $id ) ) { 
  1143. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 ); 
  1144.  
  1145. // Ensure note ID is valid 
  1146. $note = get_comment( $id ); 
  1147.  
  1148. if ( is_null( $note ) ) { 
  1149. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1150.  
  1151. // Ensure note ID is associated with given order 
  1152. if ( $note->comment_post_ID != $order->get_id() ) { 
  1153. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'The order note ID provided is not associated with the order', 'woocommerce' ), 400 ); 
  1154.  
  1155. $data = apply_filters( 'woocommerce_api_edit_order_note_data', $data, $note->comment_ID, $order->get_id(), $this ); 
  1156.  
  1157. // Note content 
  1158. if ( isset( $data['note'] ) ) { 
  1159.  
  1160. wp_update_comment( 
  1161. array( 
  1162. 'comment_ID' => $note->comment_ID,  
  1163. 'comment_content' => $data['note'],  
  1164. ); 
  1165.  
  1166. // Customer note 
  1167. if ( isset( $data['customer_note'] ) ) { 
  1168.  
  1169. update_comment_meta( $note->comment_ID, 'is_customer_note', true === $data['customer_note'] ? 1 : 0 ); 
  1170.  
  1171. do_action( 'woocommerce_api_edit_order_note', $note->comment_ID, $order->get_id(), $this ); 
  1172.  
  1173. return $this->get_order_note( $order->get_id(), $note->comment_ID ); 
  1174. } catch ( WC_Data_Exception $e ) { 
  1175. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1176. } catch ( WC_API_Exception $e ) { 
  1177. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1178.  
  1179. /** 
  1180. * Delete order note 
  1181. * @since 2.2 
  1182. * @param string $order_id order ID 
  1183. * @param string $id note ID 
  1184. * @return WP_Error|array error or deleted message 
  1185. */ 
  1186. public function delete_order_note( $order_id, $id ) { 
  1187. try { 
  1188. $order_id = $this->validate_request( $order_id, $this->post_type, 'delete' ); 
  1189.  
  1190. if ( is_wp_error( $order_id ) ) { 
  1191. return $order_id; 
  1192.  
  1193. // Validate note ID 
  1194. $id = absint( $id ); 
  1195.  
  1196. if ( empty( $id ) ) { 
  1197. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 ); 
  1198.  
  1199. // Ensure note ID is valid 
  1200. $note = get_comment( $id ); 
  1201.  
  1202. if ( is_null( $note ) ) { 
  1203. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1204.  
  1205. // Ensure note ID is associated with given order 
  1206. if ( $note->comment_post_ID != $order_id ) { 
  1207. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'The order note ID provided is not associated with the order', 'woocommerce' ), 400 ); 
  1208.  
  1209. // Force delete since trashed order notes could not be managed through comments list table 
  1210. $result = wp_delete_comment( $note->comment_ID, true ); 
  1211.  
  1212. if ( ! $result ) { 
  1213. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_order_note', __( 'This order note cannot be deleted', 'woocommerce' ), 500 ); 
  1214.  
  1215. do_action( 'woocommerce_api_delete_order_note', $note->comment_ID, $order_id, $this ); 
  1216.  
  1217. return array( 'message' => __( 'Permanently deleted order note', 'woocommerce' ) ); 
  1218. } catch ( WC_API_Exception $e ) { 
  1219. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1220.  
  1221. /** 
  1222. * Get the order refunds for an order 
  1223. * @since 2.2 
  1224. * @param string $order_id order ID 
  1225. * @param string|null $fields fields to include in response 
  1226. * @return array 
  1227. */ 
  1228. public function get_order_refunds( $order_id, $fields = null ) { 
  1229.  
  1230. // Ensure ID is valid order ID 
  1231. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1232.  
  1233. if ( is_wp_error( $order_id ) ) { 
  1234. return $order_id; 
  1235.  
  1236. $refund_items = wc_get_orders( array( 
  1237. 'type' => 'shop_order_refund',  
  1238. 'parent' => $order_id,  
  1239. 'limit' => -1,  
  1240. 'return' => 'ids',  
  1241. ) ); 
  1242. $order_refunds = array(); 
  1243.  
  1244. foreach ( $refund_items as $refund_id ) { 
  1245. $order_refunds[] = current( $this->get_order_refund( $order_id, $refund_id, $fields ) ); 
  1246.  
  1247. return array( 'order_refunds' => apply_filters( 'woocommerce_api_order_refunds_response', $order_refunds, $order_id, $fields, $refund_items, $this ) ); 
  1248.  
  1249. /** 
  1250. * Get an order refund for the given order ID and ID 
  1251. * @since 2.2 
  1252. * @param string $order_id order ID 
  1253. * @param string|null $fields fields to limit response to 
  1254. * @return array 
  1255. */ 
  1256. public function get_order_refund( $order_id, $id, $fields = null ) { 
  1257. try { 
  1258. // Validate order ID 
  1259. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1260.  
  1261. if ( is_wp_error( $order_id ) ) { 
  1262. return $order_id; 
  1263.  
  1264. $id = absint( $id ); 
  1265.  
  1266. if ( empty( $id ) ) { 
  1267. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 400 ); 
  1268.  
  1269. $order = wc_get_order( $order_id ); 
  1270. $refund = wc_get_order( $id ); 
  1271.  
  1272. if ( ! $refund ) { 
  1273. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found.', 'woocommerce' ), 404 ); 
  1274.  
  1275. $line_items = array(); 
  1276.  
  1277. // Add line items 
  1278. foreach ( $refund->get_items( 'line_item' ) as $item_id => $item ) { 
  1279. $product = $item->get_product(); 
  1280. $hideprefix = ( isset( $filter['all_item_meta'] ) && 'true' === $filter['all_item_meta'] ) ? null : '_'; 
  1281. $item_meta = $item->get_formatted_meta_data( $hideprefix ); 
  1282.  
  1283. foreach ( $item_meta as $key => $values ) { 
  1284. $item_meta[ $key ]->label = $values->display_key; 
  1285. unset( $item_meta[ $key ]->display_key ); 
  1286. unset( $item_meta[ $key ]->display_value ); 
  1287.  
  1288. $line_items[] = array( 
  1289. 'id' => $item_id,  
  1290. 'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item ), 2 ),  
  1291. 'subtotal_tax' => wc_format_decimal( $item->get_subtotal_tax(), 2 ),  
  1292. 'total' => wc_format_decimal( $order->get_line_total( $item ), 2 ),  
  1293. 'total_tax' => wc_format_decimal( $order->get_line_tax( $item ), 2 ),  
  1294. 'price' => wc_format_decimal( $order->get_item_total( $item ), 2 ),  
  1295. 'quantity' => $item->get_quantity(),  
  1296. 'tax_class' => $item->get_tax_class(),  
  1297. 'name' => $item->get_name(),  
  1298. 'product_id' => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(),  
  1299. 'sku' => is_object( $product ) ? $product->get_sku() : null,  
  1300. 'meta' => array_values( $item_meta ),  
  1301. 'refunded_item_id' => (int) $item->get_meta( 'refunded_item_id' ),  
  1302. ); 
  1303.  
  1304. $order_refund = array( 
  1305. 'id' => $refund->id,  
  1306. 'created_at' => $this->server->format_datetime( $refund->get_date_created() ? $refund->get_date_created()->getTimestamp() : 0, false, false ),  
  1307. 'amount' => wc_format_decimal( $refund->get_amount(), 2 ),  
  1308. 'reason' => $refund->get_reason(),  
  1309. 'line_items' => $line_items,  
  1310. ); 
  1311.  
  1312. return array( 'order_refund' => apply_filters( 'woocommerce_api_order_refund_response', $order_refund, $id, $fields, $refund, $order_id, $this ) ); 
  1313. } catch ( WC_API_Exception $e ) { 
  1314. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1315.  
  1316. /** 
  1317. * Create a new order refund for the given order 
  1318. * @since 2.2 
  1319. * @param string $order_id order ID 
  1320. * @param array $data raw request data 
  1321. * @param bool $api_refund do refund using a payment gateway API 
  1322. * @return WP_Error|array error or created refund response data 
  1323. */ 
  1324. public function create_order_refund( $order_id, $data, $api_refund = true ) { 
  1325. try { 
  1326. if ( ! isset( $data['order_refund'] ) ) { 
  1327. throw new WC_API_Exception( 'woocommerce_api_missing_order_refund_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order_refund' ), 400 ); 
  1328.  
  1329. $data = $data['order_refund']; 
  1330.  
  1331. // Permission check 
  1332. if ( ! current_user_can( 'publish_shop_orders' ) ) { 
  1333. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order_refund', __( 'You do not have permission to create order refunds', 'woocommerce' ), 401 ); 
  1334.  
  1335. $order_id = absint( $order_id ); 
  1336.  
  1337. if ( empty( $order_id ) ) { 
  1338. throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 ); 
  1339.  
  1340. $data = apply_filters( 'woocommerce_api_create_order_refund_data', $data, $order_id, $this ); 
  1341.  
  1342. // Refund amount is required 
  1343. if ( ! isset( $data['amount'] ) ) { 
  1344. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund', __( 'Refund amount is required.', 'woocommerce' ), 400 ); 
  1345. } elseif ( 0 > $data['amount'] ) { 
  1346. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund', __( 'Refund amount must be positive.', 'woocommerce' ), 400 ); 
  1347.  
  1348. $data['order_id'] = $order_id; 
  1349. $data['refund_id'] = 0; 
  1350.  
  1351. // Create the refund 
  1352. $refund = wc_create_refund( $data ); 
  1353.  
  1354. if ( ! $refund ) { 
  1355. throw new WC_API_Exception( 'woocommerce_api_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 ); 
  1356.  
  1357. // Refund via API 
  1358. if ( $api_refund ) { 
  1359. if ( WC()->payment_gateways() ) { 
  1360. $payment_gateways = WC()->payment_gateways->payment_gateways(); 
  1361.  
  1362. $order = wc_get_order( $order_id ); 
  1363.  
  1364. if ( isset( $payment_gateways[ $order->get_payment_method() ] ) && $payment_gateways[ $order->get_payment_method() ]->supports( 'refunds' ) ) { 
  1365. $result = $payment_gateways[ $order->get_payment_method() ]->process_refund( $order_id, $refund->get_amount(), $refund->get_reason() ); 
  1366.  
  1367. if ( is_wp_error( $result ) ) { 
  1368. return $result; 
  1369. } elseif ( ! $result ) { 
  1370. throw new WC_API_Exception( 'woocommerce_api_create_order_refund_api_failed', __( 'An error occurred while attempting to create the refund using the payment gateway API.', 'woocommerce' ), 500 ); 
  1371.  
  1372. // HTTP 201 Created 
  1373. $this->server->send_status( 201 ); 
  1374.  
  1375. do_action( 'woocommerce_api_create_order_refund', $refund->id, $order_id, $this ); 
  1376.  
  1377. return $this->get_order_refund( $order_id, $refund->id ); 
  1378. } catch ( WC_Data_Exception $e ) { 
  1379. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1380. } catch ( WC_API_Exception $e ) { 
  1381. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1382.  
  1383. /** 
  1384. * Edit an order refund 
  1385. * @since 2.2 
  1386. * @param string $order_id order ID 
  1387. * @param string $id refund ID 
  1388. * @param array $data parsed request data 
  1389. * @return WP_Error|array error or edited refund response data 
  1390. */ 
  1391. public function edit_order_refund( $order_id, $id, $data ) { 
  1392. try { 
  1393. if ( ! isset( $data['order_refund'] ) ) { 
  1394. throw new WC_API_Exception( 'woocommerce_api_missing_order_refund_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order_refund' ), 400 ); 
  1395.  
  1396. $data = $data['order_refund']; 
  1397.  
  1398. // Validate order ID 
  1399. $order_id = $this->validate_request( $order_id, $this->post_type, 'edit' ); 
  1400.  
  1401. if ( is_wp_error( $order_id ) ) { 
  1402. return $order_id; 
  1403.  
  1404. // Validate refund ID 
  1405. $id = absint( $id ); 
  1406.  
  1407. if ( empty( $id ) ) { 
  1408. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 400 ); 
  1409.  
  1410. // Ensure order ID is valid 
  1411. $refund = get_post( $id ); 
  1412.  
  1413. if ( ! $refund ) { 
  1414. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found.', 'woocommerce' ), 404 ); 
  1415.  
  1416. // Ensure refund ID is associated with given order 
  1417. if ( $refund->post_parent != $order_id ) { 
  1418. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'The order refund ID provided is not associated with the order.', 'woocommerce' ), 400 ); 
  1419.  
  1420. $data = apply_filters( 'woocommerce_api_edit_order_refund_data', $data, $refund->ID, $order_id, $this ); 
  1421.  
  1422. // Update reason 
  1423. if ( isset( $data['reason'] ) ) { 
  1424. $updated_refund = wp_update_post( array( 'ID' => $refund->ID, 'post_excerpt' => $data['reason'] ) ); 
  1425.  
  1426. if ( is_wp_error( $updated_refund ) ) { 
  1427. return $updated_refund; 
  1428.  
  1429. // Update refund amount 
  1430. if ( isset( $data['amount'] ) && 0 < $data['amount'] ) { 
  1431. update_post_meta( $refund->ID, '_refund_amount', wc_format_decimal( $data['amount'] ) ); 
  1432.  
  1433. do_action( 'woocommerce_api_edit_order_refund', $refund->ID, $order_id, $this ); 
  1434.  
  1435. return $this->get_order_refund( $order_id, $refund->ID ); 
  1436. } catch ( WC_Data_Exception $e ) { 
  1437. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1438. } catch ( WC_API_Exception $e ) { 
  1439. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1440.  
  1441. /** 
  1442. * Delete order refund 
  1443. * @since 2.2 
  1444. * @param string $order_id order ID 
  1445. * @param string $id refund ID 
  1446. * @return WP_Error|array error or deleted message 
  1447. */ 
  1448. public function delete_order_refund( $order_id, $id ) { 
  1449. try { 
  1450. $order_id = $this->validate_request( $order_id, $this->post_type, 'delete' ); 
  1451.  
  1452. if ( is_wp_error( $order_id ) ) { 
  1453. return $order_id; 
  1454.  
  1455. // Validate refund ID 
  1456. $id = absint( $id ); 
  1457.  
  1458. if ( empty( $id ) ) { 
  1459. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 400 ); 
  1460.  
  1461. // Ensure refund ID is valid 
  1462. $refund = get_post( $id ); 
  1463.  
  1464. if ( ! $refund ) { 
  1465. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found.', 'woocommerce' ), 404 ); 
  1466.  
  1467. // Ensure refund ID is associated with given order 
  1468. if ( $refund->post_parent != $order_id ) { 
  1469. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'The order refund ID provided is not associated with the order.', 'woocommerce' ), 400 ); 
  1470.  
  1471. wc_delete_shop_order_transients( $order_id ); 
  1472.  
  1473. do_action( 'woocommerce_api_delete_order_refund', $refund->ID, $order_id, $this ); 
  1474.  
  1475. return $this->delete( $refund->ID, 'refund', true ); 
  1476. } catch ( WC_API_Exception $e ) { 
  1477. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1478.  
  1479. /** 
  1480. * Bulk update or insert orders 
  1481. * Accepts an array with orders in the formats supported by 
  1482. * WC_API_Orders->create_order() and WC_API_Orders->edit_order() 
  1483. * @since 2.4.0 
  1484. * @param array $data 
  1485. * @return array 
  1486. */ 
  1487. public function bulk( $data ) { 
  1488.  
  1489. try { 
  1490. if ( ! isset( $data['orders'] ) ) { 
  1491. throw new WC_API_Exception( 'woocommerce_api_missing_orders_data', sprintf( __( 'No %1$s data specified to create/edit %1$s', 'woocommerce' ), 'orders' ), 400 ); 
  1492.  
  1493. $data = $data['orders']; 
  1494. $limit = apply_filters( 'woocommerce_api_bulk_limit', 100, 'orders' ); 
  1495.  
  1496. // Limit bulk operation 
  1497. if ( count( $data ) > $limit ) { 
  1498. throw new WC_API_Exception( 'woocommerce_api_orders_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), 413 ); 
  1499.  
  1500. $orders = array(); 
  1501.  
  1502. foreach ( $data as $_order ) { 
  1503. $order_id = 0; 
  1504.  
  1505. // Try to get the order ID 
  1506. if ( isset( $_order['id'] ) ) { 
  1507. $order_id = intval( $_order['id'] ); 
  1508.  
  1509. // Order exists / edit order 
  1510. if ( $order_id ) { 
  1511. $edit = $this->edit_order( $order_id, array( 'order' => $_order ) ); 
  1512.  
  1513. if ( is_wp_error( $edit ) ) { 
  1514. $orders[] = array( 
  1515. 'id' => $order_id,  
  1516. 'error' => array( 'code' => $edit->get_error_code(), 'message' => $edit->get_error_message() ),  
  1517. ); 
  1518. } else { 
  1519. $orders[] = $edit['order']; 
  1520. } else { 
  1521. // Order don't exists / create order 
  1522. $new = $this->create_order( array( 'order' => $_order ) ); 
  1523.  
  1524. if ( is_wp_error( $new ) ) { 
  1525. $orders[] = array( 
  1526. 'id' => $order_id,  
  1527. 'error' => array( 'code' => $new->get_error_code(), 'message' => $new->get_error_message() ),  
  1528. ); 
  1529. } else { 
  1530. $orders[] = $new['order']; 
  1531.  
  1532. return array( 'orders' => apply_filters( 'woocommerce_api_orders_bulk_response', $orders, $this ) ); 
  1533. } catch ( WC_Data_Exception $e ) { 
  1534. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1535. } catch ( WC_API_Exception $e ) { 
  1536. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
/includes/api/legacy/v3/class-wc-api-orders.php  
  1. class WC_API_Orders extends WC_API_Resource { 
  2.  
  3. /** @var string $base the route base */ 
  4. protected $base = '/orders'; 
  5.  
  6. /** @var string $post_type the custom post type */ 
  7. protected $post_type = 'shop_order'; 
  8.  
  9. /** 
  10. * Register the routes for this class 
  11. * GET|POST /orders 
  12. * GET /orders/count 
  13. * GET|PUT|DELETE /orders/<id> 
  14. * GET /orders/<id>/notes 
  15. * @since 2.1 
  16. * @param array $routes 
  17. * @return array 
  18. */ 
  19. public function register_routes( $routes ) { 
  20.  
  21. # GET|POST /orders 
  22. $routes[ $this->base ] = array( 
  23. array( array( $this, 'get_orders' ), WC_API_Server::READABLE ),  
  24. array( array( $this, 'create_order' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  25. ); 
  26.  
  27. # GET /orders/count 
  28. $routes[ $this->base . '/count' ] = array( 
  29. array( array( $this, 'get_orders_count' ), WC_API_Server::READABLE ),  
  30. ); 
  31.  
  32. # GET /orders/statuses 
  33. $routes[ $this->base . '/statuses' ] = array( 
  34. array( array( $this, 'get_order_statuses' ), WC_API_Server::READABLE ),  
  35. ); 
  36.  
  37. # GET|PUT|DELETE /orders/<id> 
  38. $routes[ $this->base . '/(?P<id>\d+)' ] = array( 
  39. array( array( $this, 'get_order' ), WC_API_Server::READABLE ),  
  40. array( array( $this, 'edit_order' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  41. array( array( $this, 'delete_order' ), WC_API_Server::DELETABLE ),  
  42. ); 
  43.  
  44. # GET|POST /orders/<id>/notes 
  45. $routes[ $this->base . '/(?P<order_id>\d+)/notes' ] = array( 
  46. array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ),  
  47. array( array( $this, 'create_order_note' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  48. ); 
  49.  
  50. # GET|PUT|DELETE /orders/<order_id>/notes/<id> 
  51. $routes[ $this->base . '/(?P<order_id>\d+)/notes/(?P<id>\d+)' ] = array( 
  52. array( array( $this, 'get_order_note' ), WC_API_Server::READABLE ),  
  53. array( array( $this, 'edit_order_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  54. array( array( $this, 'delete_order_note' ), WC_API_SERVER::DELETABLE ),  
  55. ); 
  56.  
  57. # GET|POST /orders/<order_id>/refunds 
  58. $routes[ $this->base . '/(?P<order_id>\d+)/refunds' ] = array( 
  59. array( array( $this, 'get_order_refunds' ), WC_API_Server::READABLE ),  
  60. array( array( $this, 'create_order_refund' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  61. ); 
  62.  
  63. # GET|PUT|DELETE /orders/<order_id>/refunds/<id> 
  64. $routes[ $this->base . '/(?P<order_id>\d+)/refunds/(?P<id>\d+)' ] = array( 
  65. array( array( $this, 'get_order_refund' ), WC_API_Server::READABLE ),  
  66. array( array( $this, 'edit_order_refund' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  67. array( array( $this, 'delete_order_refund' ), WC_API_SERVER::DELETABLE ),  
  68. ); 
  69.  
  70. # POST|PUT /orders/bulk 
  71. $routes[ $this->base . '/bulk' ] = array( 
  72. array( array( $this, 'bulk' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  73. ); 
  74.  
  75. return $routes; 
  76.  
  77. /** 
  78. * Get all orders 
  79. * @since 2.1 
  80. * @param string $fields 
  81. * @param array $filter 
  82. * @param string $status 
  83. * @param int $page 
  84. * @return array 
  85. */ 
  86. public function get_orders( $fields = null, $filter = array(), $status = null, $page = 1 ) { 
  87.  
  88. if ( ! empty( $status ) ) { 
  89. $filter['status'] = $status; 
  90.  
  91. $filter['page'] = $page; 
  92.  
  93. $query = $this->query_orders( $filter ); 
  94.  
  95. $orders = array(); 
  96.  
  97. foreach ( $query->posts as $order_id ) { 
  98.  
  99. if ( ! $this->is_readable( $order_id ) ) { 
  100. continue; 
  101.  
  102. $orders[] = current( $this->get_order( $order_id, $fields, $filter ) ); 
  103.  
  104. $this->server->add_pagination_headers( $query ); 
  105.  
  106. return array( 'orders' => $orders ); 
  107.  
  108.  
  109. /** 
  110. * Get the order for the given ID. 
  111. * @since 2.1 
  112. * @param int $id The order ID. 
  113. * @param array $fields Request fields. 
  114. * @param array $filter Request filters. 
  115. * @return array 
  116. */ 
  117. public function get_order( $id, $fields = null, $filter = array() ) { 
  118.  
  119. // Ensure order ID is valid & user has permission to read. 
  120. $id = $this->validate_request( $id, $this->post_type, 'read' ); 
  121.  
  122. if ( is_wp_error( $id ) ) { 
  123. return $id; 
  124.  
  125. // Get the decimal precession. 
  126. $dp = ( isset( $filter['dp'] ) ? intval( $filter['dp'] ) : 2 ); 
  127. $order = wc_get_order( $id ); 
  128. $expand = array(); 
  129.  
  130. if ( ! empty( $filter['expand'] ) ) { 
  131. $expand = explode( ', ', $filter['expand'] ); 
  132.  
  133. $order_data = array( 
  134. 'id' => $order->get_id(),  
  135. 'order_number' => $order->get_order_number(),  
  136. 'order_key' => $order->get_order_key(),  
  137. 'created_at' => $this->server->format_datetime( $order->get_date_created() ? $order->get_date_created()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  138. 'updated_at' => $this->server->format_datetime( $order->get_date_modified() ? $order->get_date_modified()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  139. 'completed_at' => $this->server->format_datetime( $order->get_date_completed() ? $order->get_date_completed()->getTimestamp() : 0, false, false ), // API gives UTC times. 
  140. 'status' => $order->get_status(),  
  141. 'currency' => $order->get_currency(),  
  142. 'total' => wc_format_decimal( $order->get_total(), $dp ),  
  143. 'subtotal' => wc_format_decimal( $order->get_subtotal(), $dp ),  
  144. 'total_line_items_quantity' => $order->get_item_count(),  
  145. 'total_tax' => wc_format_decimal( $order->get_total_tax(), $dp ),  
  146. 'total_shipping' => wc_format_decimal( $order->get_shipping_total(), $dp ),  
  147. 'cart_tax' => wc_format_decimal( $order->get_cart_tax(), $dp ),  
  148. 'shipping_tax' => wc_format_decimal( $order->get_shipping_tax(), $dp ),  
  149. 'total_discount' => wc_format_decimal( $order->get_total_discount(), $dp ),  
  150. 'shipping_methods' => $order->get_shipping_method(),  
  151. 'payment_details' => array( 
  152. 'method_id' => $order->get_payment_method(),  
  153. 'method_title' => $order->get_payment_method_title(),  
  154. 'paid' => ! is_null( $order->get_date_paid() ),  
  155. ),  
  156. 'billing_address' => array( 
  157. 'first_name' => $order->get_billing_first_name(),  
  158. 'last_name' => $order->get_billing_last_name(),  
  159. 'company' => $order->get_billing_company(),  
  160. 'address_1' => $order->get_billing_address_1(),  
  161. 'address_2' => $order->get_billing_address_2(),  
  162. 'city' => $order->get_billing_city(),  
  163. 'state' => $order->get_billing_state(),  
  164. 'postcode' => $order->get_billing_postcode(),  
  165. 'country' => $order->get_billing_country(),  
  166. 'email' => $order->get_billing_email(),  
  167. 'phone' => $order->get_billing_phone(),  
  168. ),  
  169. 'shipping_address' => array( 
  170. 'first_name' => $order->get_shipping_first_name(),  
  171. 'last_name' => $order->get_shipping_last_name(),  
  172. 'company' => $order->get_shipping_company(),  
  173. 'address_1' => $order->get_shipping_address_1(),  
  174. 'address_2' => $order->get_shipping_address_2(),  
  175. 'city' => $order->get_shipping_city(),  
  176. 'state' => $order->get_shipping_state(),  
  177. 'postcode' => $order->get_shipping_postcode(),  
  178. 'country' => $order->get_shipping_country(),  
  179. ),  
  180. 'note' => $order->get_customer_note(),  
  181. 'customer_ip' => $order->get_customer_ip_address(),  
  182. 'customer_user_agent' => $order->get_customer_user_agent(),  
  183. 'customer_id' => $order->get_user_id(),  
  184. 'view_order_url' => $order->get_view_order_url(),  
  185. 'line_items' => array(),  
  186. 'shipping_lines' => array(),  
  187. 'tax_lines' => array(),  
  188. 'fee_lines' => array(),  
  189. 'coupon_lines' => array(),  
  190. ); 
  191.  
  192. // Add line items. 
  193. foreach ( $order->get_items() as $item_id => $item ) { 
  194. $product = $item->get_product(); 
  195. $hideprefix = ( isset( $filter['all_item_meta'] ) && 'true' === $filter['all_item_meta'] ) ? null : '_'; 
  196. $item_meta = $item->get_formatted_meta_data( $hideprefix ); 
  197.  
  198. foreach ( $item_meta as $key => $values ) { 
  199. $item_meta[ $key ]->label = $values->display_key; 
  200. unset( $item_meta[ $key ]->display_key ); 
  201. unset( $item_meta[ $key ]->display_value ); 
  202.  
  203. $line_item = array( 
  204. 'id' => $item_id,  
  205. 'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item, false, false ), $dp ),  
  206. 'subtotal_tax' => wc_format_decimal( $item->get_subtotal_tax(), $dp ),  
  207. 'total' => wc_format_decimal( $order->get_line_total( $item, false, false ), $dp ),  
  208. 'total_tax' => wc_format_decimal( $item->get_total_tax(), $dp ),  
  209. 'price' => wc_format_decimal( $order->get_item_total( $item, false, false ), $dp ),  
  210. 'quantity' => $item->get_quantity(),  
  211. 'tax_class' => $item->get_tax_class(),  
  212. 'name' => $item->get_name(),  
  213. 'product_id' => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(),  
  214. 'sku' => is_object( $product ) ? $product->get_sku() : null,  
  215. 'meta' => array_values( $item_meta ),  
  216. ); 
  217.  
  218. if ( in_array( 'products', $expand ) ) { 
  219. $_product_data = WC()->api->WC_API_Products->get_product( $product_id ); 
  220.  
  221. if ( isset( $_product_data['product'] ) ) { 
  222. $line_item['product_data'] = $_product_data['product']; 
  223.  
  224. $order_data['line_items'][] = $line_item; 
  225.  
  226. // Add shipping. 
  227. foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) { 
  228. $order_data['shipping_lines'][] = array( 
  229. 'id' => $shipping_item_id,  
  230. 'method_id' => $shipping_item->get_method_id(),  
  231. 'method_title' => $shipping_item->get_name(),  
  232. 'total' => wc_format_decimal( $shipping_item->get_total(), $dp ),  
  233. ); 
  234.  
  235. // Add taxes. 
  236. foreach ( $order->get_tax_totals() as $tax_code => $tax ) { 
  237. $tax_line = array( 
  238. 'id' => $tax->id,  
  239. 'rate_id' => $tax->rate_id,  
  240. 'code' => $tax_code,  
  241. 'title' => $tax->label,  
  242. 'total' => wc_format_decimal( $tax->amount, $dp ),  
  243. 'compound' => (bool) $tax->is_compound,  
  244. ); 
  245.  
  246. if ( in_array( 'taxes', $expand ) ) { 
  247. $_rate_data = WC()->api->WC_API_Taxes->get_tax( $tax->rate_id ); 
  248.  
  249. if ( isset( $_rate_data['tax'] ) ) { 
  250. $tax_line['rate_data'] = $_rate_data['tax']; 
  251.  
  252. $order_data['tax_lines'][] = $tax_line; 
  253.  
  254. // Add fees. 
  255. foreach ( $order->get_fees() as $fee_item_id => $fee_item ) { 
  256. $order_data['fee_lines'][] = array( 
  257. 'id' => $fee_item_id,  
  258. 'title' => $fee_item->get_name(),  
  259. 'tax_class' => $fee_item->get_tax_class(),  
  260. 'total' => wc_format_decimal( $order->get_line_total( $fee_item ), $dp ),  
  261. 'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), $dp ),  
  262. ); 
  263.  
  264. // Add coupons. 
  265. foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) { 
  266. $coupon_line = array( 
  267. 'id' => $coupon_item_id,  
  268. 'code' => $coupon_item->get_code(),  
  269. 'amount' => wc_format_decimal( $coupon_item->get_discount(), $dp ),  
  270. ); 
  271.  
  272. if ( in_array( 'coupons', $expand ) ) { 
  273. $_coupon_data = WC()->api->WC_API_Coupons->get_coupon_by_code( $coupon_item->get_code() ); 
  274.  
  275. if ( ! is_wp_error( $_coupon_data ) && isset( $_coupon_data['coupon'] ) ) { 
  276. $coupon_line['coupon_data'] = $_coupon_data['coupon']; 
  277.  
  278. $order_data['coupon_lines'][] = $coupon_line; 
  279.  
  280. return array( 'order' => apply_filters( 'woocommerce_api_order_response', $order_data, $order, $fields, $this->server ) ); 
  281.  
  282. /** 
  283. * Get the total number of orders 
  284. * @since 2.4 
  285. * @param string $status 
  286. * @param array $filter 
  287. * @return array 
  288. */ 
  289. public function get_orders_count( $status = null, $filter = array() ) { 
  290.  
  291. try { 
  292. if ( ! current_user_can( 'read_private_shop_orders' ) ) { 
  293. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_orders_count', __( 'You do not have permission to read the orders count', 'woocommerce' ), 401 ); 
  294.  
  295. if ( ! empty( $status ) ) { 
  296.  
  297. if ( 'any' === $status ) { 
  298.  
  299. $order_statuses = array(); 
  300.  
  301. foreach ( wc_get_order_statuses() as $slug => $name ) { 
  302. $filter['status'] = str_replace( 'wc-', '', $slug ); 
  303. $query = $this->query_orders( $filter ); 
  304. $order_statuses[ str_replace( 'wc-', '', $slug ) ] = (int) $query->found_posts; 
  305.  
  306. return array( 'count' => $order_statuses ); 
  307.  
  308. } else { 
  309. $filter['status'] = $status; 
  310.  
  311. $query = $this->query_orders( $filter ); 
  312.  
  313. return array( 'count' => (int) $query->found_posts ); 
  314.  
  315. } catch ( WC_API_Exception $e ) { 
  316. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  317.  
  318. /** 
  319. * Get a list of valid order statuses 
  320. * Note this requires no specific permissions other than being an authenticated 
  321. * API user. Order statuses (particularly custom statuses) could be considered 
  322. * private information which is why it's not in the API index. 
  323. * @since 2.1 
  324. * @return array 
  325. */ 
  326. public function get_order_statuses() { 
  327.  
  328. $order_statuses = array(); 
  329.  
  330. foreach ( wc_get_order_statuses() as $slug => $name ) { 
  331. $order_statuses[ str_replace( 'wc-', '', $slug ) ] = $name; 
  332.  
  333. return array( 'order_statuses' => apply_filters( 'woocommerce_api_order_statuses_response', $order_statuses, $this ) ); 
  334.  
  335. /** 
  336. * Create an order 
  337. * @since 2.2 
  338. * @param array $data raw order data 
  339. * @return array 
  340. */ 
  341. public function create_order( $data ) { 
  342. global $wpdb; 
  343.  
  344. wc_transaction_query( 'start' ); 
  345.  
  346. try { 
  347. if ( ! isset( $data['order'] ) ) { 
  348. throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order' ), 400 ); 
  349.  
  350. $data = $data['order']; 
  351.  
  352. // permission check 
  353. if ( ! current_user_can( 'publish_shop_orders' ) ) { 
  354. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order', __( 'You do not have permission to create orders', 'woocommerce' ), 401 ); 
  355.  
  356. $data = apply_filters( 'woocommerce_api_create_order_data', $data, $this ); 
  357.  
  358. // default order args, note that status is checked for validity in wc_create_order() 
  359. $default_order_args = array( 
  360. 'status' => isset( $data['status'] ) ? $data['status'] : '',  
  361. 'customer_note' => isset( $data['note'] ) ? $data['note'] : null,  
  362. ); 
  363.  
  364. // if creating order for existing customer 
  365. if ( ! empty( $data['customer_id'] ) ) { 
  366.  
  367. // make sure customer exists 
  368. if ( false === get_user_by( 'id', $data['customer_id'] ) ) { 
  369. throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 ); 
  370.  
  371. $default_order_args['customer_id'] = $data['customer_id']; 
  372.  
  373. // create the pending order 
  374. $order = $this->create_base_order( $default_order_args, $data ); 
  375.  
  376. if ( is_wp_error( $order ) ) { 
  377. throw new WC_API_Exception( 'woocommerce_api_cannot_create_order', sprintf( __( 'Cannot create order: %s', 'woocommerce' ), implode( ', ', $order->get_error_messages() ) ), 400 ); 
  378.  
  379. // billing/shipping addresses 
  380. $this->set_order_addresses( $order, $data ); 
  381.  
  382. $lines = array( 
  383. 'line_item' => 'line_items',  
  384. 'shipping' => 'shipping_lines',  
  385. 'fee' => 'fee_lines',  
  386. 'coupon' => 'coupon_lines',  
  387. ); 
  388.  
  389. foreach ( $lines as $line_type => $line ) { 
  390.  
  391. if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) { 
  392.  
  393. $set_item = "set_{$line_type}"; 
  394.  
  395. foreach ( $data[ $line ] as $item ) { 
  396.  
  397. $this->$set_item( $order, $item, 'create' ); 
  398.  
  399. // set is vat exempt 
  400. if ( isset( $data['is_vat_exempt'] ) ) { 
  401. update_post_meta( $order->get_id(), '_is_vat_exempt', $data['is_vat_exempt'] ? 'yes' : 'no' ); 
  402.  
  403. // calculate totals and set them 
  404. $order->calculate_totals(); 
  405.  
  406. // payment method (and payment_complete() if `paid` == true) 
  407. if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) { 
  408.  
  409. // method ID & title are required 
  410. if ( empty( $data['payment_details']['method_id'] ) || empty( $data['payment_details']['method_title'] ) ) { 
  411. throw new WC_API_Exception( 'woocommerce_invalid_payment_details', __( 'Payment method ID and title are required', 'woocommerce' ), 400 ); 
  412.  
  413. update_post_meta( $order->get_id(), '_payment_method', $data['payment_details']['method_id'] ); 
  414. update_post_meta( $order->get_id(), '_payment_method_title', $data['payment_details']['method_title'] ); 
  415.  
  416. // mark as paid if set 
  417. if ( isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) { 
  418. $order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' ); 
  419.  
  420. // set order currency 
  421. if ( isset( $data['currency'] ) ) { 
  422.  
  423. if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) { 
  424. throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid.', 'woocommerce' ), 400 ); 
  425.  
  426. update_post_meta( $order->get_id(), '_order_currency', $data['currency'] ); 
  427.  
  428. // set order meta 
  429. if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) { 
  430. $this->set_order_meta( $order->get_id(), $data['order_meta'] ); 
  431.  
  432. // HTTP 201 Created 
  433. $this->server->send_status( 201 ); 
  434.  
  435. wc_delete_shop_order_transients( $order ); 
  436.  
  437. do_action( 'woocommerce_api_create_order', $order->get_id(), $data, $this ); 
  438.  
  439. wc_transaction_query( 'commit' ); 
  440.  
  441. return $this->get_order( $order->get_id() ); 
  442.  
  443. } catch ( WC_Data_Exception $e ) { 
  444. wc_transaction_query( 'rollback' ); 
  445. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  446. } catch ( WC_API_Exception $e ) { 
  447. wc_transaction_query( 'rollback' ); 
  448. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  449.  
  450. /** 
  451. * Creates new WC_Order. 
  452. * Requires a separate function for classes that extend WC_API_Orders. 
  453. * @since 2.3 
  454. * @param $args array 
  455. * @return WC_Order 
  456. */ 
  457. protected function create_base_order( $args, $data ) { 
  458. return wc_create_order( $args ); 
  459.  
  460. /** 
  461. * Edit an order 
  462. * @since 2.2 
  463. * @param int $id the order ID 
  464. * @param array $data 
  465. * @return array 
  466. */ 
  467. public function edit_order( $id, $data ) { 
  468. try { 
  469. if ( ! isset( $data['order'] ) ) { 
  470. throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order' ), 400 ); 
  471.  
  472. $data = $data['order']; 
  473.  
  474. $update_totals = false; 
  475.  
  476. $id = $this->validate_request( $id, $this->post_type, 'edit' ); 
  477.  
  478. if ( is_wp_error( $id ) ) { 
  479. return $id; 
  480.  
  481. $data = apply_filters( 'woocommerce_api_edit_order_data', $data, $id, $this ); 
  482. $order = wc_get_order( $id ); 
  483.  
  484. if ( empty( $order ) ) { 
  485. throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 ); 
  486.  
  487. $order_args = array( 'order_id' => $order->get_id() ); 
  488.  
  489. // Customer note. 
  490. if ( isset( $data['note'] ) ) { 
  491. $order_args['customer_note'] = $data['note']; 
  492.  
  493. // Customer ID. 
  494. if ( isset( $data['customer_id'] ) && $data['customer_id'] != $order->get_user_id() ) { 
  495. // Make sure customer exists. 
  496. if ( false === get_user_by( 'id', $data['customer_id'] ) ) { 
  497. throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 ); 
  498.  
  499. update_post_meta( $order->get_id(), '_customer_user', $data['customer_id'] ); 
  500.  
  501. // Billing/shipping address. 
  502. $this->set_order_addresses( $order, $data ); 
  503.  
  504. $lines = array( 
  505. 'line_item' => 'line_items',  
  506. 'shipping' => 'shipping_lines',  
  507. 'fee' => 'fee_lines',  
  508. 'coupon' => 'coupon_lines',  
  509. ); 
  510.  
  511. foreach ( $lines as $line_type => $line ) { 
  512.  
  513. if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) { 
  514.  
  515. $update_totals = true; 
  516.  
  517. foreach ( $data[ $line ] as $item ) { 
  518. // Item ID is always required. 
  519. if ( ! array_key_exists( 'id', $item ) ) { 
  520. $item['id'] = null; 
  521.  
  522. // Create item. 
  523. if ( is_null( $item['id'] ) ) { 
  524. $this->set_item( $order, $line_type, $item, 'create' ); 
  525. } elseif ( $this->item_is_null( $item ) ) { 
  526. // Delete item. 
  527. wc_delete_order_item( $item['id'] ); 
  528. } else { 
  529. // Update item. 
  530. $this->set_item( $order, $line_type, $item, 'update' ); 
  531.  
  532. // Payment method (and payment_complete() if `paid` == true and order needs payment). 
  533. if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) { 
  534.  
  535. // Method ID. 
  536. if ( isset( $data['payment_details']['method_id'] ) ) { 
  537. update_post_meta( $order->get_id(), '_payment_method', $data['payment_details']['method_id'] ); 
  538.  
  539. // Method title. 
  540. if ( isset( $data['payment_details']['method_title'] ) ) { 
  541. update_post_meta( $order->get_id(), '_payment_method_title', $data['payment_details']['method_title'] ); 
  542.  
  543. // Mark as paid if set. 
  544. if ( $order->needs_payment() && isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) { 
  545. $order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' ); 
  546.  
  547. // Set order currency. 
  548. if ( isset( $data['currency'] ) ) { 
  549. if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) { 
  550. throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid.', 'woocommerce' ), 400 ); 
  551.  
  552. update_post_meta( $order->get_id(), '_order_currency', $data['currency'] ); 
  553.  
  554. // If items have changed, recalculate order totals. 
  555. if ( $update_totals ) { 
  556. $order->calculate_totals(); 
  557.  
  558. // Update order meta. 
  559. if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) { 
  560. $this->set_order_meta( $order->get_id(), $data['order_meta'] ); 
  561.  
  562. // Update the order post to set customer note/modified date. 
  563. wc_update_order( $order_args ); 
  564.  
  565. // Order status. 
  566. if ( ! empty( $data['status'] ) ) { 
  567. // Refresh the order instance. 
  568. $order = wc_get_order( $order->get_id() ); 
  569. $order->update_status( $data['status'], isset( $data['status_note'] ) ? $data['status_note'] : '', true ); 
  570.  
  571. wc_delete_shop_order_transients( $order ); 
  572.  
  573. do_action( 'woocommerce_api_edit_order', $order->get_id(), $data, $this ); 
  574.  
  575. return $this->get_order( $id ); 
  576. } catch ( WC_Data_Exception $e ) { 
  577. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  578. } catch ( WC_API_Exception $e ) { 
  579. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  580.  
  581. /** 
  582. * Delete an order 
  583. * @param int $id the order ID 
  584. * @param bool $force true to permanently delete order, false to move to trash 
  585. * @return array 
  586. */ 
  587. public function delete_order( $id, $force = false ) { 
  588.  
  589. $id = $this->validate_request( $id, $this->post_type, 'delete' ); 
  590.  
  591. if ( is_wp_error( $id ) ) { 
  592. return $id; 
  593.  
  594. wc_delete_shop_order_transients( $id ); 
  595.  
  596. do_action( 'woocommerce_api_delete_order', $id, $this ); 
  597.  
  598. return $this->delete( $id, 'order', ( 'true' === $force ) ); 
  599.  
  600. /** 
  601. * Helper method to get order post objects 
  602. * @since 2.1 
  603. * @param array $args request arguments for filtering query 
  604. * @return WP_Query 
  605. */ 
  606. protected function query_orders( $args ) { 
  607.  
  608. // set base query arguments 
  609. $query_args = array( 
  610. 'fields' => 'ids',  
  611. 'post_type' => $this->post_type,  
  612. 'post_status' => array_keys( wc_get_order_statuses() ),  
  613. ); 
  614.  
  615. // add status argument 
  616. if ( ! empty( $args['status'] ) ) { 
  617. $statuses = 'wc-' . str_replace( ', ', ', wc-', $args['status'] ); 
  618. $statuses = explode( ', ', $statuses ); 
  619. $query_args['post_status'] = $statuses; 
  620.  
  621. unset( $args['status'] ); 
  622.  
  623. if ( ! empty( $args['customer_id'] ) ) { 
  624. $query_args['meta_query'] = array( 
  625. array( 
  626. 'key' => '_customer_user',  
  627. 'value' => absint( $args['customer_id'] ),  
  628. 'compare' => '=',  
  629. ),  
  630. ); 
  631.  
  632. $query_args = $this->merge_query_args( $query_args, $args ); 
  633.  
  634. return new WP_Query( $query_args ); 
  635.  
  636. /** 
  637. * Helper method to set/update the billing & shipping addresses for 
  638. * an order 
  639. * @since 2.1 
  640. * @param \WC_Order $order 
  641. * @param array $data 
  642. */ 
  643. protected function set_order_addresses( $order, $data ) { 
  644.  
  645. $address_fields = array( 
  646. 'first_name',  
  647. 'last_name',  
  648. 'company',  
  649. 'email',  
  650. 'phone',  
  651. 'address_1',  
  652. 'address_2',  
  653. 'city',  
  654. 'state',  
  655. 'postcode',  
  656. 'country',  
  657. ); 
  658.  
  659. $billing_address = $shipping_address = array(); 
  660.  
  661. // billing address 
  662. if ( isset( $data['billing_address'] ) && is_array( $data['billing_address'] ) ) { 
  663.  
  664. foreach ( $address_fields as $field ) { 
  665.  
  666. if ( isset( $data['billing_address'][ $field ] ) ) { 
  667. $billing_address[ $field ] = wc_clean( $data['billing_address'][ $field ] ); 
  668.  
  669. unset( $address_fields['email'] ); 
  670. unset( $address_fields['phone'] ); 
  671.  
  672. // shipping address 
  673. if ( isset( $data['shipping_address'] ) && is_array( $data['shipping_address'] ) ) { 
  674.  
  675. foreach ( $address_fields as $field ) { 
  676.  
  677. if ( isset( $data['shipping_address'][ $field ] ) ) { 
  678. $shipping_address[ $field ] = wc_clean( $data['shipping_address'][ $field ] ); 
  679.  
  680. $this->update_address( $order, $billing_address, 'billing' ); 
  681. $this->update_address( $order, $shipping_address, 'shipping' ); 
  682.  
  683. // update user meta 
  684. if ( $order->get_user_id() ) { 
  685. foreach ( $billing_address as $key => $value ) { 
  686. update_user_meta( $order->get_user_id(), 'billing_' . $key, $value ); 
  687. foreach ( $shipping_address as $key => $value ) { 
  688. update_user_meta( $order->get_user_id(), 'shipping_' . $key, $value ); 
  689.  
  690. /** 
  691. * Update address. 
  692. * @param WC_Order $order 
  693. * @param array $posted 
  694. * @param string $type 
  695. */ 
  696. protected function update_address( $order, $posted, $type = 'billing' ) { 
  697. foreach ( $posted as $key => $value ) { 
  698. if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) { 
  699. $order->{"set_{$type}_{$key}"}( $value ); 
  700.  
  701. /** 
  702. * Helper method to add/update order meta, with two restrictions: 
  703. * 1) Only non-protected meta (no leading underscore) can be set 
  704. * 2) Meta values must be scalar (int, string, bool) 
  705. * @since 2.2 
  706. * @param int $order_id valid order ID 
  707. * @param array $order_meta order meta in array( 'meta_key' => 'meta_value' ) format 
  708. */ 
  709. protected function set_order_meta( $order_id, $order_meta ) { 
  710.  
  711. foreach ( $order_meta as $meta_key => $meta_value ) { 
  712.  
  713. if ( is_string( $meta_key ) && ! is_protected_meta( $meta_key ) && is_scalar( $meta_value ) ) { 
  714. update_post_meta( $order_id, $meta_key, $meta_value ); 
  715.  
  716. /** 
  717. * Helper method to check if the resource ID associated with the provided item is null 
  718. * Items can be deleted by setting the resource ID to null 
  719. * @since 2.2 
  720. * @param array $item item provided in the request body 
  721. * @return bool true if the item resource ID is null, false otherwise 
  722. */ 
  723. protected function item_is_null( $item ) { 
  724.  
  725. $keys = array( 'product_id', 'method_id', 'title', 'code' ); 
  726.  
  727. foreach ( $keys as $key ) { 
  728. if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) { 
  729. return true; 
  730.  
  731. return false; 
  732.  
  733. /** 
  734. * Wrapper method to create/update order items 
  735. * When updating, the item ID provided is checked to ensure it is associated 
  736. * with the order. 
  737. * @since 2.2 
  738. * @param \WC_Order $order order 
  739. * @param string $item_type 
  740. * @param array $item item provided in the request body 
  741. * @param string $action either 'create' or 'update' 
  742. * @throws WC_API_Exception if item ID is not associated with order 
  743. */ 
  744. protected function set_item( $order, $item_type, $item, $action ) { 
  745. global $wpdb; 
  746.  
  747. $set_method = "set_{$item_type}"; 
  748.  
  749. // verify provided line item ID is associated with order 
  750. if ( 'update' === $action ) { 
  751.  
  752. $result = $wpdb->get_row( 
  753. $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d",  
  754. absint( $item['id'] ),  
  755. absint( $order->get_id() ) 
  756. ) ); 
  757.  
  758. if ( is_null( $result ) ) { 
  759. throw new WC_API_Exception( 'woocommerce_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 ); 
  760.  
  761. $this->$set_method( $order, $item, $action ); 
  762.  
  763. /** 
  764. * Create or update a line item 
  765. * @since 2.2 
  766. * @param \WC_Order $order 
  767. * @param array $item line item data 
  768. * @param string $action 'create' to add line item or 'update' to update it 
  769. * @throws WC_API_Exception invalid data, server error 
  770. */ 
  771. protected function set_line_item( $order, $item, $action ) { 
  772. $creating = ( 'create' === $action ); 
  773.  
  774. // product is always required 
  775. if ( ! isset( $item['product_id'] ) && ! isset( $item['sku'] ) ) { 
  776. throw new WC_API_Exception( 'woocommerce_api_invalid_product_id', __( 'Product ID or SKU is required', 'woocommerce' ), 400 ); 
  777.  
  778. // when updating, ensure product ID provided matches 
  779. if ( 'update' === $action ) { 
  780.  
  781. $item_product_id = wc_get_order_item_meta( $item['id'], '_product_id' ); 
  782. $item_variation_id = wc_get_order_item_meta( $item['id'], '_variation_id' ); 
  783.  
  784. if ( $item['product_id'] != $item_product_id && $item['product_id'] != $item_variation_id ) { 
  785. throw new WC_API_Exception( 'woocommerce_api_invalid_product_id', __( 'Product ID provided does not match this line item', 'woocommerce' ), 400 ); 
  786.  
  787. if ( isset( $item['product_id'] ) ) { 
  788. $product_id = $item['product_id']; 
  789. } elseif ( isset( $item['sku'] ) ) { 
  790. $product_id = wc_get_product_id_by_sku( $item['sku'] ); 
  791.  
  792. // variations must each have a key & value 
  793. $variation_id = 0; 
  794. if ( isset( $item['variations'] ) && is_array( $item['variations'] ) ) { 
  795. foreach ( $item['variations'] as $key => $value ) { 
  796. if ( ! $key || ! $value ) { 
  797. throw new WC_API_Exception( 'woocommerce_api_invalid_product_variation', __( 'The product variation is invalid', 'woocommerce' ), 400 ); 
  798. $variation_id = $this->get_variation_id( wc_get_product( $product_id ), $item['variations'] ); 
  799.  
  800. $product = wc_get_product( $variation_id ? $variation_id : $product_id ); 
  801.  
  802. // must be a valid WC_Product 
  803. if ( ! is_object( $product ) ) { 
  804. throw new WC_API_Exception( 'woocommerce_api_invalid_product', __( 'Product is invalid.', 'woocommerce' ), 400 ); 
  805.  
  806. // quantity must be positive float 
  807. if ( isset( $item['quantity'] ) && floatval( $item['quantity'] ) <= 0 ) { 
  808. throw new WC_API_Exception( 'woocommerce_api_invalid_product_quantity', __( 'Product quantity must be a positive float.', 'woocommerce' ), 400 ); 
  809.  
  810. // quantity is required when creating 
  811. if ( $creating && ! isset( $item['quantity'] ) ) { 
  812. throw new WC_API_Exception( 'woocommerce_api_invalid_product_quantity', __( 'Product quantity is required.', 'woocommerce' ), 400 ); 
  813.  
  814. // quantity 
  815. if ( $creating ) { 
  816. $line_item = new WC_Order_Item_Product(); 
  817. } else { 
  818. $line_item = new WC_Order_Item_Product( $item['id'] ); 
  819.  
  820. $line_item->set_product( $product ); 
  821. $line_item->set_order_id( $order->get_id() ); 
  822.  
  823. if ( isset( $item['quantity'] ) ) { 
  824. $line_item->set_quantity( $item['quantity'] ); 
  825. if ( isset( $item['total'] ) ) { 
  826. $line_item->set_total( floatval( $item['total'] ) ); 
  827. } elseif ( $creating ) { 
  828. $total = wc_get_price_excluding_tax( $product, array( 'qty' => $line_item->get_quantity() ) ); 
  829. $line_item->set_total( $total ); 
  830. $line_item->set_subtotal( $total ); 
  831. if ( isset( $item['total_tax'] ) ) { 
  832. $line_item->set_total_tax( floatval( $item['total_tax'] ) ); 
  833. if ( isset( $item['subtotal'] ) ) { 
  834. $line_item->set_subtotal( floatval( $item['subtotal'] ) ); 
  835. if ( isset( $item['subtotal_tax'] ) ) { 
  836. $line_item->set_subtotal_tax( floatval( $item['subtotal_tax'] ) ); 
  837. if ( $variation_id ) { 
  838. $line_item->set_variation_id( $variation_id ); 
  839. $line_item->set_variation( $item['variations'] ); 
  840.  
  841. // Save or add to order. 
  842. if ( $creating ) { 
  843. $order->add_item( $line_item ); 
  844. } else { 
  845. $item_id = $line_item->save(); 
  846.  
  847. if ( ! $item_id ) { 
  848. throw new WC_API_Exception( 'woocommerce_cannot_create_line_item', __( 'Cannot create line item, try again.', 'woocommerce' ), 500 ); 
  849.  
  850. /** 
  851. * Given a product ID & API provided variations, find the correct variation ID to use for calculation 
  852. * We can't just trust input from the API to pass a variation_id manually, otherwise you could pass 
  853. * the cheapest variation ID but provide other information so we have to look up the variation ID. 
  854. * @param WC_Product $product Product instance 
  855. * @return int Returns an ID if a valid variation was found for this product 
  856. */ 
  857. public function get_variation_id( $product, $variations = array() ) { 
  858. $variation_id = null; 
  859. $variations_normalized = array(); 
  860.  
  861. if ( $product->is_type( 'variable' ) && $product->has_child() ) { 
  862. if ( isset( $variations ) && is_array( $variations ) ) { 
  863. // start by normalizing the passed variations 
  864. foreach ( $variations as $key => $value ) { 
  865. $key = str_replace( 'attribute_', '', str_replace( 'pa_', '', $key ) ); // from get_attributes in class-wc-api-products.php 
  866. $variations_normalized[ $key ] = strtolower( $value ); 
  867. // now search through each product child and see if our passed variations match anything 
  868. foreach ( $product->get_children() as $variation ) { 
  869. $meta = array(); 
  870. foreach ( get_post_meta( $variation ) as $key => $value ) { 
  871. $value = $value[0]; 
  872. $key = str_replace( 'attribute_', '', str_replace( 'pa_', '', $key ) ); 
  873. $meta[ $key ] = strtolower( $value ); 
  874. // if the variation array is a part of the $meta array, we found our match 
  875. if ( $this->array_contains( $variations_normalized, $meta ) ) { 
  876. $variation_id = $variation; 
  877. break; 
  878.  
  879. return $variation_id; 
  880.  
  881. /** 
  882. * Utility function to see if the meta array contains data from variations 
  883. */ 
  884. protected function array_contains( $needles, $haystack ) { 
  885. foreach ( $needles as $key => $value ) { 
  886. if ( $haystack[ $key ] !== $value ) { 
  887. return false; 
  888. return true; 
  889.  
  890. /** 
  891. * Create or update an order shipping method 
  892. * @since 2.2 
  893. * @param \WC_Order $order 
  894. * @param array $shipping item data 
  895. * @param string $action 'create' to add shipping or 'update' to update it 
  896. * @throws WC_API_Exception invalid data, server error 
  897. */ 
  898. protected function set_shipping( $order, $shipping, $action ) { 
  899.  
  900. // total must be a positive float 
  901. if ( isset( $shipping['total'] ) && floatval( $shipping['total'] ) < 0 ) { 
  902. throw new WC_API_Exception( 'woocommerce_invalid_shipping_total', __( 'Shipping total must be a positive amount.', 'woocommerce' ), 400 ); 
  903.  
  904. if ( 'create' === $action ) { 
  905.  
  906. // method ID is required 
  907. if ( ! isset( $shipping['method_id'] ) ) { 
  908. throw new WC_API_Exception( 'woocommerce_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 ); 
  909.  
  910. $rate = new WC_Shipping_Rate( $shipping['method_id'], isset( $shipping['method_title'] ) ? $shipping['method_title'] : '', isset( $shipping['total'] ) ? floatval( $shipping['total'] ) : 0, array(), $shipping['method_id'] ); 
  911. $item = new WC_Order_Item_Shipping(); 
  912. $item->set_order_id( $order->get_id() ); 
  913. $item->set_shipping_rate( $rate ); 
  914. $order->add_item( $item ); 
  915. } else { 
  916.  
  917. $item = new WC_Order_Item_Shipping( $shipping['id'] ); 
  918.  
  919. if ( isset( $shipping['method_id'] ) ) { 
  920. $item->set_method_id( $shipping['method_id'] ); 
  921.  
  922. if ( isset( $shipping['method_title'] ) ) { 
  923. $item->set_method_title( $shipping['method_title'] ); 
  924.  
  925. if ( isset( $shipping['total'] ) ) { 
  926. $item->set_total( floatval( $shipping['total'] ) ); 
  927.  
  928. $shipping_id = $item->save(); 
  929.  
  930. if ( ! $shipping_id ) { 
  931. throw new WC_API_Exception( 'woocommerce_cannot_update_shipping', __( 'Cannot update shipping method, try again.', 'woocommerce' ), 500 ); 
  932.  
  933. /** 
  934. * Create or update an order fee 
  935. * @since 2.2 
  936. * @param \WC_Order $order 
  937. * @param array $fee item data 
  938. * @param string $action 'create' to add fee or 'update' to update it 
  939. * @throws WC_API_Exception invalid data, server error 
  940. */ 
  941. protected function set_fee( $order, $fee, $action ) { 
  942.  
  943. if ( 'create' === $action ) { 
  944.  
  945. // fee title is required 
  946. if ( ! isset( $fee['title'] ) ) { 
  947. throw new WC_API_Exception( 'woocommerce_invalid_fee_item', __( 'Fee title is required', 'woocommerce' ), 400 ); 
  948.  
  949. $item = new WC_Order_Item_Fee(); 
  950. $item->set_order_id( $order->get_id() ); 
  951. $item->set_name( wc_clean( $fee['title'] ) ); 
  952. $item->set_total( isset( $fee['total'] ) ? floatval( $fee['total'] ) : 0 ); 
  953.  
  954. // if taxable, tax class and total are required 
  955. if ( ! empty( $fee['taxable'] ) ) { 
  956. if ( ! isset( $fee['tax_class'] ) ) { 
  957. throw new WC_API_Exception( 'woocommerce_invalid_fee_item', __( 'Fee tax class is required when fee is taxable.', 'woocommerce' ), 400 ); 
  958.  
  959. $item->set_tax_status( 'taxable' ); 
  960. $item->set_tax_class( $fee['tax_class'] ); 
  961.  
  962. if ( isset( $fee['total_tax'] ) ) { 
  963. $item->set_total_tax( isset( $fee['total_tax'] ) ? wc_format_refund_total( $fee['total_tax'] ) : 0 ); 
  964.  
  965. if ( isset( $fee['tax_data'] ) ) { 
  966. $item->set_total_tax( wc_format_refund_total( array_sum( $fee['tax_data'] ) ) ); 
  967. $item->set_taxes( array_map( 'wc_format_refund_total', $fee['tax_data'] ) ); 
  968.  
  969. $order->add_item( $item ); 
  970. } else { 
  971.  
  972. $item = new WC_Order_Item_Fee( $fee['id'] ); 
  973.  
  974. if ( isset( $fee['title'] ) ) { 
  975. $item->set_name( wc_clean( $fee['title'] ) ); 
  976.  
  977. if ( isset( $fee['tax_class'] ) ) { 
  978. $item->set_tax_class( $fee['tax_class'] ); 
  979.  
  980. if ( isset( $fee['total'] ) ) { 
  981. $item->set_total( floatval( $fee['total'] ) ); 
  982.  
  983. if ( isset( $fee['total_tax'] ) ) { 
  984. $item->set_total_tax( floatval( $fee['total_tax'] ) ); 
  985.  
  986. $fee_id = $item->save(); 
  987.  
  988. if ( ! $fee_id ) { 
  989. throw new WC_API_Exception( 'woocommerce_cannot_update_fee', __( 'Cannot update fee, try again.', 'woocommerce' ), 500 ); 
  990.  
  991. /** 
  992. * Create or update an order coupon 
  993. * @since 2.2 
  994. * @param \WC_Order $order 
  995. * @param array $coupon item data 
  996. * @param string $action 'create' to add coupon or 'update' to update it 
  997. * @throws WC_API_Exception invalid data, server error 
  998. */ 
  999. protected function set_coupon( $order, $coupon, $action ) { 
  1000.  
  1001. // coupon amount must be positive float 
  1002. if ( isset( $coupon['amount'] ) && floatval( $coupon['amount'] ) < 0 ) { 
  1003. throw new WC_API_Exception( 'woocommerce_invalid_coupon_total', __( 'Coupon discount total must be a positive amount.', 'woocommerce' ), 400 ); 
  1004.  
  1005. if ( 'create' === $action ) { 
  1006.  
  1007. // coupon code is required 
  1008. if ( empty( $coupon['code'] ) ) { 
  1009. throw new WC_API_Exception( 'woocommerce_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); 
  1010.  
  1011. $item = new WC_Order_Item_Coupon(); 
  1012. $item->set_props( array( 
  1013. 'code' => $coupon['code'],  
  1014. 'discount' => isset( $coupon['amount'] ) ? floatval( $coupon['amount'] ) : 0,  
  1015. 'discount_tax' => 0,  
  1016. 'order_id' => $order->get_id(),  
  1017. ) ); 
  1018. $order->add_item( $item ); 
  1019. } else { 
  1020.  
  1021. $item = new WC_Order_Item_Coupon( $coupon['id'] ); 
  1022.  
  1023. if ( isset( $coupon['code'] ) ) { 
  1024. $item->set_code( $coupon['code'] ); 
  1025.  
  1026. if ( isset( $coupon['amount'] ) ) { 
  1027. $item->set_discount( floatval( $coupon['amount'] ) ); 
  1028.  
  1029. $coupon_id = $item->save(); 
  1030.  
  1031. if ( ! $coupon_id ) { 
  1032. throw new WC_API_Exception( 'woocommerce_cannot_update_order_coupon', __( 'Cannot update coupon, try again.', 'woocommerce' ), 500 ); 
  1033.  
  1034. /** 
  1035. * Get the admin order notes for an order 
  1036. * @since 2.1 
  1037. * @param string $order_id order ID 
  1038. * @param string|null $fields fields to include in response 
  1039. * @return array 
  1040. */ 
  1041. public function get_order_notes( $order_id, $fields = null ) { 
  1042.  
  1043. // ensure ID is valid order ID 
  1044. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1045.  
  1046. if ( is_wp_error( $order_id ) ) { 
  1047. return $order_id; 
  1048.  
  1049. $args = array( 
  1050. 'post_id' => $order_id,  
  1051. 'approve' => 'approve',  
  1052. 'type' => 'order_note',  
  1053. ); 
  1054.  
  1055. remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); 
  1056.  
  1057. $notes = get_comments( $args ); 
  1058.  
  1059. add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); 
  1060.  
  1061. $order_notes = array(); 
  1062.  
  1063. foreach ( $notes as $note ) { 
  1064.  
  1065. $order_notes[] = current( $this->get_order_note( $order_id, $note->comment_ID, $fields ) ); 
  1066.  
  1067. return array( 'order_notes' => apply_filters( 'woocommerce_api_order_notes_response', $order_notes, $order_id, $fields, $notes, $this->server ) ); 
  1068.  
  1069. /** 
  1070. * Get an order note for the given order ID and ID 
  1071. * @since 2.2 
  1072. * @param string $order_id order ID 
  1073. * @param string $id order note ID 
  1074. * @param string|null $fields fields to limit response to 
  1075. * @return array 
  1076. */ 
  1077. public function get_order_note( $order_id, $id, $fields = null ) { 
  1078. try { 
  1079. // Validate order ID 
  1080. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1081.  
  1082. if ( is_wp_error( $order_id ) ) { 
  1083. return $order_id; 
  1084.  
  1085. $id = absint( $id ); 
  1086.  
  1087. if ( empty( $id ) ) { 
  1088. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 ); 
  1089.  
  1090. $note = get_comment( $id ); 
  1091.  
  1092. if ( is_null( $note ) ) { 
  1093. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1094.  
  1095. $order_note = array( 
  1096. 'id' => $note->comment_ID,  
  1097. 'created_at' => $this->server->format_datetime( $note->comment_date_gmt ),  
  1098. 'note' => $note->comment_content,  
  1099. 'customer_note' => get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ? true : false,  
  1100. ); 
  1101.  
  1102. return array( 'order_note' => apply_filters( 'woocommerce_api_order_note_response', $order_note, $id, $fields, $note, $order_id, $this ) ); 
  1103. } catch ( WC_API_Exception $e ) { 
  1104. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1105.  
  1106. /** 
  1107. * Create a new order note for the given order 
  1108. * @since 2.2 
  1109. * @param string $order_id order ID 
  1110. * @param array $data raw request data 
  1111. * @return WP_Error|array error or created note response data 
  1112. */ 
  1113. public function create_order_note( $order_id, $data ) { 
  1114. try { 
  1115. if ( ! isset( $data['order_note'] ) ) { 
  1116. throw new WC_API_Exception( 'woocommerce_api_missing_order_note_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order_note' ), 400 ); 
  1117.  
  1118. $data = $data['order_note']; 
  1119.  
  1120. // permission check 
  1121. if ( ! current_user_can( 'publish_shop_orders' ) ) { 
  1122. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order_note', __( 'You do not have permission to create order notes', 'woocommerce' ), 401 ); 
  1123.  
  1124. $order_id = $this->validate_request( $order_id, $this->post_type, 'edit' ); 
  1125.  
  1126. if ( is_wp_error( $order_id ) ) { 
  1127. return $order_id; 
  1128.  
  1129. $order = wc_get_order( $order_id ); 
  1130.  
  1131. $data = apply_filters( 'woocommerce_api_create_order_note_data', $data, $order_id, $this ); 
  1132.  
  1133. // note content is required 
  1134. if ( ! isset( $data['note'] ) ) { 
  1135. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note', __( 'Order note is required', 'woocommerce' ), 400 ); 
  1136.  
  1137. $is_customer_note = ( isset( $data['customer_note'] ) && true === $data['customer_note'] ); 
  1138.  
  1139. // create the note 
  1140. $note_id = $order->add_order_note( $data['note'], $is_customer_note ); 
  1141.  
  1142. if ( ! $note_id ) { 
  1143. throw new WC_API_Exception( 'woocommerce_api_cannot_create_order_note', __( 'Cannot create order note, please try again.', 'woocommerce' ), 500 ); 
  1144.  
  1145. // HTTP 201 Created 
  1146. $this->server->send_status( 201 ); 
  1147.  
  1148. do_action( 'woocommerce_api_create_order_note', $note_id, $order_id, $this ); 
  1149.  
  1150. return $this->get_order_note( $order->get_id(), $note_id ); 
  1151. } catch ( WC_Data_Exception $e ) { 
  1152. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1153. } catch ( WC_API_Exception $e ) { 
  1154. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1155.  
  1156. /** 
  1157. * Edit the order note 
  1158. * @since 2.2 
  1159. * @param string $order_id order ID 
  1160. * @param string $id note ID 
  1161. * @param array $data parsed request data 
  1162. * @return WP_Error|array error or edited note response data 
  1163. */ 
  1164. public function edit_order_note( $order_id, $id, $data ) { 
  1165. try { 
  1166. if ( ! isset( $data['order_note'] ) ) { 
  1167. throw new WC_API_Exception( 'woocommerce_api_missing_order_note_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order_note' ), 400 ); 
  1168.  
  1169. $data = $data['order_note']; 
  1170.  
  1171. // Validate order ID 
  1172. $order_id = $this->validate_request( $order_id, $this->post_type, 'edit' ); 
  1173.  
  1174. if ( is_wp_error( $order_id ) ) { 
  1175. return $order_id; 
  1176.  
  1177. $order = wc_get_order( $order_id ); 
  1178.  
  1179. // Validate note ID 
  1180. $id = absint( $id ); 
  1181.  
  1182. if ( empty( $id ) ) { 
  1183. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 ); 
  1184.  
  1185. // Ensure note ID is valid 
  1186. $note = get_comment( $id ); 
  1187.  
  1188. if ( is_null( $note ) ) { 
  1189. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1190.  
  1191. // Ensure note ID is associated with given order 
  1192. if ( $note->comment_post_ID != $order->get_id() ) { 
  1193. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'The order note ID provided is not associated with the order', 'woocommerce' ), 400 ); 
  1194.  
  1195. $data = apply_filters( 'woocommerce_api_edit_order_note_data', $data, $note->comment_ID, $order->get_id(), $this ); 
  1196.  
  1197. // Note content 
  1198. if ( isset( $data['note'] ) ) { 
  1199.  
  1200. wp_update_comment( 
  1201. array( 
  1202. 'comment_ID' => $note->comment_ID,  
  1203. 'comment_content' => $data['note'],  
  1204. ); 
  1205.  
  1206. // Customer note 
  1207. if ( isset( $data['customer_note'] ) ) { 
  1208.  
  1209. update_comment_meta( $note->comment_ID, 'is_customer_note', true === $data['customer_note'] ? 1 : 0 ); 
  1210.  
  1211. do_action( 'woocommerce_api_edit_order_note', $note->comment_ID, $order->get_id(), $this ); 
  1212.  
  1213. return $this->get_order_note( $order->get_id(), $note->comment_ID ); 
  1214. } catch ( WC_Data_Exception $e ) { 
  1215. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1216. } catch ( WC_API_Exception $e ) { 
  1217. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1218.  
  1219. /** 
  1220. * Delete order note 
  1221. * @since 2.2 
  1222. * @param string $order_id order ID 
  1223. * @param string $id note ID 
  1224. * @return WP_Error|array error or deleted message 
  1225. */ 
  1226. public function delete_order_note( $order_id, $id ) { 
  1227. try { 
  1228. $order_id = $this->validate_request( $order_id, $this->post_type, 'delete' ); 
  1229.  
  1230. if ( is_wp_error( $order_id ) ) { 
  1231. return $order_id; 
  1232.  
  1233. // Validate note ID 
  1234. $id = absint( $id ); 
  1235.  
  1236. if ( empty( $id ) ) { 
  1237. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 ); 
  1238.  
  1239. // Ensure note ID is valid 
  1240. $note = get_comment( $id ); 
  1241.  
  1242. if ( is_null( $note ) ) { 
  1243. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1244.  
  1245. // Ensure note ID is associated with given order 
  1246. if ( $note->comment_post_ID != $order_id ) { 
  1247. throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'The order note ID provided is not associated with the order', 'woocommerce' ), 400 ); 
  1248.  
  1249. // Force delete since trashed order notes could not be managed through comments list table 
  1250. $result = wp_delete_comment( $note->comment_ID, true ); 
  1251.  
  1252. if ( ! $result ) { 
  1253. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_order_note', __( 'This order note cannot be deleted', 'woocommerce' ), 500 ); 
  1254.  
  1255. do_action( 'woocommerce_api_delete_order_note', $note->comment_ID, $order_id, $this ); 
  1256.  
  1257. return array( 'message' => __( 'Permanently deleted order note', 'woocommerce' ) ); 
  1258. } catch ( WC_API_Exception $e ) { 
  1259. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1260.  
  1261. /** 
  1262. * Get the order refunds for an order 
  1263. * @since 2.2 
  1264. * @param string $order_id order ID 
  1265. * @param string|null $fields fields to include in response 
  1266. * @return array 
  1267. */ 
  1268. public function get_order_refunds( $order_id, $fields = null ) { 
  1269.  
  1270. // Ensure ID is valid order ID 
  1271. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1272.  
  1273. if ( is_wp_error( $order_id ) ) { 
  1274. return $order_id; 
  1275.  
  1276. $refund_items = wc_get_orders( array( 
  1277. 'type' => 'shop_order_refund',  
  1278. 'parent' => $order_id,  
  1279. 'limit' => -1,  
  1280. 'return' => 'ids',  
  1281. ) ); 
  1282. $order_refunds = array(); 
  1283.  
  1284. foreach ( $refund_items as $refund_id ) { 
  1285. $order_refunds[] = current( $this->get_order_refund( $order_id, $refund_id, $fields ) ); 
  1286.  
  1287. return array( 'order_refunds' => apply_filters( 'woocommerce_api_order_refunds_response', $order_refunds, $order_id, $fields, $refund_items, $this ) ); 
  1288.  
  1289. /** 
  1290. * Get an order refund for the given order ID and ID 
  1291. * @since 2.2 
  1292. * @param string $order_id order ID 
  1293. * @param string|null $fields fields to limit response to 
  1294. * @return array 
  1295. */ 
  1296. public function get_order_refund( $order_id, $id, $fields = null ) { 
  1297. try { 
  1298. // Validate order ID 
  1299. $order_id = $this->validate_request( $order_id, $this->post_type, 'read' ); 
  1300.  
  1301. if ( is_wp_error( $order_id ) ) { 
  1302. return $order_id; 
  1303.  
  1304. $id = absint( $id ); 
  1305.  
  1306. if ( empty( $id ) ) { 
  1307. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 400 ); 
  1308.  
  1309. $order = wc_get_order( $order_id ); 
  1310. $refund = wc_get_order( $id ); 
  1311.  
  1312. if ( ! $refund ) { 
  1313. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found.', 'woocommerce' ), 404 ); 
  1314.  
  1315. $line_items = array(); 
  1316.  
  1317. // Add line items 
  1318. foreach ( $refund->get_items( 'line_item' ) as $item_id => $item ) { 
  1319. $product = $item->get_product(); 
  1320. $hideprefix = ( isset( $filter['all_item_meta'] ) && 'true' === $filter['all_item_meta'] ) ? null : '_'; 
  1321. $item_meta = $item->get_formatted_meta_data( $hideprefix ); 
  1322.  
  1323. foreach ( $item_meta as $key => $values ) { 
  1324. $item_meta[ $key ]->label = $values->display_key; 
  1325. unset( $item_meta[ $key ]->display_key ); 
  1326. unset( $item_meta[ $key ]->display_value ); 
  1327.  
  1328. $line_items[] = array( 
  1329. 'id' => $item_id,  
  1330. 'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item ), 2 ),  
  1331. 'subtotal_tax' => wc_format_decimal( $item->get_subtotal_tax(), 2 ),  
  1332. 'total' => wc_format_decimal( $order->get_line_total( $item ), 2 ),  
  1333. 'total_tax' => wc_format_decimal( $order->get_line_tax( $item ), 2 ),  
  1334. 'price' => wc_format_decimal( $order->get_item_total( $item ), 2 ),  
  1335. 'quantity' => $item->get_quantity(),  
  1336. 'tax_class' => $item->get_tax_class(),  
  1337. 'name' => $item->get_name(),  
  1338. 'product_id' => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(),  
  1339. 'sku' => is_object( $product ) ? $product->get_sku() : null,  
  1340. 'meta' => array_values( $item_meta ),  
  1341. 'refunded_item_id' => (int) $item->get_meta( 'refunded_item_id' ),  
  1342. ); 
  1343.  
  1344. $order_refund = array( 
  1345. 'id' => $refund->id,  
  1346. 'created_at' => $this->server->format_datetime( $refund->get_date_created() ? $refund->get_date_created()->getTimestamp() : 0, false, false ),  
  1347. 'amount' => wc_format_decimal( $refund->get_amount(), 2 ),  
  1348. 'reason' => $refund->get_reason(),  
  1349. 'line_items' => $line_items,  
  1350. ); 
  1351.  
  1352. return array( 'order_refund' => apply_filters( 'woocommerce_api_order_refund_response', $order_refund, $id, $fields, $refund, $order_id, $this ) ); 
  1353. } catch ( WC_API_Exception $e ) { 
  1354. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1355.  
  1356. /** 
  1357. * Create a new order refund for the given order 
  1358. * @since 2.2 
  1359. * @param string $order_id order ID 
  1360. * @param array $data raw request data 
  1361. * @param bool $api_refund do refund using a payment gateway API 
  1362. * @return WP_Error|array error or created refund response data 
  1363. */ 
  1364. public function create_order_refund( $order_id, $data, $api_refund = true ) { 
  1365. try { 
  1366. if ( ! isset( $data['order_refund'] ) ) { 
  1367. throw new WC_API_Exception( 'woocommerce_api_missing_order_refund_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order_refund' ), 400 ); 
  1368.  
  1369. $data = $data['order_refund']; 
  1370.  
  1371. // Permission check 
  1372. if ( ! current_user_can( 'publish_shop_orders' ) ) { 
  1373. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order_refund', __( 'You do not have permission to create order refunds', 'woocommerce' ), 401 ); 
  1374.  
  1375. $order_id = absint( $order_id ); 
  1376.  
  1377. if ( empty( $order_id ) ) { 
  1378. throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 ); 
  1379.  
  1380. $data = apply_filters( 'woocommerce_api_create_order_refund_data', $data, $order_id, $this ); 
  1381.  
  1382. // Refund amount is required 
  1383. if ( ! isset( $data['amount'] ) ) { 
  1384. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund', __( 'Refund amount is required.', 'woocommerce' ), 400 ); 
  1385. } elseif ( 0 > $data['amount'] ) { 
  1386. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund', __( 'Refund amount must be positive.', 'woocommerce' ), 400 ); 
  1387.  
  1388. $data['order_id'] = $order_id; 
  1389. $data['refund_id'] = 0; 
  1390.  
  1391. // Create the refund 
  1392. $refund = wc_create_refund( $data ); 
  1393.  
  1394. if ( ! $refund ) { 
  1395. throw new WC_API_Exception( 'woocommerce_api_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 ); 
  1396.  
  1397. // Refund via API 
  1398. if ( $api_refund ) { 
  1399. if ( WC()->payment_gateways() ) { 
  1400. $payment_gateways = WC()->payment_gateways->payment_gateways(); 
  1401.  
  1402. $order = wc_get_order( $order_id ); 
  1403.  
  1404. if ( isset( $payment_gateways[ $order->get_payment_method() ] ) && $payment_gateways[ $order->get_payment_method() ]->supports( 'refunds' ) ) { 
  1405. $result = $payment_gateways[ $order->get_payment_method() ]->process_refund( $order_id, $refund->get_amount(), $refund->get_reason() ); 
  1406.  
  1407. if ( is_wp_error( $result ) ) { 
  1408. return $result; 
  1409. } elseif ( ! $result ) { 
  1410. throw new WC_API_Exception( 'woocommerce_api_create_order_refund_api_failed', __( 'An error occurred while attempting to create the refund using the payment gateway API.', 'woocommerce' ), 500 ); 
  1411.  
  1412. // HTTP 201 Created 
  1413. $this->server->send_status( 201 ); 
  1414.  
  1415. do_action( 'woocommerce_api_create_order_refund', $refund->id, $order_id, $this ); 
  1416.  
  1417. return $this->get_order_refund( $order_id, $refund->id ); 
  1418. } catch ( WC_Data_Exception $e ) { 
  1419. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1420. } catch ( WC_API_Exception $e ) { 
  1421. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1422.  
  1423. /** 
  1424. * Edit an order refund 
  1425. * @since 2.2 
  1426. * @param string $order_id order ID 
  1427. * @param string $id refund ID 
  1428. * @param array $data parsed request data 
  1429. * @return WP_Error|array error or edited refund response data 
  1430. */ 
  1431. public function edit_order_refund( $order_id, $id, $data ) { 
  1432. try { 
  1433. if ( ! isset( $data['order_refund'] ) ) { 
  1434. throw new WC_API_Exception( 'woocommerce_api_missing_order_refund_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order_refund' ), 400 ); 
  1435.  
  1436. $data = $data['order_refund']; 
  1437.  
  1438. // Validate order ID 
  1439. $order_id = $this->validate_request( $order_id, $this->post_type, 'edit' ); 
  1440.  
  1441. if ( is_wp_error( $order_id ) ) { 
  1442. return $order_id; 
  1443.  
  1444. // Validate refund ID 
  1445. $id = absint( $id ); 
  1446.  
  1447. if ( empty( $id ) ) { 
  1448. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 400 ); 
  1449.  
  1450. // Ensure order ID is valid 
  1451. $refund = get_post( $id ); 
  1452.  
  1453. if ( ! $refund ) { 
  1454. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found.', 'woocommerce' ), 404 ); 
  1455.  
  1456. // Ensure refund ID is associated with given order 
  1457. if ( $refund->post_parent != $order_id ) { 
  1458. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'The order refund ID provided is not associated with the order.', 'woocommerce' ), 400 ); 
  1459.  
  1460. $data = apply_filters( 'woocommerce_api_edit_order_refund_data', $data, $refund->ID, $order_id, $this ); 
  1461.  
  1462. // Update reason 
  1463. if ( isset( $data['reason'] ) ) { 
  1464. $updated_refund = wp_update_post( array( 'ID' => $refund->ID, 'post_excerpt' => $data['reason'] ) ); 
  1465.  
  1466. if ( is_wp_error( $updated_refund ) ) { 
  1467. return $updated_refund; 
  1468.  
  1469. // Update refund amount 
  1470. if ( isset( $data['amount'] ) && 0 < $data['amount'] ) { 
  1471. update_post_meta( $refund->ID, '_refund_amount', wc_format_decimal( $data['amount'] ) ); 
  1472.  
  1473. do_action( 'woocommerce_api_edit_order_refund', $refund->ID, $order_id, $this ); 
  1474.  
  1475. return $this->get_order_refund( $order_id, $refund->ID ); 
  1476. } catch ( WC_Data_Exception $e ) { 
  1477. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1478. } catch ( WC_API_Exception $e ) { 
  1479. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1480.  
  1481. /** 
  1482. * Delete order refund 
  1483. * @since 2.2 
  1484. * @param string $order_id order ID 
  1485. * @param string $id refund ID 
  1486. * @return WP_Error|array error or deleted message 
  1487. */ 
  1488. public function delete_order_refund( $order_id, $id ) { 
  1489. try { 
  1490. $order_id = $this->validate_request( $order_id, $this->post_type, 'delete' ); 
  1491.  
  1492. if ( is_wp_error( $order_id ) ) { 
  1493. return $order_id; 
  1494.  
  1495. // Validate refund ID 
  1496. $id = absint( $id ); 
  1497.  
  1498. if ( empty( $id ) ) { 
  1499. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 400 ); 
  1500.  
  1501. // Ensure refund ID is valid 
  1502. $refund = get_post( $id ); 
  1503.  
  1504. if ( ! $refund ) { 
  1505. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found.', 'woocommerce' ), 404 ); 
  1506.  
  1507. // Ensure refund ID is associated with given order 
  1508. if ( $refund->post_parent != $order_id ) { 
  1509. throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'The order refund ID provided is not associated with the order.', 'woocommerce' ), 400 ); 
  1510.  
  1511. wc_delete_shop_order_transients( $order_id ); 
  1512.  
  1513. do_action( 'woocommerce_api_delete_order_refund', $refund->ID, $order_id, $this ); 
  1514.  
  1515. return $this->delete( $refund->ID, 'refund', true ); 
  1516. } catch ( WC_API_Exception $e ) { 
  1517. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1518.  
  1519. /** 
  1520. * Bulk update or insert orders 
  1521. * Accepts an array with orders in the formats supported by 
  1522. * WC_API_Orders->create_order() and WC_API_Orders->edit_order() 
  1523. * @since 2.4.0 
  1524. * @param array $data 
  1525. * @return array 
  1526. */ 
  1527. public function bulk( $data ) { 
  1528.  
  1529. try { 
  1530. if ( ! isset( $data['orders'] ) ) { 
  1531. throw new WC_API_Exception( 'woocommerce_api_missing_orders_data', sprintf( __( 'No %1$s data specified to create/edit %1$s', 'woocommerce' ), 'orders' ), 400 ); 
  1532.  
  1533. $data = $data['orders']; 
  1534. $limit = apply_filters( 'woocommerce_api_bulk_limit', 100, 'orders' ); 
  1535.  
  1536. // Limit bulk operation 
  1537. if ( count( $data ) > $limit ) { 
  1538. throw new WC_API_Exception( 'woocommerce_api_orders_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), 413 ); 
  1539.  
  1540. $orders = array(); 
  1541.  
  1542. foreach ( $data as $_order ) { 
  1543. $order_id = 0; 
  1544.  
  1545. // Try to get the order ID 
  1546. if ( isset( $_order['id'] ) ) { 
  1547. $order_id = intval( $_order['id'] ); 
  1548.  
  1549. if ( $order_id ) { 
  1550.  
  1551. // Order exists / edit order 
  1552. $edit = $this->edit_order( $order_id, array( 'order' => $_order ) ); 
  1553.  
  1554. if ( is_wp_error( $edit ) ) { 
  1555. $orders[] = array( 
  1556. 'id' => $order_id,  
  1557. 'error' => array( 'code' => $edit->get_error_code(), 'message' => $edit->get_error_message() ),  
  1558. ); 
  1559. } else { 
  1560. $orders[] = $edit['order']; 
  1561. } else { 
  1562.  
  1563. // Order don't exists / create order 
  1564. $new = $this->create_order( array( 'order' => $_order ) ); 
  1565.  
  1566. if ( is_wp_error( $new ) ) { 
  1567. $orders[] = array( 
  1568. 'id' => $order_id,  
  1569. 'error' => array( 'code' => $new->get_error_code(), 'message' => $new->get_error_message() ),  
  1570. ); 
  1571. } else { 
  1572. $orders[] = $new['order']; 
  1573.  
  1574. return array( 'orders' => apply_filters( 'woocommerce_api_orders_bulk_response', $orders, $this ) ); 
  1575. } catch ( WC_Data_Exception $e ) { 
  1576. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) ); 
  1577. } catch ( WC_API_Exception $e ) { 
  1578. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );