/includes/api/v1/class-wc-rest-order-refunds-controller.php

  1. <?php 
  2. /** 
  3. * REST API Order Refunds controller 
  4. * 
  5. * Handles requests to the /orders/<order_id>/refunds endpoint. 
  6. * 
  7. * @author WooThemes 
  8. * @category API 
  9. * @package WooCommerce/API 
  10. * @since 2.6.0 
  11. */ 
  12.  
  13. if ( ! defined( 'ABSPATH' ) ) { 
  14. exit; 
  15.  
  16. /** 
  17. * REST API Order Refunds controller class. 
  18. * 
  19. * @package WooCommerce/API 
  20. * @extends WC_REST_Orders_V1_Controller 
  21. */ 
  22. class WC_REST_Order_Refunds_V1_Controller extends WC_REST_Orders_V1_Controller { 
  23.  
  24. /** 
  25. * Endpoint namespace. 
  26. * 
  27. * @var string 
  28. */ 
  29. protected $namespace = 'wc/v1'; 
  30.  
  31. /** 
  32. * Route base. 
  33. * 
  34. * @var string 
  35. */ 
  36. protected $rest_base = 'orders/(?P<order_id>[\d]+)/refunds'; 
  37.  
  38. /** 
  39. * Post type. 
  40. * 
  41. * @var string 
  42. */ 
  43. protected $post_type = 'shop_order_refund'; 
  44.  
  45. /** 
  46. * Order refunds actions. 
  47. */ 
  48. public function __construct() { 
  49. add_filter( "woocommerce_rest_{$this->post_type}_trashable", '__return_false' ); 
  50. add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 ); 
  51.  
  52. /** 
  53. * Register the routes for order refunds. 
  54. */ 
  55. public function register_routes() { 
  56. register_rest_route( $this->namespace, '/' . $this->rest_base, array( 
  57. 'args' => array( 
  58. 'order_id' => array( 
  59. 'description' => __( 'The order ID.', 'woocommerce' ),  
  60. 'type' => 'integer',  
  61. ),  
  62. ),  
  63. array( 
  64. 'methods' => WP_REST_Server::READABLE,  
  65. 'callback' => array( $this, 'get_items' ),  
  66. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  67. 'args' => $this->get_collection_params(),  
  68. ),  
  69. array( 
  70. 'methods' => WP_REST_Server::CREATABLE,  
  71. 'callback' => array( $this, 'create_item' ),  
  72. 'permission_callback' => array( $this, 'create_item_permissions_check' ),  
  73. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),  
  74. ),  
  75. 'schema' => array( $this, 'get_public_item_schema' ),  
  76. ) ); 
  77.  
  78. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  79. 'args' => array( 
  80. 'order_id' => array( 
  81. 'description' => __( 'The order ID.', 'woocommerce' ),  
  82. 'type' => 'integer',  
  83. ),  
  84. 'id' => array( 
  85. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  86. 'type' => 'integer',  
  87. ),  
  88. ),  
  89. array( 
  90. 'methods' => WP_REST_Server::READABLE,  
  91. 'callback' => array( $this, 'get_item' ),  
  92. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  93. 'args' => array( 
  94. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  95. ),  
  96. ),  
  97. array( 
  98. 'methods' => WP_REST_Server::DELETABLE,  
  99. 'callback' => array( $this, 'delete_item' ),  
  100. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  101. 'args' => array( 
  102. 'force' => array( 
  103. 'default' => true,  
  104. 'type' => 'boolean',  
  105. 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),  
  106. ),  
  107. ),  
  108. ),  
  109. 'schema' => array( $this, 'get_public_item_schema' ),  
  110. ) ); 
  111.  
  112. /** 
  113. * Prepare a single order refund output for response. 
  114. * 
  115. * @param WP_Post $post Post object. 
  116. * @param WP_REST_Request $request Request object. 
  117. * @return WP_REST_Response $data 
  118. */ 
  119. public function prepare_item_for_response( $post, $request ) { 
  120. $order = wc_get_order( (int) $request['order_id'] ); 
  121.  
  122. if ( ! $order ) { 
  123. return new WP_Error( 'woocommerce_rest_invalid_order_id', __( 'Invalid order ID.', 'woocommerce' ), 404 ); 
  124.  
  125. $refund = wc_get_order( $post ); 
  126.  
  127. if ( ! $refund || $refund->get_parent_id() !== $order->get_id() ) { 
  128. return new WP_Error( 'woocommerce_rest_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 404 ); 
  129.  
  130. $dp = $request['dp']; 
  131.  
  132. $data = array( 
  133. 'id' => $refund->get_id(),  
  134. 'date_created' => wc_rest_prepare_date_response( $refund->get_date_created() ),  
  135. 'amount' => wc_format_decimal( $refund->get_amount(), $dp ),  
  136. 'reason' => $refund->get_reason(),  
  137. 'line_items' => array(),  
  138. ); 
  139.  
  140. // Add line items. 
  141. foreach ( $refund->get_items() as $item_id => $item ) { 
  142. $product = $refund->get_product_from_item( $item ); 
  143. $product_id = 0; 
  144. $variation_id = 0; 
  145. $product_sku = null; 
  146.  
  147. // Check if the product exists. 
  148. if ( is_object( $product ) ) { 
  149. $product_id = $item->get_product_id(); 
  150. $variation_id = $item->get_variation_id(); 
  151. $product_sku = $product->get_sku(); 
  152.  
  153. $meta = new WC_Order_Item_Meta( $item, $product ); 
  154.  
  155. $item_meta = array(); 
  156.  
  157. $hideprefix = 'true' === $request['all_item_meta'] ? null : '_'; 
  158.  
  159. foreach ( $meta->get_formatted( $hideprefix ) as $meta_key => $formatted_meta ) { 
  160. $item_meta[] = array( 
  161. 'key' => $formatted_meta['key'],  
  162. 'label' => $formatted_meta['label'],  
  163. 'value' => $formatted_meta['value'],  
  164. ); 
  165.  
  166. $line_item = array( 
  167. 'id' => $item_id,  
  168. 'name' => $item['name'],  
  169. 'sku' => $product_sku,  
  170. 'product_id' => (int) $product_id,  
  171. 'variation_id' => (int) $variation_id,  
  172. 'quantity' => wc_stock_amount( $item['qty'] ),  
  173. 'tax_class' => ! empty( $item['tax_class'] ) ? $item['tax_class'] : '',  
  174. 'price' => wc_format_decimal( $refund->get_item_total( $item, false, false ), $dp ),  
  175. 'subtotal' => wc_format_decimal( $refund->get_line_subtotal( $item, false, false ), $dp ),  
  176. 'subtotal_tax' => wc_format_decimal( $item['line_subtotal_tax'], $dp ),  
  177. 'total' => wc_format_decimal( $refund->get_line_total( $item, false, false ), $dp ),  
  178. 'total_tax' => wc_format_decimal( $item['line_tax'], $dp ),  
  179. 'taxes' => array(),  
  180. 'meta' => $item_meta,  
  181. ); 
  182.  
  183. $item_line_taxes = maybe_unserialize( $item['line_tax_data'] ); 
  184. if ( isset( $item_line_taxes['total'] ) ) { 
  185. $line_tax = array(); 
  186.  
  187. foreach ( $item_line_taxes['total'] as $tax_rate_id => $tax ) { 
  188. $line_tax[ $tax_rate_id ] = array( 
  189. 'id' => $tax_rate_id,  
  190. 'total' => $tax,  
  191. 'subtotal' => '',  
  192. ); 
  193.  
  194. foreach ( $item_line_taxes['subtotal'] as $tax_rate_id => $tax ) { 
  195. $line_tax[ $tax_rate_id ]['subtotal'] = $tax; 
  196.  
  197. $line_item['taxes'] = array_values( $line_tax ); 
  198.  
  199. $data['line_items'][] = $line_item; 
  200.  
  201. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  202. $data = $this->add_additional_fields_to_object( $data, $request ); 
  203. $data = $this->filter_response_by_context( $data, $context ); 
  204.  
  205. // Wrap the data in a response object. 
  206. $response = rest_ensure_response( $data ); 
  207.  
  208. $response->add_links( $this->prepare_links( $refund, $request ) ); 
  209.  
  210. /** 
  211. * Filter the data for a response. 
  212. * 
  213. * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 
  214. * prepared for the response. 
  215. * 
  216. * @param WP_REST_Response $response The response object. 
  217. * @param WP_Post $post Post object. 
  218. * @param WP_REST_Request $request Request object. 
  219. */ 
  220. return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request ); 
  221.  
  222. /** 
  223. * Prepare links for the request. 
  224. * 
  225. * @param WC_Order_Refund $refund Comment object. 
  226. * @param WP_REST_Request $request Request object. 
  227. * @return array Links for the given order refund. 
  228. */ 
  229. protected function prepare_links( $refund, $request ) { 
  230. $order_id = $refund->get_parent_id(); 
  231. $base = str_replace( '(?P<order_id>[\d]+)', $order_id, $this->rest_base ); 
  232. $links = array( 
  233. 'self' => array( 
  234. 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $refund->get_id() ) ),  
  235. ),  
  236. 'collection' => array( 
  237. 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),  
  238. ),  
  239. 'up' => array( 
  240. 'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $order_id ) ),  
  241. ),  
  242. ); 
  243.  
  244. return $links; 
  245.  
  246. /** 
  247. * Query args. 
  248. * 
  249. * @param array $args Request args. 
  250. * @param WP_REST_Request $request Request object. 
  251. * @return array 
  252. */ 
  253. public function query_args( $args, $request ) { 
  254. $args['post_status'] = array_keys( wc_get_order_statuses() ); 
  255. $args['post_parent__in'] = array( absint( $request['order_id'] ) ); 
  256.  
  257. return $args; 
  258.  
  259. /** 
  260. * Create a single item. 
  261. * 
  262. * @param WP_REST_Request $request Full details about the request. 
  263. * @return WP_Error|WP_REST_Response 
  264. */ 
  265. public function create_item( $request ) { 
  266. if ( ! empty( $request['id'] ) ) { 
  267. /** translators: %s: post type */ 
  268. return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) ); 
  269.  
  270. $order_data = get_post( (int) $request['order_id'] ); 
  271.  
  272. if ( empty( $order_data ) ) { 
  273. return new WP_Error( 'woocommerce_rest_invalid_order', __( 'Order is invalid', 'woocommerce' ), 400 ); 
  274.  
  275. if ( 0 > $request['amount'] ) { 
  276. return new WP_Error( 'woocommerce_rest_invalid_order_refund', __( 'Refund amount must be greater than zero.', 'woocommerce' ), 400 ); 
  277.  
  278. // Create the refund. 
  279. $refund = wc_create_refund( array( 
  280. 'order_id' => $order_data->ID,  
  281. 'amount' => $request['amount'],  
  282. 'reason' => empty( $request['reason'] ) ? null : $request['reason'],  
  283. 'line_items' => $request['line_items'],  
  284. 'refund_payment' => is_bool( $request['api_refund'] ) ? $request['api_refund'] : true,  
  285. 'restock_items' => true,  
  286. ) ); 
  287.  
  288. if ( is_wp_error( $refund ) ) { 
  289. return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', $refund->get_error_message(), 500 ); 
  290.  
  291. if ( ! $refund ) { 
  292. return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 ); 
  293.  
  294. $post = get_post( $refund->get_id() ); 
  295. $this->update_additional_fields_for_object( $post, $request ); 
  296.  
  297. /** 
  298. * Fires after a single item is created or updated via the REST API. 
  299. * 
  300. * @param WP_Post $post Post object. 
  301. * @param WP_REST_Request $request Request object. 
  302. * @param boolean $creating True when creating item, false when updating. 
  303. */ 
  304. do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true ); 
  305.  
  306. $request->set_param( 'context', 'edit' ); 
  307. $response = $this->prepare_item_for_response( $post, $request ); 
  308. $response = rest_ensure_response( $response ); 
  309. $response->set_status( 201 ); 
  310. $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) ); 
  311.  
  312. return $response; 
  313.  
  314. /** 
  315. * Get the Order's schema, conforming to JSON Schema. 
  316. * 
  317. * @return array 
  318. */ 
  319. public function get_item_schema() { 
  320. $schema = array( 
  321. '$schema' => 'http://json-schema.org/draft-04/schema#',  
  322. 'title' => $this->post_type,  
  323. 'type' => 'object',  
  324. 'properties' => array( 
  325. 'id' => array( 
  326. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  327. 'type' => 'integer',  
  328. 'context' => array( 'view', 'edit' ),  
  329. 'readonly' => true,  
  330. ),  
  331. 'date_created' => array( 
  332. 'description' => __( "The date the order refund was created, in the site's timezone.", 'woocommerce' ),  
  333. 'type' => 'date-time',  
  334. 'context' => array( 'view', 'edit' ),  
  335. 'readonly' => true,  
  336. ),  
  337. 'amount' => array( 
  338. 'description' => __( 'Refund amount.', 'woocommerce' ),  
  339. 'type' => 'string',  
  340. 'context' => array( 'view', 'edit' ),  
  341. ),  
  342. 'reason' => array( 
  343. 'description' => __( 'Reason for refund.', 'woocommerce' ),  
  344. 'type' => 'string',  
  345. 'context' => array( 'view', 'edit' ),  
  346. ),  
  347. 'line_items' => array( 
  348. 'description' => __( 'Line items data.', 'woocommerce' ),  
  349. 'type' => 'array',  
  350. 'context' => array( 'view', 'edit' ),  
  351. 'items' => array( 
  352. 'type' => 'object',  
  353. 'properties' => array( 
  354. 'id' => array( 
  355. 'description' => __( 'Item ID.', 'woocommerce' ),  
  356. 'type' => 'integer',  
  357. 'context' => array( 'view', 'edit' ),  
  358. 'readonly' => true,  
  359. ),  
  360. 'name' => array( 
  361. 'description' => __( 'Product name.', 'woocommerce' ),  
  362. 'type' => 'string',  
  363. 'context' => array( 'view', 'edit' ),  
  364. 'readonly' => true,  
  365. ),  
  366. 'sku' => array( 
  367. 'description' => __( 'Product SKU.', 'woocommerce' ),  
  368. 'type' => 'string',  
  369. 'context' => array( 'view', 'edit' ),  
  370. 'readonly' => true,  
  371. ),  
  372. 'product_id' => array( 
  373. 'description' => __( 'Product ID.', 'woocommerce' ),  
  374. 'type' => 'integer',  
  375. 'context' => array( 'view', 'edit' ),  
  376. ),  
  377. 'variation_id' => array( 
  378. 'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),  
  379. 'type' => 'integer',  
  380. 'context' => array( 'view', 'edit' ),  
  381. ),  
  382. 'quantity' => array( 
  383. 'description' => __( 'Quantity ordered.', 'woocommerce' ),  
  384. 'type' => 'integer',  
  385. 'context' => array( 'view', 'edit' ),  
  386. ),  
  387. 'tax_class' => array( 
  388. 'description' => __( 'Tax class of product.', 'woocommerce' ),  
  389. 'type' => 'string',  
  390. 'context' => array( 'view', 'edit' ),  
  391. 'readonly' => true,  
  392. ),  
  393. 'price' => array( 
  394. 'description' => __( 'Product price.', 'woocommerce' ),  
  395. 'type' => 'string',  
  396. 'context' => array( 'view', 'edit' ),  
  397. 'readonly' => true,  
  398. ),  
  399. 'subtotal' => array( 
  400. 'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),  
  401. 'type' => 'string',  
  402. 'context' => array( 'view', 'edit' ),  
  403. ),  
  404. 'subtotal_tax' => array( 
  405. 'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),  
  406. 'type' => 'string',  
  407. 'context' => array( 'view', 'edit' ),  
  408. ),  
  409. 'total' => array( 
  410. 'description' => __( 'Line total (after discounts).', 'woocommerce' ),  
  411. 'type' => 'string',  
  412. 'context' => array( 'view', 'edit' ),  
  413. ),  
  414. 'total_tax' => array( 
  415. 'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),  
  416. 'type' => 'string',  
  417. 'context' => array( 'view', 'edit' ),  
  418. ),  
  419. 'taxes' => array( 
  420. 'description' => __( 'Line taxes.', 'woocommerce' ),  
  421. 'type' => 'array',  
  422. 'context' => array( 'view', 'edit' ),  
  423. 'readonly' => true,  
  424. 'items' => array( 
  425. 'type' => 'object',  
  426. 'properties' => array( 
  427. 'id' => array( 
  428. 'description' => __( 'Tax rate ID.', 'woocommerce' ),  
  429. 'type' => 'integer',  
  430. 'context' => array( 'view', 'edit' ),  
  431. 'readonly' => true,  
  432. ),  
  433. 'total' => array( 
  434. 'description' => __( 'Tax total.', 'woocommerce' ),  
  435. 'type' => 'string',  
  436. 'context' => array( 'view', 'edit' ),  
  437. 'readonly' => true,  
  438. ),  
  439. 'subtotal' => array( 
  440. 'description' => __( 'Tax subtotal.', 'woocommerce' ),  
  441. 'type' => 'string',  
  442. 'context' => array( 'view', 'edit' ),  
  443. 'readonly' => true,  
  444. ),  
  445. ),  
  446. ),  
  447. ),  
  448. 'meta' => array( 
  449. 'description' => __( 'Line item meta data.', 'woocommerce' ),  
  450. 'type' => 'array',  
  451. 'context' => array( 'view', 'edit' ),  
  452. 'readonly' => true,  
  453. 'items' => array( 
  454. 'type' => 'object',  
  455. 'properties' => array( 
  456. 'key' => array( 
  457. 'description' => __( 'Meta key.', 'woocommerce' ),  
  458. 'type' => 'string',  
  459. 'context' => array( 'view', 'edit' ),  
  460. 'readonly' => true,  
  461. ),  
  462. 'label' => array( 
  463. 'description' => __( 'Meta label.', 'woocommerce' ),  
  464. 'type' => 'string',  
  465. 'context' => array( 'view', 'edit' ),  
  466. 'readonly' => true,  
  467. ),  
  468. 'value' => array( 
  469. 'description' => __( 'Meta value.', 'woocommerce' ),  
  470. 'type' => 'string',  
  471. 'context' => array( 'view', 'edit' ),  
  472. 'readonly' => true,  
  473. ),  
  474. ),  
  475. ),  
  476. ),  
  477. ),  
  478. ),  
  479. ),  
  480. ),  
  481. ); 
  482.  
  483. return $this->add_additional_fields_schema( $schema ); 
  484.  
  485. /** 
  486. * Get the query params for collections. 
  487. * 
  488. * @return array 
  489. */ 
  490. public function get_collection_params() { 
  491. $params = parent::get_collection_params(); 
  492.  
  493. $params['dp'] = array( 
  494. 'default' => 2,  
  495. 'description' => __( 'Number of decimal points to use in each resource.', 'woocommerce' ),  
  496. 'type' => 'integer',  
  497. 'sanitize_callback' => 'absint',  
  498. 'validate_callback' => 'rest_validate_request_arg',  
  499. ); 
  500.  
  501. return $params; 
.