PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.7.5
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.7.5
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / classes / apis / class-api-for-woo-order.php
commercebird / includes / classes / apis Last commit date
class-api-for-cmbird.php 7 months ago class-api-for-exact-webhooks.php 5 months ago class-api-for-product-webhook.php 5 months ago class-api-for-shipping-status.php 9 months ago class-api-for-woo-order.php 4 months ago class-api-for-zoho-inventory.php 9 months ago class-commercebird-list-items-api-controller.php 10 months ago class-commercebird-media-api-controller.php 10 months ago class-commercebird-metadata-controller.php 5 months ago index.php 1 year ago trait-api-permission.php 7 months ago
class-api-for-woo-order.php
562 lines
1 <?php
2
3 namespace CommerceBird\API;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 use CommerceBird\Admin\Traits\LogWriter;
10 use WC_Coupon;
11 use WC_Customer;
12 use WP_REST_Response;
13
14 class CreateOrderWebhook {
15
16 use Api;
17 use LogWriter;
18
19 private static string $endpoint = 'create-woo-order';
20
21
22 public function __construct() {
23 register_rest_route(
24 self::$namespace,
25 self::$endpoint,
26 array(
27 'methods' => 'POST',
28 'callback' => array( $this, 'handle' ),
29 'permission_callback' => array( $this, 'permission_check' ),
30 )
31 );
32 }
33
34 /**
35 * @param $address
36 *
37 * @return array
38 */
39 public function format_address( $address ): array {
40 if ( array_key_exists( 'attention', $address ) ) {
41 $names = explode( ' ', $address['attention'] );
42 $first_name = $names[0] ?? '';
43 $last_name = $names[1] ?? '';
44 }
45 return array(
46 'first_name' => $first_name ?? '',
47 'last_name' => $last_name ?? '',
48 'address_1' => $address['address'] ?? '',
49 'address_2' => $address['street2'] ?? '',
50 'city' => $address['city'] ?? '',
51 'state' => $address['state_code'] ?? '',
52 'postcode' => $address['zip'] ?? '',
53 'country' => $address['country_code'] ?? '',
54 'phone' => $address['phone'] ?? '',
55 );
56 }
57
58 private function process( array $order_data ): WP_REST_Response {
59 $response = new WP_REST_Response();
60
61 // Check if the order data is valid.
62 if ( empty( $order_data ) || empty( $order_data['salesorder'] ) ) {
63 $response->set_data( $this->empty_response );
64 $response->set_status( 404 );
65 return $response;
66 }
67
68 // Validate salesorder_id exists.
69 if ( ! isset( $order_data['salesorder']['salesorder_id'] ) || empty( $order_data['salesorder']['salesorder_id'] ) ) {
70 $message = sprintf( __( 'Zoho order could not be created in your store %1$s because of missing order ID.', 'commercebird' ), get_bloginfo( 'name' ) );
71 $common_class = new \CMBIRD_Common_Functions();
72 $common_class->send_email( __( 'Zoho Order Sync', 'commercebird' ), $message );
73 $response->set_status( 500 );
74 $response->set_data( $message );
75 return $response;
76 }
77
78 $allowed_keys = array(
79 'salesorder_id',
80 'salesorder_number',
81 'customer_id',
82 'billing_address',
83 'shipping_address',
84 'delivery_method',
85 'currency_code',
86 'line_items',
87 'contact_person_details',
88 'shipping_charge',
89 'order_status',
90 'paid_status',
91 'invoiced_status',
92 'discount',
93 'discount_percent',
94 'notes',
95 'salesperson_name',
96 );
97
98 $order_data = array_intersect_key( $order_data['salesorder'], array_flip( $allowed_keys ) );
99 $billing_address = $this->format_address( $order_data['billing_address'] );
100 $shipping_address = $this->format_address( $order_data['shipping_address'] );
101
102 // Handle customer creation/update.
103 $customer = null;
104 if ( isset( $order_data['contact_person_details'][0]['email'] ) ) {
105 $customer_data = $order_data['contact_person_details'][0];
106 $customer_mail = $customer_data['email'];
107 $customer = get_user_by( 'email', $customer_mail );
108 if ( empty( $customer ) ) {
109 $customer_id = wc_create_new_customer( $customer_mail );
110 $wc_customer = new \WC_Customer( $customer_id );
111 // Set billing details.
112 $wc_customer->set_billing_first_name( $billing_address['first_name'] );
113 $wc_customer->set_billing_last_name( $billing_address['last_name'] );
114 $wc_customer->set_billing_address_1( $billing_address['address_1'] );
115 $wc_customer->set_billing_address_2( $billing_address['address_2'] );
116 $wc_customer->set_billing_city( $billing_address['city'] );
117 $wc_customer->set_billing_postcode( $billing_address['postcode'] );
118 $wc_customer->set_billing_country( $billing_address['country'] );
119 $wc_customer->set_billing_state( $billing_address['state'] );
120 $wc_customer->set_billing_email( $customer_mail );
121 $wc_customer->set_billing_phone( $billing_address['phone'] );
122 // Set shipping details.
123 $wc_customer->set_shipping_first_name( $shipping_address['first_name'] );
124 $wc_customer->set_shipping_last_name( $shipping_address['last_name'] );
125 $wc_customer->set_shipping_address_1( $shipping_address['address_1'] );
126 $wc_customer->set_shipping_address_2( $shipping_address['address_2'] );
127 $wc_customer->set_shipping_city( $shipping_address['city'] );
128 $wc_customer->set_shipping_postcode( $shipping_address['postcode'] );
129 $wc_customer->set_shipping_country( $shipping_address['country'] );
130 $wc_customer->set_shipping_state( $shipping_address['state'] );
131 $wc_customer->save();
132 $customer = get_user_by( 'id', $customer_id );
133 } else {
134 $wc_customer = new \WC_Customer( $customer->ID );
135 // Set billing details.
136 $wc_customer->set_billing_first_name( $billing_address['first_name'] );
137 $wc_customer->set_billing_last_name( $billing_address['last_name'] );
138 $wc_customer->set_billing_address_1( $billing_address['address_1'] );
139 $wc_customer->set_billing_address_2( $billing_address['address_2'] );
140 $wc_customer->set_billing_city( $billing_address['city'] );
141 $wc_customer->set_billing_postcode( $billing_address['postcode'] );
142 $wc_customer->set_billing_country( $billing_address['country'] );
143 $wc_customer->set_billing_state( $billing_address['state'] );
144 $wc_customer->set_billing_email( $customer_mail );
145 $wc_customer->set_billing_phone( $billing_address['phone'] );
146 // Set shipping details.
147 $wc_customer->set_shipping_first_name( $shipping_address['first_name'] );
148 $wc_customer->set_shipping_last_name( $shipping_address['last_name'] );
149 $wc_customer->set_shipping_address_1( $shipping_address['address_1'] );
150 $wc_customer->set_shipping_address_2( $shipping_address['address_2'] );
151 $wc_customer->set_shipping_city( $shipping_address['city'] );
152 $wc_customer->set_shipping_postcode( $shipping_address['postcode'] );
153 $wc_customer->set_shipping_country( $shipping_address['country'] );
154 $wc_customer->set_shipping_state( $shipping_address['state'] );
155 $wc_customer->save();
156 }
157 }
158
159 // Validate line items exist.
160 if ( empty( $order_data['line_items'] ) ) {
161 // translators: 1: order number, 2: Store name.
162 $message = sprintf( __( 'Zoho order #%1$s could not be created in your store %2$s because of missing line items.', 'commercebird' ), $order_data['salesorder_number'], get_bloginfo( 'name' ) );
163 $common_class = new \CMBIRD_Common_Functions();
164 $common_class->send_email( __( 'Zoho Order Sync', 'commercebird' ), $message );
165 $response->set_status( 500 );
166 $response->set_data( $message );
167 return $response;
168 }
169
170 $line_items = $this->get_items( $order_data['line_items'] );
171
172 if ( ! empty( $line_items['not_found'] ) ) {
173 // translators: 1: order number, 2: Store name, 3: missing items.
174 $message = sprintf( __( 'Zoho order #%1$s could not be created in your store %2$s because of missing items: %3$s', 'commercebird' ), $order_data['salesorder_number'], get_bloginfo( 'name' ), implode( ', ', $line_items['not_found'] ) );
175 $common_class = new \CMBIRD_Common_Functions();
176 $common_class->send_email( __( 'Zoho Order Sync', 'commercebird' ), $message );
177 $response->set_status( 500 );
178 $response->set_data( $message );
179 return $response;
180 }
181
182 $discount_percent = $order_data['discount_percent'] ?? '';
183 if ( ! empty( $discount_percent ) && $discount_percent > 0 ) {
184 $coupon_code = $this->get_coupon_code( $order_data );
185 }
186
187 // Check if order already exists.
188 $orders = wc_get_orders(
189 array(
190 'limit' => 1,
191 'meta_key' => 'zi_salesorder_id',
192 'meta_value' => $order_data['salesorder_id'],
193 'meta_compare' => '=',
194 'return' => 'ids',
195 )
196 );
197 if ( ! empty( $orders ) ) {
198 $existing_order = wc_get_order( $orders[0] );
199 }
200
201 // Update existing order.
202 if ( ! empty( $existing_order ) && empty( $line_items['not_found'] ) ) {
203 // if order status is completed or processing, do not update the order and return error response.
204 if ( in_array( $existing_order->get_status(), array( 'completed', 'processing' ), true ) ) {
205 $message = sprintf( __( 'Zoho order #%1$s could not be updated in your store %2$s because the order is already in %3$s status.', 'commercebird' ), $order_data['salesorder_number'], get_bloginfo( 'name' ), $existing_order->get_status() );
206 $common_class = new \CMBIRD_Common_Functions();
207 $common_class->send_email( __( 'Zoho Order Sync', 'commercebird' ), $message );
208 $response->set_status( 200 );
209 $response->set_data( $message );
210 return $response;
211 }
212
213 $existing_order->set_address( $billing_address, 'billing' );
214 $existing_order->set_address( $shipping_address, 'shipping' );
215 // Remove existing order items.
216 $existing_order->remove_order_items( 'line_item' );
217 // Get all applied coupons and remove them.
218 $applied_coupons = $existing_order->get_coupon_codes();
219 foreach ( $applied_coupons as $coupon_code_old ) {
220 $existing_order->remove_coupon( $coupon_code_old );
221 }
222 // Add products to the order.
223 foreach ( $line_items as $line_item ) {
224 $product_id = $line_item['id'];
225 $quantity = $line_item['quantity'];
226 $rate = $line_item['rate'] ?? null;
227 if ( ! empty( $product_id ) ) {
228 $product = wc_get_product( $product_id );
229 if ( $product && $product->is_type( 'variation' ) ) {
230 // Get variation object.
231 $variation = wc_get_product_object( 'variation', $product_id );
232 if ( $variation ) {
233 $item_id = $existing_order->add_product( $variation, $quantity );
234 if ( null !== $rate && $item_id ) {
235 $item = $existing_order->get_item( $item_id );
236 $line_total = $rate * $quantity;
237 $item->set_subtotal( $line_total );
238 $item->set_total( $line_total );
239 $item->save();
240 }
241 }
242 } else {
243 $item_id = $existing_order->add_product( $product, $quantity );
244 if ( null !== $rate && $item_id ) {
245 $item = $existing_order->get_item( $item_id );
246 $line_total = $rate * $quantity;
247 $item->set_subtotal( $line_total );
248 $item->set_total( $line_total );
249 $item->save();
250 }
251 }
252 }
253 }
254
255 // Set order details.
256 $existing_order->set_customer_note( isset( $order_data['notes'] ) ? $order_data['notes'] : '' );
257 $existing_order->update_status( $this->map_status( $order_data['paid_status'], $order_data['invoiced_status'] ?? '' ) );
258
259 // Handle shipping.
260 // Remove existing shipping items first.
261 foreach ( $existing_order->get_items( 'shipping' ) as $item_id => $item ) {
262 $existing_order->remove_item( $item_id );
263 }
264
265 if ( isset( $order_data['shipping_charge'] ) && $order_data['shipping_charge'] > 0 ) {
266 $shipping = new \WC_Order_Item_Shipping();
267 $shipping->set_method_title( $order_data['delivery_method'] ?? 'Flat Rate' );
268 $shipping->set_method_id( 'flat_rate' );
269 $shipping->set_total( $order_data['shipping_charge'] );
270 $existing_order->add_item( $shipping );
271 }
272
273 $existing_order->calculate_totals();
274 $existing_order->save();
275 $note = 'Updated in Zoho Inventory';
276 if ( ! empty( $order_data['salesperson_name'] ) ) {
277 $note .= ' by ' . $order_data['salesperson_name'];
278 }
279 $existing_order->add_order_note( $note );
280 // get new order object and calculate totals again.
281 $updated_order = wc_get_order( $existing_order->get_id() );
282 $updated_order->calculate_totals();
283 $updated_order->save();
284
285 $response->set_data(
286 array(
287 'order_id' => $existing_order->get_id(),
288 'total' => $existing_order->get_total(),
289 'status' => $existing_order->get_status(),
290 )
291 );
292 $response->set_status( 200 );
293 } elseif ( empty( $line_items['not_found'] ) ) {
294 // Create new order.
295 $order = wc_create_order();
296 if ( $customer ) {
297 $order->set_customer_id( $customer->ID );
298 }
299 // Add products to the order.
300 foreach ( $line_items as $line_item ) {
301 $product_id = $line_item['id'];
302 $quantity = $line_item['quantity'];
303 $rate = $line_item['rate'] ?? null;
304 if ( ! empty( $product_id ) ) {
305 $product = wc_get_product( $product_id );
306 if ( $product && $product->is_type( 'variation' ) ) {
307 // Get variation object.
308 $variation = wc_get_product_object( 'variation', $product_id );
309 if ( $variation ) {
310 $item_id = $order->add_product( $variation, $quantity );
311 if ( null !== $rate && $item_id ) {
312 $item = $order->get_item( $item_id );
313 $line_total = $rate * $quantity;
314 $item->set_subtotal( $line_total );
315 $item->set_total( $line_total );
316 $item->save();
317 }
318 }
319 } else {
320 $item_id = $order->add_product( $product, $quantity );
321 if ( null !== $rate && $item_id ) {
322 $item = $order->get_item( $item_id );
323 $line_total = $rate * $quantity;
324 $item->set_subtotal( $line_total );
325 $item->set_total( $line_total );
326 $item->save();
327 }
328 }
329 }
330 }
331
332 $order->set_address( $billing_address, 'billing' );
333 $order->set_address( $shipping_address, 'shipping' );
334 $order->set_currency( $order_data['currency_code'] );
335 $order->set_status( $this->map_status( $order_data['paid_status'], $order_data['invoiced_status'] ?? '' ) );
336 // Handle shipping.
337 if ( isset( $order_data['shipping_charge'] ) && $order_data['shipping_charge'] > 0 ) {
338 $shipping = new \WC_Order_Item_Shipping();
339 $shipping->set_method_title( $order_data['delivery_method'] ?? 'Flat Rate' );
340 $shipping->set_method_id( 'flat_rate' );
341 $shipping->set_total( $order_data['shipping_charge'] );
342 $order->add_item( $shipping );
343 }
344
345 // Apply coupon if discount exists.
346 if ( isset( $coupon_code ) && ! empty( $coupon_code ) ) {
347 $order->apply_coupon( $coupon_code );
348 }
349 $order->calculate_totals();
350 $order->set_customer_note( $order_data['notes'] ?? '' );
351 $order->save();
352 $order->update_meta_data( 'zi_salesorder_id', $order_data['salesorder_id'] );
353 $order->save();
354 $note = 'Synced in Zoho Inventory';
355 if ( ! empty( $order_data['salesperson_name'] ) ) {
356 $note .= ' by ' . $order_data['salesperson_name'];
357 }
358 $order->add_order_note( $note );
359
360 // Update Zoho sales order with WooCommerce order ID as reference number.
361 $this->update_zoho_reference_number( $order, $order_data['salesorder_id'] );
362
363 $response->set_data(
364 array(
365 'order_id' => $order->get_id(),
366 'status' => $order->get_status(),
367 )
368 );
369 $response->set_status( 200 );
370 } else {
371 $response->set_data(
372 array(
373 'status' => 'Something went wrong. Please try again.',
374 )
375 );
376 $response->set_status( 500 );
377 }
378
379 return $response;
380 }
381
382
383 private function map_status( $paid_status, $invoiced_status = '' ): string {
384 // If invoiced, status must be processing.
385 if ( 'invoiced' === $invoiced_status ) {
386 return 'wc-processing';
387 }
388
389 // Otherwise check paid status.
390 switch ( $paid_status ) {
391 case 'paid':
392 return 'wc-completed';
393 default:
394 return 'wc-pending';
395 }
396 }
397
398
399 /**
400 * Update Zoho sales order reference number with WooCommerce order ID.
401 *
402 * @param \WC_Order $order WooCommerce order object.
403 * @param string $salesorder_id Zoho sales order ID.
404 * @return void
405 */
406 private function update_zoho_reference_number( $order, $salesorder_id ): void {
407 // Get Zoho Inventory configuration.
408 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
409 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
410
411 if ( empty( $zoho_inventory_url ) || empty( $zoho_inventory_oid ) || empty( $salesorder_id ) ) {
412 return;
413 }
414
415 // Prepare reference number using the same logic as order sync.
416 $transaction_id = $order->get_transaction_id();
417 if ( empty( $transaction_id ) ) {
418 $transaction_id = $order->get_meta( '_order_number', true );
419 }
420
421 $order_prefix = get_option( 'cmbird_zoho_order_prefix_status' );
422 $reference_number = '';
423
424 if ( class_exists( 'WCJ_Order_Numbers' ) || class_exists( 'WC_Seq_Order_Number_Pro' ) ) {
425 $reference_number = $order_prefix . $transaction_id;
426 } elseif ( ! empty( $order_prefix ) ) {
427 $reference_number = $order_prefix . '-' . $order->get_id();
428 } else {
429 $reference_number = $order->get_id();
430 }
431
432 // Build the API URL.
433 $url = $zoho_inventory_url . 'inventory/v1/salesorders/' . $salesorder_id . '?organization_id=' . $zoho_inventory_oid;
434
435 // Prepare data for update.
436 $data = array(
437 'JSONString' => wp_json_encode(
438 array(
439 'reference_number' => $reference_number,
440 )
441 ),
442 );
443
444 // Make the API call to update Zoho sales order.
445 $api_handler = new \CMBIRD_API_Handler_Zoho();
446 $response = $api_handler->execute_curl_call_put( $url, $data );
447
448 // Log any errors.
449 $logger = wc_get_logger();
450 if ( is_wp_error( $response ) ) {
451 $logger->error(
452 'Error updating Zoho reference number: ' . $response->get_error_message(),
453 array(
454 'source' => 'commercebird',
455 'order_id' => $order->get_id(),
456 'salesorder_id' => $salesorder_id,
457 )
458 );
459 } elseif ( isset( $response->code ) && 0 !== $response->code ) {
460 $error_message = isset( $response->message ) ? $response->message : 'Unknown error';
461 $logger->error(
462 'Error updating Zoho reference number: ' . $error_message,
463 array(
464 'source' => 'commercebird',
465 'order_id' => $order->get_id(),
466 'salesorder_id' => $salesorder_id,
467 )
468 );
469 } else {
470 $order->add_order_note( 'Updated Zoho reference number to: ' . $reference_number );
471 }
472 }
473
474
475 /**
476 * Find the product ids from the line items from Zoho Inventory.
477 *
478 * @param mixed $line_items Line items from the salesorder.
479 * @return array
480 */
481 private function get_items( $line_items ): array {
482 $product_ids = array();
483 $not_found = array();
484
485 foreach ( $line_items as $item ) {
486 // Check if the sku key is present (case-insensitive check).
487 $sku = isset( $item['sku'] ) ? $item['sku'] : ( isset( $item['SKU'] ) ? $item['SKU'] : null );
488 $quantity = $item['quantity'] ?? 1;
489 $rate = $item['rate'] ?? null;
490
491 if ( null !== $sku ) {
492 $product_id = wc_get_product_id_by_sku( $sku );
493 if ( $product_id ) {
494 // Append to product_ids instead of overwriting.
495 $product_ids[] = array(
496 'id' => $product_id,
497 'quantity' => $quantity,
498 'rate' => $rate,
499 );
500 } else {
501 $not_found[] = $sku;
502 }
503 } else {
504 $not_found[] = 'Unknown SKU';
505 }
506 }
507
508 if ( ! empty( $not_found ) ) {
509 return array( 'not_found' => $not_found );
510 }
511
512 return $product_ids;
513 }
514
515 /**
516 * Find the coupon code based on Discount or create the coupon code and return it.
517 * @param mixed $order_data The order data from salesforce.
518 * @return string The coupon code.
519 */
520 private function get_coupon_code( $order_data ): string {
521 if ( isset( $order_data['discount_percent'] ) && ! empty( $order_data['discount_percent'] ) ) {
522 $fixed_cart_amount = $order_data['discount_percent'];
523 // Search for an existing coupon by fixed cart amount
524 $existing_coupon = get_posts(
525 array(
526 'post_type' => 'shop_coupon',
527 'post_status' => 'publish',
528 'post_per_page' => -1,
529 'meta_query' => array(
530 'relation' => 'AND',
531 array(
532 'key' => 'discount_type',
533 'value' => 'percent',
534 ),
535 array(
536 'key' => 'coupon_amount',
537 'value' => $fixed_cart_amount,
538 'compare' => '=',
539 ),
540 ),
541 )
542 );
543
544 if ( $existing_coupon ) {
545 $coupon_code = $existing_coupon[0]->post_title;
546 return $coupon_code;
547 } else {
548 // If not found, create a new coupon with a random code
549 $coupon_code = 'AUTO_' . wp_generate_password( 8, false );
550 $coupon = new \WC_Coupon();
551 $coupon->set_code( $coupon_code );
552 $coupon->set_description( 'Auto-generated coupon.' );
553 $coupon->set_discount_type( 'percent' );
554 $coupon->set_amount( $fixed_cart_amount );
555 $coupon->save();
556 return $coupon_code;
557 }
558 }
559 return '';
560 }
561 }
562