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

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