class-wc-rest-orders-controller.php 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687
  1. <?php
  2. /**
  3. * REST API Orders controller
  4. *
  5. * Handles requests to the /orders endpoint.
  6. *
  7. * @package WooCommerce/API
  8. * @since 2.6.0
  9. */
  10. defined( 'ABSPATH' ) || exit;
  11. /**
  12. * REST API Orders controller class.
  13. *
  14. * @package WooCommerce/API
  15. * @extends WC_REST_CRUD_Controller
  16. */
  17. class WC_REST_Orders_Controller extends WC_REST_Legacy_Orders_Controller {
  18. /**
  19. * Endpoint namespace.
  20. *
  21. * @var string
  22. */
  23. protected $namespace = 'wc/v2';
  24. /**
  25. * Route base.
  26. *
  27. * @var string
  28. */
  29. protected $rest_base = 'orders';
  30. /**
  31. * Post type.
  32. *
  33. * @var string
  34. */
  35. protected $post_type = 'shop_order';
  36. /**
  37. * If object is hierarchical.
  38. *
  39. * @var bool
  40. */
  41. protected $hierarchical = true;
  42. /**
  43. * Stores the request.
  44. *
  45. * @var array
  46. */
  47. protected $request = array();
  48. /**
  49. * Register the routes for orders.
  50. */
  51. public function register_routes() {
  52. register_rest_route(
  53. $this->namespace, '/' . $this->rest_base, array(
  54. array(
  55. 'methods' => WP_REST_Server::READABLE,
  56. 'callback' => array( $this, 'get_items' ),
  57. 'permission_callback' => array( $this, 'get_items_permissions_check' ),
  58. 'args' => $this->get_collection_params(),
  59. ),
  60. array(
  61. 'methods' => WP_REST_Server::CREATABLE,
  62. 'callback' => array( $this, 'create_item' ),
  63. 'permission_callback' => array( $this, 'create_item_permissions_check' ),
  64. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
  65. ),
  66. 'schema' => array( $this, 'get_public_item_schema' ),
  67. )
  68. );
  69. register_rest_route(
  70. $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
  71. 'args' => array(
  72. 'id' => array(
  73. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
  74. 'type' => 'integer',
  75. ),
  76. ),
  77. array(
  78. 'methods' => WP_REST_Server::READABLE,
  79. 'callback' => array( $this, 'get_item' ),
  80. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  81. 'args' => array(
  82. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  83. ),
  84. ),
  85. array(
  86. 'methods' => WP_REST_Server::EDITABLE,
  87. 'callback' => array( $this, 'update_item' ),
  88. 'permission_callback' => array( $this, 'update_item_permissions_check' ),
  89. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  90. ),
  91. array(
  92. 'methods' => WP_REST_Server::DELETABLE,
  93. 'callback' => array( $this, 'delete_item' ),
  94. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  95. 'args' => array(
  96. 'force' => array(
  97. 'default' => false,
  98. 'type' => 'boolean',
  99. 'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
  100. ),
  101. ),
  102. ),
  103. 'schema' => array( $this, 'get_public_item_schema' ),
  104. )
  105. );
  106. register_rest_route(
  107. $this->namespace, '/' . $this->rest_base . '/batch', array(
  108. array(
  109. 'methods' => WP_REST_Server::EDITABLE,
  110. 'callback' => array( $this, 'batch_items' ),
  111. 'permission_callback' => array( $this, 'batch_items_permissions_check' ),
  112. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  113. ),
  114. 'schema' => array( $this, 'get_public_batch_schema' ),
  115. )
  116. );
  117. }
  118. /**
  119. * Get object.
  120. *
  121. * @since 3.0.0
  122. * @param int $id Object ID.
  123. * @return WC_Data
  124. */
  125. protected function get_object( $id ) {
  126. return wc_get_order( $id );
  127. }
  128. /**
  129. * Expands an order item to get its data.
  130. *
  131. * @param WC_Order_item $item Order item data.
  132. * @return array
  133. */
  134. protected function get_order_item_data( $item ) {
  135. $data = $item->get_data();
  136. $format_decimal = array( 'subtotal', 'subtotal_tax', 'total', 'total_tax', 'tax_total', 'shipping_tax_total' );
  137. // Format decimal values.
  138. foreach ( $format_decimal as $key ) {
  139. if ( isset( $data[ $key ] ) ) {
  140. $data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
  141. }
  142. }
  143. // Add SKU and PRICE to products.
  144. if ( is_callable( array( $item, 'get_product' ) ) ) {
  145. $data['sku'] = $item->get_product() ? $item->get_product()->get_sku() : null;
  146. $data['price'] = $item->get_quantity() ? $item->get_total() / $item->get_quantity() : 0;
  147. }
  148. // Format taxes.
  149. if ( ! empty( $data['taxes']['total'] ) ) {
  150. $taxes = array();
  151. foreach ( $data['taxes']['total'] as $tax_rate_id => $tax ) {
  152. $taxes[] = array(
  153. 'id' => $tax_rate_id,
  154. 'total' => $tax,
  155. 'subtotal' => isset( $data['taxes']['subtotal'][ $tax_rate_id ] ) ? $data['taxes']['subtotal'][ $tax_rate_id ] : '',
  156. );
  157. }
  158. $data['taxes'] = $taxes;
  159. } elseif ( isset( $data['taxes'] ) ) {
  160. $data['taxes'] = array();
  161. }
  162. // Remove names for coupons, taxes and shipping.
  163. if ( isset( $data['code'] ) || isset( $data['rate_code'] ) || isset( $data['method_title'] ) ) {
  164. unset( $data['name'] );
  165. }
  166. // Remove props we don't want to expose.
  167. unset( $data['order_id'] );
  168. unset( $data['type'] );
  169. return $data;
  170. }
  171. /**
  172. * Get formatted item data.
  173. *
  174. * @since 3.0.0
  175. * @param WC_Data $object WC_Data instance.
  176. * @return array
  177. */
  178. protected function get_formatted_item_data( $object ) {
  179. $data = $object->get_data();
  180. $format_decimal = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' );
  181. $format_date = array( 'date_created', 'date_modified', 'date_completed', 'date_paid' );
  182. $format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' );
  183. // Format decimal values.
  184. foreach ( $format_decimal as $key ) {
  185. $data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
  186. }
  187. // Format date values.
  188. foreach ( $format_date as $key ) {
  189. $datetime = $data[ $key ];
  190. $data[ $key ] = wc_rest_prepare_date_response( $datetime, false );
  191. $data[ $key . '_gmt' ] = wc_rest_prepare_date_response( $datetime );
  192. }
  193. // Format the order status.
  194. $data['status'] = 'wc-' === substr( $data['status'], 0, 3 ) ? substr( $data['status'], 3 ) : $data['status'];
  195. // Format line items.
  196. foreach ( $format_line_items as $key ) {
  197. $data[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $data[ $key ] ) );
  198. }
  199. // Refunds.
  200. $data['refunds'] = array();
  201. foreach ( $object->get_refunds() as $refund ) {
  202. $data['refunds'][] = array(
  203. 'id' => $refund->get_id(),
  204. 'reason' => $refund->get_reason() ? $refund->get_reason() : '',
  205. 'total' => '-' . wc_format_decimal( $refund->get_amount(), $this->request['dp'] ),
  206. );
  207. }
  208. return array(
  209. 'id' => $object->get_id(),
  210. 'parent_id' => $data['parent_id'],
  211. 'number' => $data['number'],
  212. 'order_key' => $data['order_key'],
  213. 'created_via' => $data['created_via'],
  214. 'version' => $data['version'],
  215. 'status' => $data['status'],
  216. 'currency' => $data['currency'],
  217. 'date_created' => $data['date_created'],
  218. 'date_created_gmt' => $data['date_created_gmt'],
  219. 'date_modified' => $data['date_modified'],
  220. 'date_modified_gmt' => $data['date_modified_gmt'],
  221. 'discount_total' => $data['discount_total'],
  222. 'discount_tax' => $data['discount_tax'],
  223. 'shipping_total' => $data['shipping_total'],
  224. 'shipping_tax' => $data['shipping_tax'],
  225. 'cart_tax' => $data['cart_tax'],
  226. 'total' => $data['total'],
  227. 'total_tax' => $data['total_tax'],
  228. 'prices_include_tax' => $data['prices_include_tax'],
  229. 'customer_id' => $data['customer_id'],
  230. 'customer_ip_address' => $data['customer_ip_address'],
  231. 'customer_user_agent' => $data['customer_user_agent'],
  232. 'customer_note' => $data['customer_note'],
  233. 'billing' => $data['billing'],
  234. 'shipping' => $data['shipping'],
  235. 'payment_method' => $data['payment_method'],
  236. 'payment_method_title' => $data['payment_method_title'],
  237. 'transaction_id' => $data['transaction_id'],
  238. 'date_paid' => $data['date_paid'],
  239. 'date_paid_gmt' => $data['date_paid_gmt'],
  240. 'date_completed' => $data['date_completed'],
  241. 'date_completed_gmt' => $data['date_completed_gmt'],
  242. 'cart_hash' => $data['cart_hash'],
  243. 'meta_data' => $data['meta_data'],
  244. 'line_items' => $data['line_items'],
  245. 'tax_lines' => $data['tax_lines'],
  246. 'shipping_lines' => $data['shipping_lines'],
  247. 'fee_lines' => $data['fee_lines'],
  248. 'coupon_lines' => $data['coupon_lines'],
  249. 'refunds' => $data['refunds'],
  250. );
  251. }
  252. /**
  253. * Prepare a single order output for response.
  254. *
  255. * @since 3.0.0
  256. * @param WC_Data $object Object data.
  257. * @param WP_REST_Request $request Request object.
  258. * @return WP_REST_Response
  259. */
  260. public function prepare_object_for_response( $object, $request ) {
  261. $this->request = $request;
  262. $this->request['dp'] = is_null( $this->request['dp'] ) ? wc_get_price_decimals() : absint( $this->request['dp'] );
  263. $data = $this->get_formatted_item_data( $object );
  264. $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
  265. $data = $this->add_additional_fields_to_object( $data, $request );
  266. $data = $this->filter_response_by_context( $data, $context );
  267. $response = rest_ensure_response( $data );
  268. $response->add_links( $this->prepare_links( $object, $request ) );
  269. /**
  270. * Filter the data for a response.
  271. *
  272. * The dynamic portion of the hook name, $this->post_type,
  273. * refers to object type being prepared for the response.
  274. *
  275. * @param WP_REST_Response $response The response object.
  276. * @param WC_Data $object Object data.
  277. * @param WP_REST_Request $request Request object.
  278. */
  279. return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
  280. }
  281. /**
  282. * Prepare links for the request.
  283. *
  284. * @param WC_Data $object Object data.
  285. * @param WP_REST_Request $request Request object.
  286. * @return array Links for the given post.
  287. */
  288. protected function prepare_links( $object, $request ) {
  289. $links = array(
  290. 'self' => array(
  291. 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ),
  292. ),
  293. 'collection' => array(
  294. 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
  295. ),
  296. );
  297. if ( 0 !== (int) $object->get_customer_id() ) {
  298. $links['customer'] = array(
  299. 'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $object->get_customer_id() ) ),
  300. );
  301. }
  302. if ( 0 !== (int) $object->get_parent_id() ) {
  303. $links['up'] = array(
  304. 'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $object->get_parent_id() ) ),
  305. );
  306. }
  307. return $links;
  308. }
  309. /**
  310. * Prepare objects query.
  311. *
  312. * @since 3.0.0
  313. * @param WP_REST_Request $request Full details about the request.
  314. * @return array
  315. */
  316. protected function prepare_objects_query( $request ) {
  317. global $wpdb;
  318. $args = parent::prepare_objects_query( $request );
  319. // Set post_status.
  320. if ( 'any' !== $request['status'] ) {
  321. $args['post_status'] = 'wc-' . $request['status'];
  322. } else {
  323. $args['post_status'] = 'any';
  324. }
  325. if ( isset( $request['customer'] ) ) {
  326. if ( ! empty( $args['meta_query'] ) ) {
  327. $args['meta_query'] = array(); // WPCS: slow query ok.
  328. }
  329. $args['meta_query'][] = array(
  330. 'key' => '_customer_user',
  331. 'value' => $request['customer'],
  332. 'type' => 'NUMERIC',
  333. );
  334. }
  335. // Search by product.
  336. if ( ! empty( $request['product'] ) ) {
  337. $order_ids = $wpdb->get_col(
  338. $wpdb->prepare(
  339. "
  340. SELECT order_id
  341. FROM {$wpdb->prefix}woocommerce_order_items
  342. WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
  343. AND order_item_type = 'line_item'
  344. ", $request['product']
  345. )
  346. );
  347. // Force WP_Query return empty if don't found any order.
  348. $order_ids = ! empty( $order_ids ) ? $order_ids : array( 0 );
  349. $args['post__in'] = $order_ids;
  350. }
  351. // Search.
  352. if ( ! empty( $args['s'] ) ) {
  353. $order_ids = wc_order_search( $args['s'] );
  354. if ( ! empty( $order_ids ) ) {
  355. unset( $args['s'] );
  356. $args['post__in'] = array_merge( $order_ids, array( 0 ) );
  357. }
  358. }
  359. /**
  360. * Filter the query arguments for a request.
  361. *
  362. * Enables adding extra arguments or setting defaults for an order collection request.
  363. *
  364. * @param array $args Key value array of query var to query value.
  365. * @param WP_REST_Request $request The request used.
  366. */
  367. $args = apply_filters( 'woocommerce_rest_orders_prepare_object_query', $args, $request );
  368. return $args;
  369. }
  370. /**
  371. * Only return writable props from schema.
  372. *
  373. * @param array $schema Schema.
  374. * @return bool
  375. */
  376. protected function filter_writable_props( $schema ) {
  377. return empty( $schema['readonly'] );
  378. }
  379. /**
  380. * Prepare a single order for create or update.
  381. *
  382. * @param WP_REST_Request $request Request object.
  383. * @param bool $creating If is creating a new object.
  384. * @return WP_Error|WC_Data
  385. */
  386. protected function prepare_object_for_database( $request, $creating = false ) {
  387. $id = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
  388. $order = new WC_Order( $id );
  389. $schema = $this->get_item_schema();
  390. $data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
  391. // Handle all writable props.
  392. foreach ( $data_keys as $key ) {
  393. $value = $request[ $key ];
  394. if ( ! is_null( $value ) ) {
  395. switch ( $key ) {
  396. case 'status':
  397. // Status change should be done later so transitions have new data.
  398. break;
  399. case 'billing':
  400. case 'shipping':
  401. $this->update_address( $order, $value, $key );
  402. break;
  403. case 'line_items':
  404. case 'shipping_lines':
  405. case 'fee_lines':
  406. case 'coupon_lines':
  407. if ( is_array( $value ) ) {
  408. foreach ( $value as $item ) {
  409. if ( is_array( $item ) ) {
  410. if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
  411. $order->remove_item( $item['id'] );
  412. } else {
  413. $this->set_item( $order, $key, $item );
  414. }
  415. }
  416. }
  417. }
  418. break;
  419. case 'meta_data':
  420. if ( is_array( $value ) ) {
  421. foreach ( $value as $meta ) {
  422. $order->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
  423. }
  424. }
  425. break;
  426. default:
  427. if ( is_callable( array( $order, "set_{$key}" ) ) ) {
  428. $order->{"set_{$key}"}( $value );
  429. }
  430. break;
  431. }
  432. }
  433. }
  434. /**
  435. * Filters an object before it is inserted via the REST API.
  436. *
  437. * The dynamic portion of the hook name, `$this->post_type`,
  438. * refers to the object type slug.
  439. *
  440. * @param WC_Data $order Object object.
  441. * @param WP_REST_Request $request Request object.
  442. * @param bool $creating If is creating a new object.
  443. */
  444. return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $order, $request, $creating );
  445. }
  446. /**
  447. * Save an object data.
  448. *
  449. * @since 3.0.0
  450. * @throws WC_REST_Exception But all errors are validated before returning any data.
  451. * @param WP_REST_Request $request Full details about the request.
  452. * @param bool $creating If is creating a new object.
  453. * @return WC_Data|WP_Error
  454. */
  455. protected function save_object( $request, $creating = false ) {
  456. try {
  457. $object = $this->prepare_object_for_database( $request, $creating );
  458. if ( is_wp_error( $object ) ) {
  459. return $object;
  460. }
  461. // Make sure gateways are loaded so hooks from gateways fire on save/create.
  462. WC()->payment_gateways();
  463. if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] ) {
  464. // Make sure customer exists.
  465. if ( false === get_user_by( 'id', $request['customer_id'] ) ) {
  466. throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 );
  467. }
  468. // Make sure customer is part of blog.
  469. if ( is_multisite() && ! is_user_member_of_blog( $request['customer_id'] ) ) {
  470. throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id_network', __( 'Customer ID does not belong to this site.', 'woocommerce' ), 400 );
  471. }
  472. }
  473. if ( $creating ) {
  474. $object->set_created_via( 'rest-api' );
  475. $object->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
  476. $object->calculate_totals();
  477. } else {
  478. // If items have changed, recalculate order totals.
  479. if ( isset( $request['billing'] ) || isset( $request['shipping'] ) || isset( $request['line_items'] ) || isset( $request['shipping_lines'] ) || isset( $request['fee_lines'] ) || isset( $request['coupon_lines'] ) ) {
  480. $object->calculate_totals( true );
  481. }
  482. }
  483. // Set status.
  484. if ( ! empty( $request['status'] ) ) {
  485. $object->set_status( $request['status'] );
  486. }
  487. $object->save();
  488. // Actions for after the order is saved.
  489. if ( true === $request['set_paid'] ) {
  490. if ( $creating || $object->needs_payment() ) {
  491. $object->payment_complete( $request['transaction_id'] );
  492. }
  493. }
  494. return $this->get_object( $object->get_id() );
  495. } catch ( WC_Data_Exception $e ) {
  496. return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
  497. } catch ( WC_REST_Exception $e ) {
  498. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
  499. }
  500. }
  501. /**
  502. * Update address.
  503. *
  504. * @param WC_Order $order Order data.
  505. * @param array $posted Posted data.
  506. * @param string $type Address type.
  507. */
  508. protected function update_address( $order, $posted, $type = 'billing' ) {
  509. foreach ( $posted as $key => $value ) {
  510. if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) {
  511. $order->{"set_{$type}_{$key}"}( $value );
  512. }
  513. }
  514. }
  515. /**
  516. * Gets the product ID from the SKU or posted ID.
  517. *
  518. * @throws WC_REST_Exception When SKU or ID is not valid.
  519. * @param array $posted Request data.
  520. * @return int
  521. */
  522. protected function get_product_id( $posted ) {
  523. if ( ! empty( $posted['sku'] ) ) {
  524. $product_id = (int) wc_get_product_id_by_sku( $posted['sku'] );
  525. } elseif ( ! empty( $posted['product_id'] ) && empty( $posted['variation_id'] ) ) {
  526. $product_id = (int) $posted['product_id'];
  527. } elseif ( ! empty( $posted['variation_id'] ) ) {
  528. $product_id = (int) $posted['variation_id'];
  529. } else {
  530. throw new WC_REST_Exception( 'woocommerce_rest_required_product_reference', __( 'Product ID or SKU is required.', 'woocommerce' ), 400 );
  531. }
  532. return $product_id;
  533. }
  534. /**
  535. * Maybe set an item prop if the value was posted.
  536. *
  537. * @param WC_Order_Item $item Order item.
  538. * @param string $prop Order property.
  539. * @param array $posted Request data.
  540. */
  541. protected function maybe_set_item_prop( $item, $prop, $posted ) {
  542. if ( isset( $posted[ $prop ] ) ) {
  543. $item->{"set_$prop"}( $posted[ $prop ] );
  544. }
  545. }
  546. /**
  547. * Maybe set item props if the values were posted.
  548. *
  549. * @param WC_Order_Item $item Order item data.
  550. * @param string[] $props Properties.
  551. * @param array $posted Request data.
  552. */
  553. protected function maybe_set_item_props( $item, $props, $posted ) {
  554. foreach ( $props as $prop ) {
  555. $this->maybe_set_item_prop( $item, $prop, $posted );
  556. }
  557. }
  558. /**
  559. * Maybe set item meta if posted.
  560. *
  561. * @param WC_Order_Item $item Order item data.
  562. * @param array $posted Request data.
  563. */
  564. protected function maybe_set_item_meta_data( $item, $posted ) {
  565. if ( ! empty( $posted['meta_data'] ) && is_array( $posted['meta_data'] ) ) {
  566. foreach ( $posted['meta_data'] as $meta ) {
  567. if ( isset( $meta['key'] ) ) {
  568. $value = isset( $meta['value'] ) ? $meta['value'] : null;
  569. $item->update_meta_data( $meta['key'], $value, isset( $meta['id'] ) ? $meta['id'] : '' );
  570. }
  571. }
  572. }
  573. }
  574. /**
  575. * Create or update a line item.
  576. *
  577. * @param array $posted Line item data.
  578. * @param string $action 'create' to add line item or 'update' to update it.
  579. * @param object $item Passed when updating an item. Null during creation.
  580. * @return WC_Order_Item_Product
  581. * @throws WC_REST_Exception Invalid data, server error.
  582. */
  583. protected function prepare_line_items( $posted, $action = 'create', $item = null ) {
  584. $item = is_null( $item ) ? new WC_Order_Item_Product( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
  585. $product = wc_get_product( $this->get_product_id( $posted ) );
  586. if ( $product !== $item->get_product() ) {
  587. $item->set_product( $product );
  588. if ( 'create' === $action ) {
  589. $quantity = isset( $posted['quantity'] ) ? $posted['quantity'] : 1;
  590. $total = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
  591. $item->set_total( $total );
  592. $item->set_subtotal( $total );
  593. }
  594. }
  595. $this->maybe_set_item_props( $item, array( 'name', 'quantity', 'total', 'subtotal', 'tax_class' ), $posted );
  596. $this->maybe_set_item_meta_data( $item, $posted );
  597. return $item;
  598. }
  599. /**
  600. * Create or update an order shipping method.
  601. *
  602. * @param array $posted $shipping Item data.
  603. * @param string $action 'create' to add shipping or 'update' to update it.
  604. * @param object $item Passed when updating an item. Null during creation.
  605. * @return WC_Order_Item_Shipping
  606. * @throws WC_REST_Exception Invalid data, server error.
  607. */
  608. protected function prepare_shipping_lines( $posted, $action = 'create', $item = null ) {
  609. $item = is_null( $item ) ? new WC_Order_Item_Shipping( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
  610. if ( 'create' === $action ) {
  611. if ( empty( $posted['method_id'] ) ) {
  612. throw new WC_REST_Exception( 'woocommerce_rest_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 );
  613. }
  614. }
  615. $this->maybe_set_item_props( $item, array( 'method_id', 'method_title', 'total' ), $posted );
  616. $this->maybe_set_item_meta_data( $item, $posted );
  617. return $item;
  618. }
  619. /**
  620. * Create or update an order fee.
  621. *
  622. * @param array $posted Item data.
  623. * @param string $action 'create' to add fee or 'update' to update it.
  624. * @param object $item Passed when updating an item. Null during creation.
  625. * @return WC_Order_Item_Fee
  626. * @throws WC_REST_Exception Invalid data, server error.
  627. */
  628. protected function prepare_fee_lines( $posted, $action = 'create', $item = null ) {
  629. $item = is_null( $item ) ? new WC_Order_Item_Fee( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
  630. if ( 'create' === $action ) {
  631. if ( empty( $posted['name'] ) ) {
  632. throw new WC_REST_Exception( 'woocommerce_rest_invalid_fee_item', __( 'Fee name is required.', 'woocommerce' ), 400 );
  633. }
  634. }
  635. $this->maybe_set_item_props( $item, array( 'name', 'tax_class', 'tax_status', 'total' ), $posted );
  636. $this->maybe_set_item_meta_data( $item, $posted );
  637. return $item;
  638. }
  639. /**
  640. * Create or update an order coupon.
  641. *
  642. * @param array $posted Item data.
  643. * @param string $action 'create' to add coupon or 'update' to update it.
  644. * @param object $item Passed when updating an item. Null during creation.
  645. * @return WC_Order_Item_Coupon
  646. * @throws WC_REST_Exception Invalid data, server error.
  647. */
  648. protected function prepare_coupon_lines( $posted, $action = 'create', $item = null ) {
  649. $item = is_null( $item ) ? new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
  650. if ( 'create' === $action ) {
  651. if ( empty( $posted['code'] ) ) {
  652. throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
  653. }
  654. }
  655. $this->maybe_set_item_props( $item, array( 'code', 'discount' ), $posted );
  656. $this->maybe_set_item_meta_data( $item, $posted );
  657. return $item;
  658. }
  659. /**
  660. * Wrapper method to create/update order items.
  661. * When updating, the item ID provided is checked to ensure it is associated
  662. * with the order.
  663. *
  664. * @param WC_Order $order order object.
  665. * @param string $item_type The item type.
  666. * @param array $posted item provided in the request body.
  667. * @throws WC_REST_Exception If item ID is not associated with order.
  668. */
  669. protected function set_item( $order, $item_type, $posted ) {
  670. global $wpdb;
  671. if ( ! empty( $posted['id'] ) ) {
  672. $action = 'update';
  673. } else {
  674. $action = 'create';
  675. }
  676. $method = 'prepare_' . $item_type;
  677. $item = null;
  678. // Verify provided line item ID is associated with order.
  679. if ( 'update' === $action ) {
  680. $item = $order->get_item( absint( $posted['id'] ), false );
  681. if ( ! $item ) {
  682. throw new WC_REST_Exception( 'woocommerce_rest_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 );
  683. }
  684. }
  685. // Prepare item data.
  686. $item = $this->$method( $posted, $action, $item );
  687. do_action( 'woocommerce_rest_set_order_item', $item, $posted );
  688. // If creating the order, add the item to it.
  689. if ( 'create' === $action ) {
  690. $order->add_item( $item );
  691. } else {
  692. $item->save();
  693. }
  694. }
  695. /**
  696. * Helper method to check if the resource ID associated with the provided item is null.
  697. * Items can be deleted by setting the resource ID to null.
  698. *
  699. * @param array $item Item provided in the request body.
  700. * @return bool True if the item resource ID is null, false otherwise.
  701. */
  702. protected function item_is_null( $item ) {
  703. $keys = array( 'product_id', 'method_id', 'method_title', 'name', 'code' );
  704. foreach ( $keys as $key ) {
  705. if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
  706. return true;
  707. }
  708. }
  709. return false;
  710. }
  711. /**
  712. * Get order statuses without prefixes.
  713. *
  714. * @return array
  715. */
  716. protected function get_order_statuses() {
  717. $order_statuses = array();
  718. foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
  719. $order_statuses[] = str_replace( 'wc-', '', $status );
  720. }
  721. return $order_statuses;
  722. }
  723. /**
  724. * Get the Order's schema, conforming to JSON Schema.
  725. *
  726. * @return array
  727. */
  728. public function get_item_schema() {
  729. $schema = array(
  730. '$schema' => 'http://json-schema.org/draft-04/schema#',
  731. 'title' => $this->post_type,
  732. 'type' => 'object',
  733. 'properties' => array(
  734. 'id' => array(
  735. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
  736. 'type' => 'integer',
  737. 'context' => array( 'view', 'edit' ),
  738. 'readonly' => true,
  739. ),
  740. 'parent_id' => array(
  741. 'description' => __( 'Parent order ID.', 'woocommerce' ),
  742. 'type' => 'integer',
  743. 'context' => array( 'view', 'edit' ),
  744. ),
  745. 'number' => array(
  746. 'description' => __( 'Order number.', 'woocommerce' ),
  747. 'type' => 'string',
  748. 'context' => array( 'view', 'edit' ),
  749. 'readonly' => true,
  750. ),
  751. 'order_key' => array(
  752. 'description' => __( 'Order key.', 'woocommerce' ),
  753. 'type' => 'string',
  754. 'context' => array( 'view', 'edit' ),
  755. 'readonly' => true,
  756. ),
  757. 'created_via' => array(
  758. 'description' => __( 'Shows where the order was created.', 'woocommerce' ),
  759. 'type' => 'string',
  760. 'context' => array( 'view', 'edit' ),
  761. 'readonly' => true,
  762. ),
  763. 'version' => array(
  764. 'description' => __( 'Version of WooCommerce which last updated the order.', 'woocommerce' ),
  765. 'type' => 'integer',
  766. 'context' => array( 'view', 'edit' ),
  767. 'readonly' => true,
  768. ),
  769. 'status' => array(
  770. 'description' => __( 'Order status.', 'woocommerce' ),
  771. 'type' => 'string',
  772. 'default' => 'pending',
  773. 'enum' => $this->get_order_statuses(),
  774. 'context' => array( 'view', 'edit' ),
  775. ),
  776. 'currency' => array(
  777. 'description' => __( 'Currency the order was created with, in ISO format.', 'woocommerce' ),
  778. 'type' => 'string',
  779. 'default' => get_woocommerce_currency(),
  780. 'enum' => array_keys( get_woocommerce_currencies() ),
  781. 'context' => array( 'view', 'edit' ),
  782. ),
  783. 'date_created' => array(
  784. 'description' => __( "The date the order was created, in the site's timezone.", 'woocommerce' ),
  785. 'type' => 'date-time',
  786. 'context' => array( 'view', 'edit' ),
  787. 'readonly' => true,
  788. ),
  789. 'date_created_gmt' => array(
  790. 'description' => __( 'The date the order was created, as GMT.', 'woocommerce' ),
  791. 'type' => 'date-time',
  792. 'context' => array( 'view', 'edit' ),
  793. 'readonly' => true,
  794. ),
  795. 'date_modified' => array(
  796. 'description' => __( "The date the order was last modified, in the site's timezone.", 'woocommerce' ),
  797. 'type' => 'date-time',
  798. 'context' => array( 'view', 'edit' ),
  799. 'readonly' => true,
  800. ),
  801. 'date_modified_gmt' => array(
  802. 'description' => __( 'The date the order was last modified, as GMT.', 'woocommerce' ),
  803. 'type' => 'date-time',
  804. 'context' => array( 'view', 'edit' ),
  805. 'readonly' => true,
  806. ),
  807. 'discount_total' => array(
  808. 'description' => __( 'Total discount amount for the order.', 'woocommerce' ),
  809. 'type' => 'string',
  810. 'context' => array( 'view', 'edit' ),
  811. 'readonly' => true,
  812. ),
  813. 'discount_tax' => array(
  814. 'description' => __( 'Total discount tax amount for the order.', 'woocommerce' ),
  815. 'type' => 'string',
  816. 'context' => array( 'view', 'edit' ),
  817. 'readonly' => true,
  818. ),
  819. 'shipping_total' => array(
  820. 'description' => __( 'Total shipping amount for the order.', 'woocommerce' ),
  821. 'type' => 'string',
  822. 'context' => array( 'view', 'edit' ),
  823. 'readonly' => true,
  824. ),
  825. 'shipping_tax' => array(
  826. 'description' => __( 'Total shipping tax amount for the order.', 'woocommerce' ),
  827. 'type' => 'string',
  828. 'context' => array( 'view', 'edit' ),
  829. 'readonly' => true,
  830. ),
  831. 'cart_tax' => array(
  832. 'description' => __( 'Sum of line item taxes only.', 'woocommerce' ),
  833. 'type' => 'string',
  834. 'context' => array( 'view', 'edit' ),
  835. 'readonly' => true,
  836. ),
  837. 'total' => array(
  838. 'description' => __( 'Grand total.', 'woocommerce' ),
  839. 'type' => 'string',
  840. 'context' => array( 'view', 'edit' ),
  841. 'readonly' => true,
  842. ),
  843. 'total_tax' => array(
  844. 'description' => __( 'Sum of all taxes.', 'woocommerce' ),
  845. 'type' => 'string',
  846. 'context' => array( 'view', 'edit' ),
  847. 'readonly' => true,
  848. ),
  849. 'prices_include_tax' => array(
  850. 'description' => __( 'True the prices included tax during checkout.', 'woocommerce' ),
  851. 'type' => 'boolean',
  852. 'context' => array( 'view', 'edit' ),
  853. 'readonly' => true,
  854. ),
  855. 'customer_id' => array(
  856. 'description' => __( 'User ID who owns the order. 0 for guests.', 'woocommerce' ),
  857. 'type' => 'integer',
  858. 'default' => 0,
  859. 'context' => array( 'view', 'edit' ),
  860. ),
  861. 'customer_ip_address' => array(
  862. 'description' => __( "Customer's IP address.", 'woocommerce' ),
  863. 'type' => 'string',
  864. 'context' => array( 'view', 'edit' ),
  865. 'readonly' => true,
  866. ),
  867. 'customer_user_agent' => array(
  868. 'description' => __( 'User agent of the customer.', 'woocommerce' ),
  869. 'type' => 'string',
  870. 'context' => array( 'view', 'edit' ),
  871. 'readonly' => true,
  872. ),
  873. 'customer_note' => array(
  874. 'description' => __( 'Note left by customer during checkout.', 'woocommerce' ),
  875. 'type' => 'string',
  876. 'context' => array( 'view', 'edit' ),
  877. ),
  878. 'billing' => array(
  879. 'description' => __( 'Billing address.', 'woocommerce' ),
  880. 'type' => 'object',
  881. 'context' => array( 'view', 'edit' ),
  882. 'properties' => array(
  883. 'first_name' => array(
  884. 'description' => __( 'First name.', 'woocommerce' ),
  885. 'type' => 'string',
  886. 'context' => array( 'view', 'edit' ),
  887. ),
  888. 'last_name' => array(
  889. 'description' => __( 'Last name.', 'woocommerce' ),
  890. 'type' => 'string',
  891. 'context' => array( 'view', 'edit' ),
  892. ),
  893. 'company' => array(
  894. 'description' => __( 'Company name.', 'woocommerce' ),
  895. 'type' => 'string',
  896. 'context' => array( 'view', 'edit' ),
  897. ),
  898. 'address_1' => array(
  899. 'description' => __( 'Address line 1', 'woocommerce' ),
  900. 'type' => 'string',
  901. 'context' => array( 'view', 'edit' ),
  902. ),
  903. 'address_2' => array(
  904. 'description' => __( 'Address line 2', 'woocommerce' ),
  905. 'type' => 'string',
  906. 'context' => array( 'view', 'edit' ),
  907. ),
  908. 'city' => array(
  909. 'description' => __( 'City name.', 'woocommerce' ),
  910. 'type' => 'string',
  911. 'context' => array( 'view', 'edit' ),
  912. ),
  913. 'state' => array(
  914. 'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
  915. 'type' => 'string',
  916. 'context' => array( 'view', 'edit' ),
  917. ),
  918. 'postcode' => array(
  919. 'description' => __( 'Postal code.', 'woocommerce' ),
  920. 'type' => 'string',
  921. 'context' => array( 'view', 'edit' ),
  922. ),
  923. 'country' => array(
  924. 'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
  925. 'type' => 'string',
  926. 'context' => array( 'view', 'edit' ),
  927. ),
  928. 'email' => array(
  929. 'description' => __( 'Email address.', 'woocommerce' ),
  930. 'type' => 'string',
  931. 'format' => 'email',
  932. 'context' => array( 'view', 'edit' ),
  933. ),
  934. 'phone' => array(
  935. 'description' => __( 'Phone number.', 'woocommerce' ),
  936. 'type' => 'string',
  937. 'context' => array( 'view', 'edit' ),
  938. ),
  939. ),
  940. ),
  941. 'shipping' => array(
  942. 'description' => __( 'Shipping address.', 'woocommerce' ),
  943. 'type' => 'object',
  944. 'context' => array( 'view', 'edit' ),
  945. 'properties' => array(
  946. 'first_name' => array(
  947. 'description' => __( 'First name.', 'woocommerce' ),
  948. 'type' => 'string',
  949. 'context' => array( 'view', 'edit' ),
  950. ),
  951. 'last_name' => array(
  952. 'description' => __( 'Last name.', 'woocommerce' ),
  953. 'type' => 'string',
  954. 'context' => array( 'view', 'edit' ),
  955. ),
  956. 'company' => array(
  957. 'description' => __( 'Company name.', 'woocommerce' ),
  958. 'type' => 'string',
  959. 'context' => array( 'view', 'edit' ),
  960. ),
  961. 'address_1' => array(
  962. 'description' => __( 'Address line 1', 'woocommerce' ),
  963. 'type' => 'string',
  964. 'context' => array( 'view', 'edit' ),
  965. ),
  966. 'address_2' => array(
  967. 'description' => __( 'Address line 2', 'woocommerce' ),
  968. 'type' => 'string',
  969. 'context' => array( 'view', 'edit' ),
  970. ),
  971. 'city' => array(
  972. 'description' => __( 'City name.', 'woocommerce' ),
  973. 'type' => 'string',
  974. 'context' => array( 'view', 'edit' ),
  975. ),
  976. 'state' => array(
  977. 'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
  978. 'type' => 'string',
  979. 'context' => array( 'view', 'edit' ),
  980. ),
  981. 'postcode' => array(
  982. 'description' => __( 'Postal code.', 'woocommerce' ),
  983. 'type' => 'string',
  984. 'context' => array( 'view', 'edit' ),
  985. ),
  986. 'country' => array(
  987. 'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
  988. 'type' => 'string',
  989. 'context' => array( 'view', 'edit' ),
  990. ),
  991. ),
  992. ),
  993. 'payment_method' => array(
  994. 'description' => __( 'Payment method ID.', 'woocommerce' ),
  995. 'type' => 'string',
  996. 'context' => array( 'view', 'edit' ),
  997. ),
  998. 'payment_method_title' => array(
  999. 'description' => __( 'Payment method title.', 'woocommerce' ),
  1000. 'type' => 'string',
  1001. 'context' => array( 'view', 'edit' ),
  1002. ),
  1003. 'transaction_id' => array(
  1004. 'description' => __( 'Unique transaction ID.', 'woocommerce' ),
  1005. 'type' => 'string',
  1006. 'context' => array( 'view', 'edit' ),
  1007. ),
  1008. 'date_paid' => array(
  1009. 'description' => __( "The date the order was paid, in the site's timezone.", 'woocommerce' ),
  1010. 'type' => 'date-time',
  1011. 'context' => array( 'view', 'edit' ),
  1012. 'readonly' => true,
  1013. ),
  1014. 'date_paid_gmt' => array(
  1015. 'description' => __( 'The date the order was paid, as GMT.', 'woocommerce' ),
  1016. 'type' => 'date-time',
  1017. 'context' => array( 'view', 'edit' ),
  1018. 'readonly' => true,
  1019. ),
  1020. 'date_completed' => array(
  1021. 'description' => __( "The date the order was completed, in the site's timezone.", 'woocommerce' ),
  1022. 'type' => 'date-time',
  1023. 'context' => array( 'view', 'edit' ),
  1024. 'readonly' => true,
  1025. ),
  1026. 'date_completed_gmt' => array(
  1027. 'description' => __( 'The date the order was completed, as GMT.', 'woocommerce' ),
  1028. 'type' => 'date-time',
  1029. 'context' => array( 'view', 'edit' ),
  1030. 'readonly' => true,
  1031. ),
  1032. 'cart_hash' => array(
  1033. 'description' => __( 'MD5 hash of cart items to ensure orders are not modified.', 'woocommerce' ),
  1034. 'type' => 'string',
  1035. 'context' => array( 'view', 'edit' ),
  1036. 'readonly' => true,
  1037. ),
  1038. 'meta_data' => array(
  1039. 'description' => __( 'Meta data.', 'woocommerce' ),
  1040. 'type' => 'array',
  1041. 'context' => array( 'view', 'edit' ),
  1042. 'items' => array(
  1043. 'type' => 'object',
  1044. 'properties' => array(
  1045. 'id' => array(
  1046. 'description' => __( 'Meta ID.', 'woocommerce' ),
  1047. 'type' => 'integer',
  1048. 'context' => array( 'view', 'edit' ),
  1049. 'readonly' => true,
  1050. ),
  1051. 'key' => array(
  1052. 'description' => __( 'Meta key.', 'woocommerce' ),
  1053. 'type' => 'string',
  1054. 'context' => array( 'view', 'edit' ),
  1055. ),
  1056. 'value' => array(
  1057. 'description' => __( 'Meta value.', 'woocommerce' ),
  1058. 'type' => 'mixed',
  1059. 'context' => array( 'view', 'edit' ),
  1060. ),
  1061. ),
  1062. ),
  1063. ),
  1064. 'line_items' => array(
  1065. 'description' => __( 'Line items data.', 'woocommerce' ),
  1066. 'type' => 'array',
  1067. 'context' => array( 'view', 'edit' ),
  1068. 'items' => array(
  1069. 'type' => 'object',
  1070. 'properties' => array(
  1071. 'id' => array(
  1072. 'description' => __( 'Item ID.', 'woocommerce' ),
  1073. 'type' => 'integer',
  1074. 'context' => array( 'view', 'edit' ),
  1075. 'readonly' => true,
  1076. ),
  1077. 'name' => array(
  1078. 'description' => __( 'Product name.', 'woocommerce' ),
  1079. 'type' => 'mixed',
  1080. 'context' => array( 'view', 'edit' ),
  1081. ),
  1082. 'product_id' => array(
  1083. 'description' => __( 'Product ID.', 'woocommerce' ),
  1084. 'type' => 'mixed',
  1085. 'context' => array( 'view', 'edit' ),
  1086. ),
  1087. 'variation_id' => array(
  1088. 'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
  1089. 'type' => 'integer',
  1090. 'context' => array( 'view', 'edit' ),
  1091. ),
  1092. 'quantity' => array(
  1093. 'description' => __( 'Quantity ordered.', 'woocommerce' ),
  1094. 'type' => 'integer',
  1095. 'context' => array( 'view', 'edit' ),
  1096. ),
  1097. 'tax_class' => array(
  1098. 'description' => __( 'Tax class of product.', 'woocommerce' ),
  1099. 'type' => 'string',
  1100. 'context' => array( 'view', 'edit' ),
  1101. ),
  1102. 'subtotal' => array(
  1103. 'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
  1104. 'type' => 'string',
  1105. 'context' => array( 'view', 'edit' ),
  1106. ),
  1107. 'subtotal_tax' => array(
  1108. 'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),
  1109. 'type' => 'string',
  1110. 'context' => array( 'view', 'edit' ),
  1111. 'readonly' => true,
  1112. ),
  1113. 'total' => array(
  1114. 'description' => __( 'Line total (after discounts).', 'woocommerce' ),
  1115. 'type' => 'string',
  1116. 'context' => array( 'view', 'edit' ),
  1117. ),
  1118. 'total_tax' => array(
  1119. 'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
  1120. 'type' => 'string',
  1121. 'context' => array( 'view', 'edit' ),
  1122. 'readonly' => true,
  1123. ),
  1124. 'taxes' => array(
  1125. 'description' => __( 'Line taxes.', 'woocommerce' ),
  1126. 'type' => 'array',
  1127. 'context' => array( 'view', 'edit' ),
  1128. 'readonly' => true,
  1129. 'items' => array(
  1130. 'type' => 'object',
  1131. 'properties' => array(
  1132. 'id' => array(
  1133. 'description' => __( 'Tax rate ID.', 'woocommerce' ),
  1134. 'type' => 'integer',
  1135. 'context' => array( 'view', 'edit' ),
  1136. ),
  1137. 'total' => array(
  1138. 'description' => __( 'Tax total.', 'woocommerce' ),
  1139. 'type' => 'string',
  1140. 'context' => array( 'view', 'edit' ),
  1141. ),
  1142. 'subtotal' => array(
  1143. 'description' => __( 'Tax subtotal.', 'woocommerce' ),
  1144. 'type' => 'string',
  1145. 'context' => array( 'view', 'edit' ),
  1146. ),
  1147. ),
  1148. ),
  1149. ),
  1150. 'meta_data' => array(
  1151. 'description' => __( 'Meta data.', 'woocommerce' ),
  1152. 'type' => 'array',
  1153. 'context' => array( 'view', 'edit' ),
  1154. 'items' => array(
  1155. 'type' => 'object',
  1156. 'properties' => array(
  1157. 'id' => array(
  1158. 'description' => __( 'Meta ID.', 'woocommerce' ),
  1159. 'type' => 'integer',
  1160. 'context' => array( 'view', 'edit' ),
  1161. 'readonly' => true,
  1162. ),
  1163. 'key' => array(
  1164. 'description' => __( 'Meta key.', 'woocommerce' ),
  1165. 'type' => 'string',
  1166. 'context' => array( 'view', 'edit' ),
  1167. ),
  1168. 'value' => array(
  1169. 'description' => __( 'Meta value.', 'woocommerce' ),
  1170. 'type' => 'mixed',
  1171. 'context' => array( 'view', 'edit' ),
  1172. ),
  1173. ),
  1174. ),
  1175. ),
  1176. 'sku' => array(
  1177. 'description' => __( 'Product SKU.', 'woocommerce' ),
  1178. 'type' => 'string',
  1179. 'context' => array( 'view', 'edit' ),
  1180. 'readonly' => true,
  1181. ),
  1182. 'price' => array(
  1183. 'description' => __( 'Product price.', 'woocommerce' ),
  1184. 'type' => 'number',
  1185. 'context' => array( 'view', 'edit' ),
  1186. 'readonly' => true,
  1187. ),
  1188. ),
  1189. ),
  1190. ),
  1191. 'tax_lines' => array(
  1192. 'description' => __( 'Tax lines data.', 'woocommerce' ),
  1193. 'type' => 'array',
  1194. 'context' => array( 'view', 'edit' ),
  1195. 'readonly' => true,
  1196. 'items' => array(
  1197. 'type' => 'object',
  1198. 'properties' => array(
  1199. 'id' => array(
  1200. 'description' => __( 'Item ID.', 'woocommerce' ),
  1201. 'type' => 'integer',
  1202. 'context' => array( 'view', 'edit' ),
  1203. 'readonly' => true,
  1204. ),
  1205. 'rate_code' => array(
  1206. 'description' => __( 'Tax rate code.', 'woocommerce' ),
  1207. 'type' => 'string',
  1208. 'context' => array( 'view', 'edit' ),
  1209. 'readonly' => true,
  1210. ),
  1211. 'rate_id' => array(
  1212. 'description' => __( 'Tax rate ID.', 'woocommerce' ),
  1213. 'type' => 'string',
  1214. 'context' => array( 'view', 'edit' ),
  1215. 'readonly' => true,
  1216. ),
  1217. 'label' => array(
  1218. 'description' => __( 'Tax rate label.', 'woocommerce' ),
  1219. 'type' => 'string',
  1220. 'context' => array( 'view', 'edit' ),
  1221. 'readonly' => true,
  1222. ),
  1223. 'compound' => array(
  1224. 'description' => __( 'Show if is a compound tax rate.', 'woocommerce' ),
  1225. 'type' => 'boolean',
  1226. 'context' => array( 'view', 'edit' ),
  1227. 'readonly' => true,
  1228. ),
  1229. 'tax_total' => array(
  1230. 'description' => __( 'Tax total (not including shipping taxes).', 'woocommerce' ),
  1231. 'type' => 'string',
  1232. 'context' => array( 'view', 'edit' ),
  1233. 'readonly' => true,
  1234. ),
  1235. 'shipping_tax_total' => array(
  1236. 'description' => __( 'Shipping tax total.', 'woocommerce' ),
  1237. 'type' => 'string',
  1238. 'context' => array( 'view', 'edit' ),
  1239. 'readonly' => true,
  1240. ),
  1241. 'meta_data' => array(
  1242. 'description' => __( 'Meta data.', 'woocommerce' ),
  1243. 'type' => 'array',
  1244. 'context' => array( 'view', 'edit' ),
  1245. 'items' => array(
  1246. 'type' => 'object',
  1247. 'properties' => array(
  1248. 'id' => array(
  1249. 'description' => __( 'Meta ID.', 'woocommerce' ),
  1250. 'type' => 'integer',
  1251. 'context' => array( 'view', 'edit' ),
  1252. 'readonly' => true,
  1253. ),
  1254. 'key' => array(
  1255. 'description' => __( 'Meta key.', 'woocommerce' ),
  1256. 'type' => 'string',
  1257. 'context' => array( 'view', 'edit' ),
  1258. ),
  1259. 'value' => array(
  1260. 'description' => __( 'Meta value.', 'woocommerce' ),
  1261. 'type' => 'mixed',
  1262. 'context' => array( 'view', 'edit' ),
  1263. ),
  1264. ),
  1265. ),
  1266. ),
  1267. ),
  1268. ),
  1269. ),
  1270. 'shipping_lines' => array(
  1271. 'description' => __( 'Shipping lines data.', 'woocommerce' ),
  1272. 'type' => 'array',
  1273. 'context' => array( 'view', 'edit' ),
  1274. 'items' => array(
  1275. 'type' => 'object',
  1276. 'properties' => array(
  1277. 'id' => array(
  1278. 'description' => __( 'Item ID.', 'woocommerce' ),
  1279. 'type' => 'integer',
  1280. 'context' => array( 'view', 'edit' ),
  1281. 'readonly' => true,
  1282. ),
  1283. 'method_title' => array(
  1284. 'description' => __( 'Shipping method name.', 'woocommerce' ),
  1285. 'type' => 'mixed',
  1286. 'context' => array( 'view', 'edit' ),
  1287. ),
  1288. 'method_id' => array(
  1289. 'description' => __( 'Shipping method ID.', 'woocommerce' ),
  1290. 'type' => 'mixed',
  1291. 'context' => array( 'view', 'edit' ),
  1292. ),
  1293. 'total' => array(
  1294. 'description' => __( 'Line total (after discounts).', 'woocommerce' ),
  1295. 'type' => 'string',
  1296. 'context' => array( 'view', 'edit' ),
  1297. ),
  1298. 'total_tax' => array(
  1299. 'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
  1300. 'type' => 'string',
  1301. 'context' => array( 'view', 'edit' ),
  1302. 'readonly' => true,
  1303. ),
  1304. 'taxes' => array(
  1305. 'description' => __( 'Line taxes.', 'woocommerce' ),
  1306. 'type' => 'array',
  1307. 'context' => array( 'view', 'edit' ),
  1308. 'readonly' => true,
  1309. 'items' => array(
  1310. 'type' => 'object',
  1311. 'properties' => array(
  1312. 'id' => array(
  1313. 'description' => __( 'Tax rate ID.', 'woocommerce' ),
  1314. 'type' => 'integer',
  1315. 'context' => array( 'view', 'edit' ),
  1316. 'readonly' => true,
  1317. ),
  1318. 'total' => array(
  1319. 'description' => __( 'Tax total.', 'woocommerce' ),
  1320. 'type' => 'string',
  1321. 'context' => array( 'view', 'edit' ),
  1322. 'readonly' => true,
  1323. ),
  1324. ),
  1325. ),
  1326. ),
  1327. 'meta_data' => array(
  1328. 'description' => __( 'Meta data.', 'woocommerce' ),
  1329. 'type' => 'array',
  1330. 'context' => array( 'view', 'edit' ),
  1331. 'items' => array(
  1332. 'type' => 'object',
  1333. 'properties' => array(
  1334. 'id' => array(
  1335. 'description' => __( 'Meta ID.', 'woocommerce' ),
  1336. 'type' => 'integer',
  1337. 'context' => array( 'view', 'edit' ),
  1338. 'readonly' => true,
  1339. ),
  1340. 'key' => array(
  1341. 'description' => __( 'Meta key.', 'woocommerce' ),
  1342. 'type' => 'string',
  1343. 'context' => array( 'view', 'edit' ),
  1344. ),
  1345. 'value' => array(
  1346. 'description' => __( 'Meta value.', 'woocommerce' ),
  1347. 'type' => 'mixed',
  1348. 'context' => array( 'view', 'edit' ),
  1349. ),
  1350. ),
  1351. ),
  1352. ),
  1353. ),
  1354. ),
  1355. ),
  1356. 'fee_lines' => array(
  1357. 'description' => __( 'Fee lines data.', 'woocommerce' ),
  1358. 'type' => 'array',
  1359. 'context' => array( 'view', 'edit' ),
  1360. 'items' => array(
  1361. 'type' => 'object',
  1362. 'properties' => array(
  1363. 'id' => array(
  1364. 'description' => __( 'Item ID.', 'woocommerce' ),
  1365. 'type' => 'integer',
  1366. 'context' => array( 'view', 'edit' ),
  1367. 'readonly' => true,
  1368. ),
  1369. 'name' => array(
  1370. 'description' => __( 'Fee name.', 'woocommerce' ),
  1371. 'type' => 'mixed',
  1372. 'context' => array( 'view', 'edit' ),
  1373. ),
  1374. 'tax_class' => array(
  1375. 'description' => __( 'Tax class of fee.', 'woocommerce' ),
  1376. 'type' => 'string',
  1377. 'context' => array( 'view', 'edit' ),
  1378. ),
  1379. 'tax_status' => array(
  1380. 'description' => __( 'Tax status of fee.', 'woocommerce' ),
  1381. 'type' => 'string',
  1382. 'context' => array( 'view', 'edit' ),
  1383. 'enum' => array( 'taxable', 'none' ),
  1384. ),
  1385. 'total' => array(
  1386. 'description' => __( 'Line total (after discounts).', 'woocommerce' ),
  1387. 'type' => 'string',
  1388. 'context' => array( 'view', 'edit' ),
  1389. ),
  1390. 'total_tax' => array(
  1391. 'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
  1392. 'type' => 'string',
  1393. 'context' => array( 'view', 'edit' ),
  1394. 'readonly' => true,
  1395. ),
  1396. 'taxes' => array(
  1397. 'description' => __( 'Line taxes.', 'woocommerce' ),
  1398. 'type' => 'array',
  1399. 'context' => array( 'view', 'edit' ),
  1400. 'readonly' => true,
  1401. 'items' => array(
  1402. 'type' => 'object',
  1403. 'properties' => array(
  1404. 'id' => array(
  1405. 'description' => __( 'Tax rate ID.', 'woocommerce' ),
  1406. 'type' => 'integer',
  1407. 'context' => array( 'view', 'edit' ),
  1408. 'readonly' => true,
  1409. ),
  1410. 'total' => array(
  1411. 'description' => __( 'Tax total.', 'woocommerce' ),
  1412. 'type' => 'string',
  1413. 'context' => array( 'view', 'edit' ),
  1414. 'readonly' => true,
  1415. ),
  1416. 'subtotal' => array(
  1417. 'description' => __( 'Tax subtotal.', 'woocommerce' ),
  1418. 'type' => 'string',
  1419. 'context' => array( 'view', 'edit' ),
  1420. 'readonly' => true,
  1421. ),
  1422. ),
  1423. ),
  1424. ),
  1425. 'meta_data' => array(
  1426. 'description' => __( 'Meta data.', 'woocommerce' ),
  1427. 'type' => 'array',
  1428. 'context' => array( 'view', 'edit' ),
  1429. 'items' => array(
  1430. 'type' => 'object',
  1431. 'properties' => array(
  1432. 'id' => array(
  1433. 'description' => __( 'Meta ID.', 'woocommerce' ),
  1434. 'type' => 'integer',
  1435. 'context' => array( 'view', 'edit' ),
  1436. 'readonly' => true,
  1437. ),
  1438. 'key' => array(
  1439. 'description' => __( 'Meta key.', 'woocommerce' ),
  1440. 'type' => 'string',
  1441. 'context' => array( 'view', 'edit' ),
  1442. ),
  1443. 'value' => array(
  1444. 'description' => __( 'Meta value.', 'woocommerce' ),
  1445. 'type' => 'mixed',
  1446. 'context' => array( 'view', 'edit' ),
  1447. ),
  1448. ),
  1449. ),
  1450. ),
  1451. ),
  1452. ),
  1453. ),
  1454. 'coupon_lines' => array(
  1455. 'description' => __( 'Coupons line data.', 'woocommerce' ),
  1456. 'type' => 'array',
  1457. 'context' => array( 'view', 'edit' ),
  1458. 'items' => array(
  1459. 'type' => 'object',
  1460. 'properties' => array(
  1461. 'id' => array(
  1462. 'description' => __( 'Item ID.', 'woocommerce' ),
  1463. 'type' => 'integer',
  1464. 'context' => array( 'view', 'edit' ),
  1465. 'readonly' => true,
  1466. ),
  1467. 'code' => array(
  1468. 'description' => __( 'Coupon code.', 'woocommerce' ),
  1469. 'type' => 'mixed',
  1470. 'context' => array( 'view', 'edit' ),
  1471. ),
  1472. 'discount' => array(
  1473. 'description' => __( 'Discount total.', 'woocommerce' ),
  1474. 'type' => 'string',
  1475. 'context' => array( 'view', 'edit' ),
  1476. ),
  1477. 'discount_tax' => array(
  1478. 'description' => __( 'Discount total tax.', 'woocommerce' ),
  1479. 'type' => 'string',
  1480. 'context' => array( 'view', 'edit' ),
  1481. 'readonly' => true,
  1482. ),
  1483. 'meta_data' => array(
  1484. 'description' => __( 'Meta data.', 'woocommerce' ),
  1485. 'type' => 'array',
  1486. 'context' => array( 'view', 'edit' ),
  1487. 'items' => array(
  1488. 'type' => 'object',
  1489. 'properties' => array(
  1490. 'id' => array(
  1491. 'description' => __( 'Meta ID.', 'woocommerce' ),
  1492. 'type' => 'integer',
  1493. 'context' => array( 'view', 'edit' ),
  1494. 'readonly' => true,
  1495. ),
  1496. 'key' => array(
  1497. 'description' => __( 'Meta key.', 'woocommerce' ),
  1498. 'type' => 'string',
  1499. 'context' => array( 'view', 'edit' ),
  1500. ),
  1501. 'value' => array(
  1502. 'description' => __( 'Meta value.', 'woocommerce' ),
  1503. 'type' => 'mixed',
  1504. 'context' => array( 'view', 'edit' ),
  1505. ),
  1506. ),
  1507. ),
  1508. ),
  1509. ),
  1510. ),
  1511. ),
  1512. 'refunds' => array(
  1513. 'description' => __( 'List of refunds.', 'woocommerce' ),
  1514. 'type' => 'array',
  1515. 'context' => array( 'view', 'edit' ),
  1516. 'readonly' => true,
  1517. 'items' => array(
  1518. 'type' => 'object',
  1519. 'properties' => array(
  1520. 'id' => array(
  1521. 'description' => __( 'Refund ID.', 'woocommerce' ),
  1522. 'type' => 'integer',
  1523. 'context' => array( 'view', 'edit' ),
  1524. 'readonly' => true,
  1525. ),
  1526. 'reason' => array(
  1527. 'description' => __( 'Refund reason.', 'woocommerce' ),
  1528. 'type' => 'string',
  1529. 'context' => array( 'view', 'edit' ),
  1530. 'readonly' => true,
  1531. ),
  1532. 'total' => array(
  1533. 'description' => __( 'Refund total.', 'woocommerce' ),
  1534. 'type' => 'string',
  1535. 'context' => array( 'view', 'edit' ),
  1536. 'readonly' => true,
  1537. ),
  1538. ),
  1539. ),
  1540. ),
  1541. 'set_paid' => array(
  1542. 'description' => __( 'Define if the order is paid. It will set the status to processing and reduce stock items.', 'woocommerce' ),
  1543. 'type' => 'boolean',
  1544. 'default' => false,
  1545. 'context' => array( 'edit' ),
  1546. ),
  1547. ),
  1548. );
  1549. return $this->add_additional_fields_schema( $schema );
  1550. }
  1551. /**
  1552. * Get the query params for collections.
  1553. *
  1554. * @return array
  1555. */
  1556. public function get_collection_params() {
  1557. $params = parent::get_collection_params();
  1558. $params['status'] = array(
  1559. 'default' => 'any',
  1560. 'description' => __( 'Limit result set to orders assigned a specific status.', 'woocommerce' ),
  1561. 'type' => 'string',
  1562. 'enum' => array_merge( array( 'any' ), $this->get_order_statuses() ),
  1563. 'sanitize_callback' => 'sanitize_key',
  1564. 'validate_callback' => 'rest_validate_request_arg',
  1565. );
  1566. $params['customer'] = array(
  1567. 'description' => __( 'Limit result set to orders assigned a specific customer.', 'woocommerce' ),
  1568. 'type' => 'integer',
  1569. 'sanitize_callback' => 'absint',
  1570. 'validate_callback' => 'rest_validate_request_arg',
  1571. );
  1572. $params['product'] = array(
  1573. 'description' => __( 'Limit result set to orders assigned a specific product.', 'woocommerce' ),
  1574. 'type' => 'integer',
  1575. 'sanitize_callback' => 'absint',
  1576. 'validate_callback' => 'rest_validate_request_arg',
  1577. );
  1578. $params['dp'] = array(
  1579. 'default' => wc_get_price_decimals(),
  1580. 'description' => __( 'Number of decimal points to use in each resource.', 'woocommerce' ),
  1581. 'type' => 'integer',
  1582. 'sanitize_callback' => 'absint',
  1583. 'validate_callback' => 'rest_validate_request_arg',
  1584. );
  1585. return $params;
  1586. }
  1587. }