|
1
|
|
|
<?php |
|
|
|
|
|
|
2
|
|
|
/** |
|
3
|
|
|
* WooCommerce API Orders Class |
|
4
|
|
|
* |
|
5
|
|
|
* Handles requests to the /orders endpoint |
|
6
|
|
|
* |
|
7
|
|
|
* @author WooThemes |
|
8
|
|
|
* @category API |
|
9
|
|
|
* @package WooCommerce/API |
|
10
|
|
|
* @since 2.1 |
|
11
|
|
|
* @version 2.1 |
|
12
|
|
|
*/ |
|
13
|
|
|
|
|
14
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
|
15
|
|
|
exit; // Exit if accessed directly |
|
16
|
|
|
} |
|
17
|
|
|
|
|
18
|
|
|
class WC_API_Orders extends WC_API_Resource { |
|
19
|
|
|
|
|
20
|
|
|
/** @var string $base the route base */ |
|
21
|
|
|
protected $base = '/orders'; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* Register the routes for this class |
|
25
|
|
|
* |
|
26
|
|
|
* GET /orders |
|
27
|
|
|
* GET /orders/count |
|
28
|
|
|
* GET|PUT /orders/<id> |
|
29
|
|
|
* GET /orders/<id>/notes |
|
30
|
|
|
* |
|
31
|
|
|
* @since 2.1 |
|
32
|
|
|
* @param array $routes |
|
33
|
|
|
* @return array |
|
34
|
|
|
*/ |
|
35
|
|
|
public function register_routes( $routes ) { |
|
36
|
|
|
|
|
37
|
|
|
# GET /orders |
|
38
|
|
|
$routes[ $this->base ] = array( |
|
39
|
|
|
array( array( $this, 'get_orders' ), WC_API_Server::READABLE ), |
|
40
|
|
|
); |
|
41
|
|
|
|
|
42
|
|
|
# GET /orders/count |
|
43
|
|
|
$routes[ $this->base . '/count'] = array( |
|
44
|
|
|
array( array( $this, 'get_orders_count' ), WC_API_Server::READABLE ), |
|
45
|
|
|
); |
|
46
|
|
|
|
|
47
|
|
|
# GET|PUT /orders/<id> |
|
48
|
|
|
$routes[ $this->base . '/(?P<id>\d+)' ] = array( |
|
49
|
|
|
array( array( $this, 'get_order' ), WC_API_Server::READABLE ), |
|
50
|
|
|
array( array( $this, 'edit_order' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ), |
|
51
|
|
|
); |
|
52
|
|
|
|
|
53
|
|
|
# GET /orders/<id>/notes |
|
54
|
|
|
$routes[ $this->base . '/(?P<id>\d+)/notes' ] = array( |
|
55
|
|
|
array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ), |
|
56
|
|
|
); |
|
57
|
|
|
|
|
58
|
|
|
return $routes; |
|
59
|
|
|
} |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Get all orders |
|
63
|
|
|
* |
|
64
|
|
|
* @since 2.1 |
|
65
|
|
|
* @param string $fields |
|
66
|
|
|
* @param array $filter |
|
67
|
|
|
* @param string $status |
|
68
|
|
|
* @param int $page |
|
69
|
|
|
* @return array |
|
70
|
|
|
*/ |
|
71
|
|
View Code Duplication |
public function get_orders( $fields = null, $filter = array(), $status = null, $page = 1 ) { |
|
|
|
|
|
|
72
|
|
|
|
|
73
|
|
|
if ( ! empty( $status ) ) |
|
74
|
|
|
$filter['status'] = $status; |
|
75
|
|
|
|
|
76
|
|
|
$filter['page'] = $page; |
|
77
|
|
|
|
|
78
|
|
|
$query = $this->query_orders( $filter ); |
|
79
|
|
|
|
|
80
|
|
|
$orders = array(); |
|
81
|
|
|
|
|
82
|
|
|
foreach( $query->posts as $order_id ) { |
|
83
|
|
|
|
|
84
|
|
|
if ( ! $this->is_readable( $order_id ) ) |
|
85
|
|
|
continue; |
|
86
|
|
|
|
|
87
|
|
|
$orders[] = current( $this->get_order( $order_id, $fields ) ); |
|
|
|
|
|
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
$this->server->add_pagination_headers( $query ); |
|
91
|
|
|
|
|
92
|
|
|
return array( 'orders' => $orders ); |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
|
|
96
|
|
|
/** |
|
97
|
|
|
* Get the order for the given ID |
|
98
|
|
|
* |
|
99
|
|
|
* @since 2.1 |
|
100
|
|
|
* @param int $id the order ID |
|
101
|
|
|
* @param array $fields |
|
102
|
|
|
* @return array |
|
103
|
|
|
*/ |
|
104
|
|
|
public function get_order( $id, $fields = null ) { |
|
105
|
|
|
|
|
106
|
|
|
// ensure order ID is valid & user has permission to read |
|
107
|
|
|
$id = $this->validate_request( $id, 'shop_order', 'read' ); |
|
108
|
|
|
|
|
109
|
|
|
if ( is_wp_error( $id ) ) |
|
110
|
|
|
return $id; |
|
111
|
|
|
|
|
112
|
|
|
$order = wc_get_order( $id ); |
|
113
|
|
|
|
|
114
|
|
|
$order_post = get_post( $id ); |
|
115
|
|
|
|
|
116
|
|
|
$order_data = array( |
|
117
|
|
|
'id' => $order->id, |
|
118
|
|
|
'order_number' => $order->get_order_number(), |
|
119
|
|
|
'created_at' => $this->server->format_datetime( $order_post->post_date_gmt ), |
|
120
|
|
|
'updated_at' => $this->server->format_datetime( $order_post->post_modified_gmt ), |
|
121
|
|
|
'completed_at' => $this->server->format_datetime( $order->completed_date, true ), |
|
122
|
|
|
'status' => $order->get_status(), |
|
123
|
|
|
'currency' => $order->order_currency, |
|
124
|
|
|
'total' => wc_format_decimal( $order->get_total(), 2 ), |
|
125
|
|
|
'subtotal' => wc_format_decimal( $this->get_order_subtotal( $order ), 2 ), |
|
126
|
|
|
'total_line_items_quantity' => $order->get_item_count(), |
|
127
|
|
|
'total_tax' => wc_format_decimal( $order->get_total_tax(), 2 ), |
|
128
|
|
|
'total_shipping' => wc_format_decimal( $order->get_total_shipping(), 2 ), |
|
129
|
|
|
'cart_tax' => wc_format_decimal( $order->get_cart_tax(), 2 ), |
|
130
|
|
|
'shipping_tax' => wc_format_decimal( $order->get_shipping_tax(), 2 ), |
|
131
|
|
|
'total_discount' => wc_format_decimal( $order->get_total_discount(), 2 ), |
|
132
|
|
|
'cart_discount' => wc_format_decimal( $order->get_cart_discount(), 2 ), |
|
|
|
|
|
|
133
|
|
|
'order_discount' => wc_format_decimal( $order->get_order_discount(), 2 ), |
|
|
|
|
|
|
134
|
|
|
'shipping_methods' => $order->get_shipping_method(), |
|
135
|
|
|
'payment_details' => array( |
|
136
|
|
|
'method_id' => $order->payment_method, |
|
137
|
|
|
'method_title' => $order->payment_method_title, |
|
138
|
|
|
'paid' => isset( $order->paid_date ), |
|
139
|
|
|
), |
|
140
|
|
|
'billing_address' => array( |
|
141
|
|
|
'first_name' => $order->billing_first_name, |
|
142
|
|
|
'last_name' => $order->billing_last_name, |
|
143
|
|
|
'company' => $order->billing_company, |
|
144
|
|
|
'address_1' => $order->billing_address_1, |
|
145
|
|
|
'address_2' => $order->billing_address_2, |
|
146
|
|
|
'city' => $order->billing_city, |
|
147
|
|
|
'state' => $order->billing_state, |
|
148
|
|
|
'postcode' => $order->billing_postcode, |
|
149
|
|
|
'country' => $order->billing_country, |
|
150
|
|
|
'email' => $order->billing_email, |
|
151
|
|
|
'phone' => $order->billing_phone, |
|
152
|
|
|
), |
|
153
|
|
|
'shipping_address' => array( |
|
154
|
|
|
'first_name' => $order->shipping_first_name, |
|
155
|
|
|
'last_name' => $order->shipping_last_name, |
|
156
|
|
|
'company' => $order->shipping_company, |
|
157
|
|
|
'address_1' => $order->shipping_address_1, |
|
158
|
|
|
'address_2' => $order->shipping_address_2, |
|
159
|
|
|
'city' => $order->shipping_city, |
|
160
|
|
|
'state' => $order->shipping_state, |
|
161
|
|
|
'postcode' => $order->shipping_postcode, |
|
162
|
|
|
'country' => $order->shipping_country, |
|
163
|
|
|
), |
|
164
|
|
|
'note' => $order->customer_note, |
|
165
|
|
|
'customer_ip' => $order->customer_ip_address, |
|
166
|
|
|
'customer_user_agent' => $order->customer_user_agent, |
|
167
|
|
|
'customer_id' => $order->customer_user, |
|
168
|
|
|
'view_order_url' => $order->get_view_order_url(), |
|
169
|
|
|
'line_items' => array(), |
|
170
|
|
|
'shipping_lines' => array(), |
|
171
|
|
|
'tax_lines' => array(), |
|
172
|
|
|
'fee_lines' => array(), |
|
173
|
|
|
'coupon_lines' => array(), |
|
174
|
|
|
); |
|
175
|
|
|
|
|
176
|
|
|
// add line items |
|
177
|
|
|
foreach( $order->get_items() as $item_id => $item ) { |
|
178
|
|
|
|
|
179
|
|
|
$product = $order->get_product_from_item( $item ); |
|
180
|
|
|
|
|
181
|
|
|
$order_data['line_items'][] = array( |
|
182
|
|
|
'id' => $item_id, |
|
183
|
|
|
'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item ), 2 ), |
|
184
|
|
|
'total' => wc_format_decimal( $order->get_line_total( $item ), 2 ), |
|
185
|
|
|
'total_tax' => wc_format_decimal( $order->get_line_tax( $item ), 2 ), |
|
186
|
|
|
'price' => wc_format_decimal( $order->get_item_total( $item ), 2 ), |
|
187
|
|
|
'quantity' => (int) $item['qty'], |
|
188
|
|
|
'tax_class' => ( ! empty( $item['tax_class'] ) ) ? $item['tax_class'] : null, |
|
189
|
|
|
'name' => $item['name'], |
|
190
|
|
|
'product_id' => ( isset( $product->variation_id ) ) ? $product->variation_id : $product->id, |
|
191
|
|
|
'sku' => is_object( $product ) ? $product->get_sku() : null, |
|
192
|
|
|
); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
// add shipping |
|
196
|
|
|
foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) { |
|
197
|
|
|
|
|
198
|
|
|
$order_data['shipping_lines'][] = array( |
|
199
|
|
|
'id' => $shipping_item_id, |
|
200
|
|
|
'method_id' => $shipping_item['method_id'], |
|
201
|
|
|
'method_title' => $shipping_item['name'], |
|
202
|
|
|
'total' => wc_format_decimal( $shipping_item['cost'], 2 ), |
|
203
|
|
|
); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
// add taxes |
|
207
|
|
|
foreach ( $order->get_tax_totals() as $tax_code => $tax ) { |
|
208
|
|
|
|
|
209
|
|
|
$order_data['tax_lines'][] = array( |
|
210
|
|
|
'code' => $tax_code, |
|
211
|
|
|
'title' => $tax->label, |
|
212
|
|
|
'total' => wc_format_decimal( $tax->amount, 2 ), |
|
213
|
|
|
'compound' => (bool) $tax->is_compound, |
|
214
|
|
|
); |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
// add fees |
|
218
|
|
View Code Duplication |
foreach ( $order->get_fees() as $fee_item_id => $fee_item ) { |
|
|
|
|
|
|
219
|
|
|
|
|
220
|
|
|
$order_data['fee_lines'][] = array( |
|
221
|
|
|
'id' => $fee_item_id, |
|
222
|
|
|
'title' => $fee_item['name'], |
|
223
|
|
|
'tax_class' => ( ! empty( $fee_item['tax_class'] ) ) ? $fee_item['tax_class'] : null, |
|
224
|
|
|
'total' => wc_format_decimal( $order->get_line_total( $fee_item ), 2 ), |
|
225
|
|
|
'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), 2 ), |
|
226
|
|
|
); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
// add coupons |
|
230
|
|
View Code Duplication |
foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) { |
|
|
|
|
|
|
231
|
|
|
|
|
232
|
|
|
$order_data['coupon_lines'][] = array( |
|
233
|
|
|
'id' => $coupon_item_id, |
|
234
|
|
|
'code' => $coupon_item['name'], |
|
235
|
|
|
'amount' => wc_format_decimal( $coupon_item['discount_amount'], 2 ), |
|
236
|
|
|
); |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
return array( 'order' => apply_filters( 'woocommerce_api_order_response', $order_data, $order, $fields, $this->server ) ); |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* Get the total number of orders |
|
244
|
|
|
* |
|
245
|
|
|
* @since 2.1 |
|
246
|
|
|
* @param string $status |
|
247
|
|
|
* @param array $filter |
|
248
|
|
|
* @return array |
|
249
|
|
|
*/ |
|
250
|
|
View Code Duplication |
public function get_orders_count( $status = null, $filter = array() ) { |
|
|
|
|
|
|
251
|
|
|
|
|
252
|
|
|
if ( ! empty( $status ) ) |
|
253
|
|
|
$filter['status'] = $status; |
|
254
|
|
|
|
|
255
|
|
|
$query = $this->query_orders( $filter ); |
|
256
|
|
|
|
|
257
|
|
|
if ( ! current_user_can( 'read_private_shop_orders' ) ) |
|
258
|
|
|
return new WP_Error( 'woocommerce_api_user_cannot_read_orders_count', __( 'You do not have permission to read the orders count', 'woocommerce' ), array( 'status' => 401 ) ); |
|
259
|
|
|
|
|
260
|
|
|
return array( 'count' => (int) $query->found_posts ); |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
/** |
|
264
|
|
|
* Edit an order |
|
265
|
|
|
* |
|
266
|
|
|
* API v1 only allows updating the status of an order |
|
267
|
|
|
* |
|
268
|
|
|
* @since 2.1 |
|
269
|
|
|
* @param int $id the order ID |
|
270
|
|
|
* @param array $data |
|
271
|
|
|
* @return array |
|
272
|
|
|
*/ |
|
273
|
|
|
public function edit_order( $id, $data ) { |
|
274
|
|
|
|
|
275
|
|
|
$id = $this->validate_request( $id, 'shop_order', 'edit' ); |
|
276
|
|
|
|
|
277
|
|
|
if ( is_wp_error( $id ) ) |
|
278
|
|
|
return $id; |
|
279
|
|
|
|
|
280
|
|
|
$order = wc_get_order( $id ); |
|
281
|
|
|
|
|
282
|
|
|
if ( ! empty( $data['status'] ) ) { |
|
283
|
|
|
|
|
284
|
|
|
$order->update_status( $data['status'], isset( $data['note'] ) ? $data['note'] : '' ); |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
return $this->get_order( $id ); |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
/** |
|
291
|
|
|
* Delete an order |
|
292
|
|
|
* |
|
293
|
|
|
* @TODO enable along with POST in 2.2 |
|
294
|
|
|
* @param int $id the order ID |
|
295
|
|
|
* @param bool $force true to permanently delete order, false to move to trash |
|
296
|
|
|
* @return array |
|
297
|
|
|
*/ |
|
298
|
|
|
public function delete_order( $id, $force = false ) { |
|
299
|
|
|
|
|
300
|
|
|
$id = $this->validate_request( $id, 'shop_order', 'delete' ); |
|
301
|
|
|
|
|
302
|
|
|
return $this->delete( $id, 'order', ( 'true' === $force ) ); |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
/** |
|
306
|
|
|
* Get the admin order notes for an order |
|
307
|
|
|
* |
|
308
|
|
|
* @since 2.1 |
|
309
|
|
|
* @param int $id the order ID |
|
310
|
|
|
* @param string $fields fields to include in response |
|
311
|
|
|
* @return array |
|
312
|
|
|
*/ |
|
313
|
|
|
public function get_order_notes( $id, $fields = null ) { |
|
314
|
|
|
|
|
315
|
|
|
// ensure ID is valid order ID |
|
316
|
|
|
$id = $this->validate_request( $id, 'shop_order', 'read' ); |
|
317
|
|
|
|
|
318
|
|
|
if ( is_wp_error( $id ) ) |
|
319
|
|
|
return $id; |
|
320
|
|
|
|
|
321
|
|
|
$args = array( |
|
322
|
|
|
'post_id' => $id, |
|
323
|
|
|
'approve' => 'approve', |
|
324
|
|
|
'type' => 'order_note' |
|
325
|
|
|
); |
|
326
|
|
|
|
|
327
|
|
|
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); |
|
328
|
|
|
|
|
329
|
|
|
$notes = get_comments( $args ); |
|
330
|
|
|
|
|
331
|
|
|
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); |
|
332
|
|
|
|
|
333
|
|
|
$order_notes = array(); |
|
334
|
|
|
|
|
335
|
|
|
foreach ( $notes as $note ) { |
|
336
|
|
|
|
|
337
|
|
|
$order_notes[] = array( |
|
338
|
|
|
'id' => $note->comment_ID, |
|
339
|
|
|
'created_at' => $this->server->format_datetime( $note->comment_date_gmt ), |
|
340
|
|
|
'note' => $note->comment_content, |
|
341
|
|
|
'customer_note' => get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ? true : false, |
|
342
|
|
|
); |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
return array( 'order_notes' => apply_filters( 'woocommerce_api_order_notes_response', $order_notes, $id, $fields, $notes, $this->server ) ); |
|
346
|
|
|
} |
|
347
|
|
|
|
|
348
|
|
|
/** |
|
349
|
|
|
* Helper method to get order post objects |
|
350
|
|
|
* |
|
351
|
|
|
* @since 2.1 |
|
352
|
|
|
* @param array $args request arguments for filtering query |
|
353
|
|
|
* @return WP_Query |
|
354
|
|
|
*/ |
|
355
|
|
View Code Duplication |
private function query_orders( $args ) { |
|
|
|
|
|
|
356
|
|
|
|
|
357
|
|
|
// set base query arguments |
|
358
|
|
|
$query_args = array( |
|
359
|
|
|
'fields' => 'ids', |
|
360
|
|
|
'post_type' => 'shop_order', |
|
361
|
|
|
'post_status' => array_keys( wc_get_order_statuses() ) |
|
362
|
|
|
); |
|
363
|
|
|
|
|
364
|
|
|
// add status argument |
|
365
|
|
|
if ( ! empty( $args['status'] ) ) { |
|
366
|
|
|
|
|
367
|
|
|
$statuses = 'wc-' . str_replace( ',', ',wc-', $args['status'] ); |
|
368
|
|
|
$statuses = explode( ',', $statuses ); |
|
369
|
|
|
$query_args['post_status'] = $statuses; |
|
370
|
|
|
|
|
371
|
|
|
unset( $args['status'] ); |
|
372
|
|
|
|
|
373
|
|
|
} |
|
374
|
|
|
|
|
375
|
|
|
$query_args = $this->merge_query_args( $query_args, $args ); |
|
376
|
|
|
|
|
377
|
|
|
return new WP_Query( $query_args ); |
|
378
|
|
|
} |
|
379
|
|
|
|
|
380
|
|
|
/** |
|
381
|
|
|
* Helper method to get the order subtotal |
|
382
|
|
|
* |
|
383
|
|
|
* @since 2.1 |
|
384
|
|
|
* @param WC_Order $order |
|
385
|
|
|
* @return float |
|
386
|
|
|
*/ |
|
387
|
|
|
private function get_order_subtotal( $order ) { |
|
388
|
|
|
|
|
389
|
|
|
$subtotal = 0; |
|
390
|
|
|
|
|
391
|
|
|
// subtotal |
|
392
|
|
|
foreach ( $order->get_items() as $item ) { |
|
393
|
|
|
|
|
394
|
|
|
$subtotal += ( isset( $item['line_subtotal'] ) ) ? $item['line_subtotal'] : 0; |
|
395
|
|
|
} |
|
396
|
|
|
|
|
397
|
|
|
return $subtotal; |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
} |
|
401
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.