PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.7.3
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.7.3
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 / zoho-inventory / class-order-sync.php
commercebird / includes / classes / zoho-inventory Last commit date
class-cmbird-categories-zi.php 7 months ago class-cmbird-image-zi.php 5 months ago class-import-items.php 4 months ago class-import-price-list.php 9 months ago class-multi-currency.php 7 months ago class-order-sync.php 4 months ago class-product.php 5 months ago class-users-contact.php 5 months ago index.php 1 year ago
class-order-sync.php
1214 lines
1 <?php
2 /**
3 * Class for handling Zoho Inventory order sync related functions.
4 */
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 class CMBIRD_Order_Sync_ZI {
10
11 /**
12 * Zoho Inventory organization ID.
13 *
14 * @var string
15 */
16 private $zoho_inventory_oid;
17
18 /**
19 * Zoho Inventory API base URL.
20 *
21 * @var string
22 */
23 private $zoho_inventory_url;
24
25 /**
26 * Zoho Inventory domain code (e.g. 'in' for India).
27 *
28 * @var string
29 */
30 private $zoho_inventory_domain;
31
32 /**
33 * Location ID for Zoho Inventory.
34 *
35 * @var string
36 */
37 private $location_id;
38
39 /**
40 * Parent location ID for Zoho Inventory.
41 *
42 * @var string
43 */
44 private $parent_location_id;
45
46 /**
47 * Whether to enable order status sync.
48 *
49 * @var bool
50 */
51 private $enable_order_status;
52
53 /**
54 * Whether to enable auto-numbering for orders.
55 *
56 * @var bool
57 */
58 private $enable_auto_number;
59
60 /**
61 * Prefix for order numbers.
62 *
63 * @var string
64 */
65 private $order_prefix;
66
67 /**
68 * Custom fields for Zoho Inventory sync.
69 *
70 * @var array
71 */
72 private $custom_fields;
73
74 /**
75 * Whether to include tax in calculations.
76 *
77 * @var bool
78 */
79 private $include_tax;
80
81 /**
82 * Initialize the class.
83 */
84 public function __construct() {
85 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
86 if ( ! empty( $zoho_inventory_access_token ) ) {
87 add_action( 'woocommerce_update_order', array( $this, 'salesorder_void' ) );
88 // get options.
89 $this->zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
90 $this->zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
91 $this->zoho_inventory_domain = get_option( 'cmbird_zoho_inventory_domain' );
92 $this->location_id = get_option( 'cmbird_zoho_location_id_status' );
93 $this->parent_location_id = get_option( 'cmbird_zoho_parent_location_id_status' );
94 $this->enable_order_status = get_option( 'cmbird_zoho_enable_order_status_status' );
95 $this->enable_auto_number = get_option( 'cmbird_zoho_enable_auto_number_status' );
96 $this->order_prefix = get_option( 'cmbird_zoho_order_prefix_status' );
97 $this->custom_fields = json_decode( get_option( 'cmbird_wootozoho_custom_fields' ), true );
98 $this->include_tax = get_option( 'woocommerce_prices_include_tax' ) === 'yes';
99 } else {
100 return;
101 }
102 }
103
104 /**
105 * Function to map customer on checkout before placing order
106 *
107 * @param int $order_id Order ID.
108 */
109 public function cmbird_zi_sync_customer_checkout( $order_id ) {
110 // $fd = fopen( __DIR__ . '/cmbird_zi_sync_customer_checkout.txt', 'w+' );
111 $order = wc_get_order( $order_id );
112 $userid = $order->get_user_id();
113 $is_guest_order = empty( $userid );
114 $user_company = $order->get_billing_company();
115 $user_email = $order->get_billing_email();
116
117 // For guest orders, get contact ID from order meta; for regular users, from user meta.
118 if ( $is_guest_order ) {
119 $zi_customer_id = $order->get_meta( 'zi_contact_id', true );
120 } else {
121 $zi_customer_id = get_user_meta( $userid, 'zi_contact_id', true );
122 }
123
124 // Get currency code of the order.
125 if ( $is_guest_order ) {
126 $currency_id = intval( $order->get_meta( 'zi_currency_id', true ) );
127 } else {
128 $currency_id = intval( get_user_meta( $userid, 'zi_currency_id', true ) );
129 }
130 if ( empty( $currency_id ) ) {
131 $currency_code = $order->get_currency();
132 $multi_currency_handle = new CMBIRD_Multicurrency_Zoho();
133 $currency_id = $multi_currency_handle->zoho_currency_data( $currency_code, $userid, $order );
134 }
135
136 if ( $zi_customer_id ) {
137 $zoho_inventory_oid = $this->zoho_inventory_oid;
138 $zoho_inventory_url = $this->zoho_inventory_url;
139 $get_url = $zoho_inventory_url . 'inventory/v1/contacts/' . $zi_customer_id . '/?organization_id=' . $zoho_inventory_oid;
140
141 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
142 $json = $execute_curl_call_handle->execute_curl_call_get( $get_url );
143 // fwrite($fd,PHP_EOL.'customer_json: '.print_r($json, true)); --- IGNORE ---.
144 $code = $json->code;
145 if ( 0 !== $code && '0' !== $code ) {
146 // For guest orders, clear contact data immediately if validation fails.
147 // For registered users, be more cautious - only clear if it's a definitive "not found" error.
148 $should_clear_contact_data = false;
149
150 if ( 1003 === $code || '1003' === $code ) {
151 // For registered users, only clear if it's a specific "contact not found" error (code 1003).
152 // Other errors might be temporary (rate limiting, API issues, etc.).
153 $should_clear_contact_data = true;
154 }
155
156 if ( $should_clear_contact_data ) {
157 // Clear contact data - from order meta for guest orders, user meta for regular users.
158 if ( $is_guest_order ) {
159 $order->delete_meta_data( 'zi_contact_id' );
160 $order->delete_meta_data( 'zi_billing_address_id' );
161 $order->delete_meta_data( 'zi_primary_contact_id' );
162 $order->delete_meta_data( 'zi_shipping_address_id' );
163 $order->delete_meta_data( 'zi_created_time' );
164 $order->delete_meta_data( 'zi_last_modified_time' );
165 $order->save();
166 } else {
167 delete_user_meta( $userid, 'zi_contact_id' );
168 delete_user_meta( $userid, 'zi_billing_address_id' );
169 delete_user_meta( $userid, 'zi_primary_contact_id' );
170 delete_user_meta( $userid, 'zi_shipping_address_id' );
171 delete_user_meta( $userid, 'zi_created_time' );
172 delete_user_meta( $userid, 'zi_last_modified_time' );
173 }
174 $zi_customer_id = '';
175 } else {
176 // save the billing and shipping address IDs if contact exists.
177 foreach ( $json->contact as $key => $value ) {
178 if ( 'billing_address_id' === $key ) {
179 if ( $is_guest_order ) {
180 $order->update_meta_data( 'zi_billing_address_id', $value );
181 $order->save();
182 } else {
183 update_user_meta( $userid, 'zi_billing_address_id', $value );
184 }
185 }
186 if ( 'shipping_address_id' === $key ) {
187 if ( $is_guest_order ) {
188 $order->update_meta_data( 'zi_shipping_address_id', $value );
189 $order->save();
190 } else {
191 update_user_meta( $userid, 'zi_shipping_address_id', $value );
192 }
193 }
194 }
195 }
196 // If we don't clear the contact data, keep the existing $zi_customer_id and proceed.
197 }
198 }
199
200 /**
201 * Syncing customer if its not in Zoho yet.
202 */
203 if ( empty( $zi_customer_id ) ) {
204
205 // First check based on customer email address.
206 $zoho_inventory_oid = $this->zoho_inventory_oid;
207 $zoho_inventory_url = $this->zoho_inventory_url;
208 $url = $zoho_inventory_url . 'inventory/v1/contacts?organization_id=' . $zoho_inventory_oid . '&email=' . $user_email;
209
210 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
211 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
212
213 $code = $json->code;
214 // $message = $json->message;
215 if ( 0 === $code || '0' === $code ) {
216 // fwrite( $fd, PHP_EOL . 'email found: ' . print_r( $json, true ) );
217 if ( empty( $json->contacts ) ) {
218 // Second check based on Company Name.
219 if ( $user_company ) {
220 $company_name = str_replace( ' ', '%20', $user_company );
221 $url = $zoho_inventory_url . 'inventory/v1/contacts?organization_id=' . $zoho_inventory_oid . '&filter_by=Status.Active&search_text=' . $company_name;
222
223 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
224 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
225
226 $code = $json->code;
227 if ( 0 === $code || '0' === $code ) {
228 if ( empty( $json->contacts ) ) {
229 $zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
230 } else {
231 foreach ( $json->contacts[0] as $key => $value ) {
232 if ( 'contact_id' === $key ) {
233 $zi_customer_id = $value;
234 // Store contact ID - in order meta for guest orders, user meta for regular users.
235 if ( $is_guest_order ) {
236 $order->update_meta_data( 'zi_contact_id', $zi_customer_id );
237 $order->save();
238 } else {
239 update_user_meta( $userid, 'zi_contact_id', $zi_customer_id );
240 }
241 }
242 }
243 $zi_customer_id = $this->create_contact_person_for_order( $userid, $order_id, $is_guest_order );
244 }
245 } else {
246 // Company search failed, create new contact.
247 $zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
248 }
249 } else {
250 $zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
251 }
252 } else {
253 // fwrite($fd,PHP_EOL.'Contacts : '.print_r($json->contacts,true));
254 foreach ( $json->contacts[0] as $key => $value ) {
255 if ( 'contact_id' === $key ) {
256 $zi_customer_id = $value;
257 // Store contact ID - in order meta for guest orders, user meta for regular users.
258 if ( $is_guest_order ) {
259 $order->update_meta_data( 'zi_contact_id', $zi_customer_id );
260 $order->save();
261 } else {
262 update_user_meta( $userid, 'zi_contact_id', $zi_customer_id );
263 }
264 }
265 }
266 }
267 } else {
268 // Email search failed, create new contact.
269 $zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
270 }
271 // Http request not processed properly.
272 // echo $message;
273 // Final fallback: if still no contact and this is a guest order, always create contact.
274 if ( empty( $zi_customer_id ) && $is_guest_order ) {
275 $zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
276 }
277 return $zi_customer_id;
278 } else {
279 $zoho_inventory_oid = $this->zoho_inventory_oid;
280 $zoho_inventory_url = $this->zoho_inventory_url;
281 $get_url = $zoho_inventory_url . 'inventory/v1/contacts/' . $zi_customer_id . '/contactpersons/?organization_id=' . $zoho_inventory_oid;
282
283 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
284 $contactpersons_response = $execute_curl_call_handle->execute_curl_call_get( $get_url );
285
286 // fwrite( $fd, PHP_EOL . 'Contactpersons: ' . print_r($contactpersons_response, true) );
287
288 // first check within contactpersons endpoint and then map it with that contactperson if email-id matches.
289 if ( 0 === $contactpersons_response->code || '0' === $contactpersons_response->code ) {
290 if ( ! empty( $contactpersons_response->contact_persons ) ) {
291 foreach ( $contactpersons_response->contact_persons as $key => $contact_persons ) {
292 $person_email = trim( $contact_persons->email );
293 if ( trim( $user_email ) === $person_email ) {
294 /* Match Contact */
295 $contactid = $contact_persons->contact_person_id;
296 // Store contact person ID - in order meta for guest orders, user meta for regular users.
297 if ( $is_guest_order ) {
298 $order->update_meta_data( 'zi_contactperson_id_' . $key, $contactid );
299 $order->save();
300 } else {
301 update_user_meta( $userid, 'zi_contactperson_id_' . $key, $contactid );
302 }
303 if ( true === $contact_persons->is_primary_contact || 1 === $contact_persons->is_primary_contact ) {
304 if ( ! $is_guest_order ) {
305 $contact_class_handle = new CMBIRD_Contact_ZI();
306 $contact_class_handle->cmbird_contact_update_function( $userid, $order_id );
307 }
308 } elseif ( ! $is_guest_order ) {
309 $contact_class_handle = new CMBIRD_Contact_ZI();
310 $contact_class_handle->cmbird_update_contact_person( $userid, $order_id );
311 }
312 }
313 }
314 } else {
315 $get_url = $zoho_inventory_url . 'inventory/v1/contacts/' . $zi_customer_id . '/?organization_id=' . $zoho_inventory_oid;
316 $contact_res = $execute_curl_call_handle->execute_curl_call_get( $get_url );
317 if ( ( 0 === $contact_res->code || '0' === $contact_res->code ) && ! empty( $contact_res->contact ) ) {
318 // Access the contact object directly (no loop needed - response contains single contact object).
319 if ( trim( $contact_res->contact->email ) === trim( $user_email ) ) {
320 $contact_class_handle = new CMBIRD_Contact_ZI();
321 $contact_class_handle->cmbird_contact_update_function( $userid, $order_id );
322 } else {
323 $contact_class_handle = new CMBIRD_Contact_ZI();
324 $contact_class_handle->cmbird_create_contact_person( $userid );
325 }
326 }
327 }
328 }
329 // fwrite( $fd, PHP_EOL . 'No contactpersons ' );
330 }
331 // fclose( $fd );
332 return $zi_customer_id;
333 }
334
335 /**
336 * Function for admin zoho sync call.
337 *
338 * @param int $order_id Order ID.
339 */
340 public function zi_order_sync( $order_id ) {
341 $order = wc_get_order( $order_id );
342 if ( ! $order ) {
343 return;
344 }
345 // return if there is no zoho inventory access token.
346 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
347 if ( empty( $zoho_inventory_access_token ) ) {
348 return;
349 }
350
351 $current_time = time();
352 $last_time = $order->get_meta( 'zi_last_order_sync_time', true );
353 if ( ! empty( $last_time ) && $current_time - $last_time < 60 ) {
354 return;
355 }
356
357 // Create an action hook to prevent orders getting synced if it contains an array of statuses.
358 $ignored_statuses = apply_filters( 'cmbird_zi_order_sync_ignored_statuses', array( 'cancelled', 'failed', 'draft', 'trash' ) );
359 if ( in_array( $order->get_status(), $ignored_statuses ) ) {
360 return;
361 }
362
363 $zi_sales_order_id = $order->get_meta( 'zi_salesorder_id' );
364 $userid = $order->get_user_id();
365 $is_guest_order = empty( $userid );
366
367 // For guest orders, get contact details from order meta instead of user meta.
368 if ( $is_guest_order ) {
369 $customer_id = $order->get_meta( 'zi_contact_id', true );
370 $billing_id = $order->get_meta( 'zi_billing_address_id', true );
371 $shipping_id = $order->get_meta( 'zi_shipping_address_id', true );
372 } else {
373 $customer_id = get_user_meta( $userid, 'zi_contact_id', true );
374 $billing_id = get_user_meta( $userid, 'zi_billing_address_id', true );
375 $shipping_id = get_user_meta( $userid, 'zi_shipping_address_id', true );
376 }
377 // Sync customer if not exists.
378 if ( empty( $customer_id ) ) {
379 $customer_id = $this->cmbird_zi_sync_customer_checkout( $order_id );
380 } elseif ( empty( $billing_id ) || empty( $shipping_id ) ) {
381 // run cmbird_contact_update_function() to update address IDs.
382 $contact_class_handle = new CMBIRD_Contact_ZI();
383 $contact_class_handle->cmbird_contact_update_function( $userid, $order_id );
384 }
385 // 2nd attempt - For guest orders, get address IDs from order meta; for regular users, from user meta.
386 if ( $is_guest_order ) {
387 $billing_id = $order->get_meta( 'zi_billing_address_id', true );
388 $shipping_id = $order->get_meta( 'zi_shipping_address_id', true );
389 } else {
390 $billing_id = get_user_meta( $userid, 'zi_billing_address_id', true );
391 $shipping_id = get_user_meta( $userid, 'zi_shipping_address_id', true );
392 }
393
394 $line_items = $this->build_line_items( $order );
395 if ( empty( $line_items ) ) {
396 return;
397 }
398
399 // Final validation: Ensure customer_id is not 0 or '0' before creating request.
400 if ( empty( $customer_id ) ) {
401 $order->add_order_note( 'Zoho Order Sync Failed: Contact does not exist or could not be created.' );
402 return;
403 }
404
405 $pdt = array(
406 'customer_id' => $customer_id,
407 'date' => $order->get_date_created()->format( 'Y-m-d' ),
408 'line_items' => $line_items,
409 'is_discount_before_tax' => true,
410 'discount_type' => 'item_level',
411 'price_precision' => '2',
412 'notes' => preg_replace( '/[^A-Za-z0-9\-]/', ' ', $order->get_customer_note() ),
413 'billing_address_id' => $billing_id,
414 'shipping_address_id' => $shipping_id,
415 'delivery_method' => $order->get_shipping_method(),
416 'is_inclusive_tax' => $this->include_tax,
417 'shipping_charge' => $order->get_shipping_total(),
418 'order_status' => $this->enable_order_status ? 'draft' : 'confirmed',
419 );
420
421 if ( $this->parent_location_id && $this->location_id ) {
422 $pdt['location_id'] = $this->parent_location_id;
423 }
424
425 $shipping_tax_id = $this->get_shipping_tax_id( $order );
426 if ( $shipping_tax_id ) {
427 $pdt['shipping_charge_tax_id'] = $shipping_tax_id;
428 }
429
430 $fees = $this->get_order_fees( $order );
431 $pdt = array_merge( $pdt, $fees );
432
433 $pdt['custom_fields'] = $this->prepare_custom_fields( $order );
434 $reference = $this->prepare_reference_number( $order );
435
436 if ( $this->enable_auto_number ) {
437 $pdt['reference_number'] = $reference;
438 } else {
439 $pdt['salesorder_number'] = $order->get_id();
440 }
441
442 $userid = $order->get_user_id();
443 // get below data if zoho_inventory_domain is 'in'.
444 if ( 'in' === $this->zoho_inventory_domain ) {
445 $gst_no = get_user_meta( $userid, 'gst_no', true );
446 // if gst_no is empty then get it from order.
447 if ( empty( $gst_no ) ) {
448 $gst_no = $order->get_meta( '_gst_no', true );
449 }
450 if ( $gst_no ) {
451 $pdt['gst_no'] = $gst_no;
452 }
453
454 // Determine GST treatment based on billing company, GST number, and country.
455 $billing_company = $order->get_billing_company();
456 $billing_country = $order->get_billing_country();
457
458 // GST treatment logic - prioritize company + GST combination first.
459 if ( ! empty( $billing_company ) && ! empty( $gst_no ) ) {
460 // Company with GST number.
461 $gst_treatment = 'business_gst';
462 } elseif ( ! empty( $billing_company ) && empty( $gst_no ) && 'IN' === $billing_country ) {
463 // Company without GST number in India.
464 $gst_treatment = 'business_none';
465 } elseif ( 'IN' !== $billing_country ) {
466 // Non-Indian customer (overseas).
467 $gst_treatment = 'overseas';
468 } else {
469 // Individual consumer in India.
470 $gst_treatment = 'consumer';
471 }
472
473 // Allow override from user meta or order meta if exists.
474 $meta_gst_treatment = get_user_meta( $userid, 'gst_treatment', true );
475 if ( empty( $meta_gst_treatment ) ) {
476 $meta_gst_treatment = $order->get_meta( 'gst_treatment', true );
477 }
478 if ( ! empty( $meta_gst_treatment ) ) {
479 $gst_treatment = $meta_gst_treatment;
480 }
481
482 $pdt['gst_treatment'] = $gst_treatment;
483 $pdt['place_of_supply'] = $order->get_billing_state();
484 }
485
486 $response_msg = $zi_sales_order_id ? $this->single_saleorder_zoho_inventory_update( $order_id, $zi_sales_order_id, wp_json_encode( $pdt ) ) : $this->single_saleorder_zoho_inventory( wp_json_encode( $pdt ), $order_id );
487
488 $order->update_meta_data( 'zi_body_request', wp_json_encode( $pdt ) );
489 $order->update_meta_data( 'zi_salesorder_id', $response_msg['zi_salesorder_id'] );
490 $order->update_meta_data( 'zi_last_order_sync_time', $current_time );
491 $order->add_order_note( 'Zoho Order Sync: ' . $response_msg['message'] );
492 $order->save();
493 }
494
495 /**
496 * Build line items for a given order.
497 *
498 * @param WC_Order $order WooCommerce order object.
499 *
500 * @return array Array of line items.
501 */
502 private function build_line_items( $order ) {
503 $items = array();
504 $draft_items = array();
505
506 foreach ( $order->get_items() as $item_id => $item ) {
507 $product = $item->get_product();
508 if ( ! $product ) {
509 continue;
510 }
511
512 // Check if product is published.
513 if ( 'publish' !== $product->get_status() ) {
514 $draft_items[] = $item->get_name();
515 continue;
516 }
517
518 $name = $item->get_name();
519 $meta = $item->get_formatted_meta_data();
520 $desc = '';
521 if ( ! empty( $meta ) ) {
522 foreach ( $meta as $meta_value ) {
523 $desc .= $meta_value->display_key . ': ' . wp_strip_all_tags( $meta_value->display_value ) . ' ';
524 }
525 $desc = sanitize_text_field( trim( $desc ) );
526 }
527
528 $quantity = $item->get_quantity();
529 $subtotal = $item->get_subtotal();
530 $total = $item->get_total();
531 $rate = $subtotal / max( 1, $quantity );
532 $discount = $subtotal > $total ? $subtotal - $total : 0;
533
534 $tax_data = $item->get_taxes();
535 $tax_id = '';
536 $tax_percent = 0;
537 if ( ! empty( $tax_data['subtotal'] ) ) {
538 $tax_rate_ids = array_keys( $tax_data['subtotal'] );
539 $tax_count = count( $tax_rate_ids );
540
541 // Check if this is a compound tax (multiple tax rates).
542 if ( $tax_count > 1 ) {
543 // Calculate total tax percentage from all rates.
544 $total_tax_percent = 0;
545 foreach ( $tax_rate_ids as $tax_rate_id ) {
546 $tax_rate = WC_Tax::_get_tax_rate( $tax_rate_id );
547 $total_tax_percent += floatval( $tax_rate['tax_rate'] );
548 }
549
550 // Try to find a matching tax group.
551 $tax_id = $this->zi_get_tax_group_id( $total_tax_percent );
552
553 if ( $tax_id ) {
554 // Found a matching tax group, use the total percentage.
555 $tax_percent = $total_tax_percent;
556 } else {
557 // No tax group found, fall back to first tax rate only.
558 $tax_rate_id = $tax_rate_ids[0];
559 $tax_rate = WC_Tax::_get_tax_rate( $tax_rate_id );
560 $tax_percent = $tax_rate['tax_rate'];
561
562 // For India domain, use tax name to determine IGST or CGST/SGST.
563 if ( 'in' === $this->zoho_inventory_domain ) {
564 $tax_name = isset( $tax_rate['tax_rate_name'] ) ? $tax_rate['tax_rate_name'] : '';
565 if ( stripos( $tax_name, 'IGST' ) !== false ) {
566 $tax_id = $this->zi_get_igst_tax_id( $tax_percent );
567 } elseif ( stripos( $tax_name, 'SGST' ) !== false || stripos( $tax_name, 'CGST' ) !== false ) {
568 $tax_id = $this->zi_get_tax_id( $tax_percent );
569 } else {
570 // Keep tax_id empty if tax name doesn't indicate IGST or CGST/SGST.
571 $tax_id = '';
572 }
573 } else {
574 // For non-India domains, use regular tax_id.
575 $tax_id = $this->zi_get_tax_id( $tax_percent );
576 }
577 }
578 } else {
579 // Single tax rate - use existing logic.
580 $tax_rate_id = $tax_rate_ids[0];
581 $tax_rate = WC_Tax::_get_tax_rate( $tax_rate_id );
582 $tax_percent = $tax_rate['tax_rate'];
583
584 // For India domain, use tax name to determine IGST or CGST/SGST.
585 if ( 'in' === $this->zoho_inventory_domain ) {
586 $tax_name = isset( $tax_rate['tax_rate_name'] ) ? $tax_rate['tax_rate_name'] : '';
587 if ( stripos( $tax_name, 'IGST' ) !== false ) {
588 $tax_id = $this->zi_get_igst_tax_id( $tax_percent );
589 } elseif ( stripos( $tax_name, 'SGST' ) !== false || stripos( $tax_name, 'CGST' ) !== false ) {
590 $tax_id = $this->zi_get_tax_id( $tax_percent );
591 } else {
592 // Keep tax_id empty if tax name doesn't indicate IGST or CGST/SGST.
593 $tax_id = '';
594 }
595 } else {
596 // For non-India domains, use regular tax_id.
597 $tax_id = $this->zi_get_tax_id( $tax_percent );
598 }
599 $discount += $discount * ( $tax_percent / 100 );
600 }
601
602 $line = array(
603 'item_id' => get_post_meta( $product->get_id(), 'zi_item_id', true ),
604 'name' => $name,
605 'description' => $desc,
606 'quantity' => $quantity,
607 'rate' => round( $rate, 2 ),
608 );
609
610 if ( $discount > 0 ) {
611 $line['discount'] = round( $discount, 2 );
612 }
613 if ( $tax_id ) {
614 $line['tax_percentage'] = $tax_percent;
615 $line['tax_id'] = $tax_id;
616 }
617 // add location id if set.
618 if ( ! empty( $this->location_id ) ) {
619 $line['location_id'] = $this->location_id;
620 }
621 $items[] = $line;
622 }
623
624 // If there are draft items, add order note and return empty array to stop sync.
625 if ( ! empty( $draft_items ) ) {
626 $draft_list = '"' . implode( '", "', $draft_items ) . '"';
627 $order->add_order_note( 'Zoho Order Sync: cannot sync draft items ' . $draft_list );
628 $order->save();
629 return array();
630 }
631 }
632 return $items;
633 }
634
635 /**
636 * Get the Zoho Inventory tax id for the shipping tax.
637 *
638 * @param WC_Order $order The WooCommerce order object.
639 * @return string The Zoho Inventory tax id, or empty string if no tax is applicable.
640 */
641 private function get_shipping_tax_id( $order ) {
642 $shipping_total = $order->get_shipping_total();
643 $shipping_tax = $order->get_shipping_tax();
644 if ( $shipping_total > 0 && $shipping_tax > 0 ) {
645 $percent = ( $shipping_tax / $shipping_total ) * 100;
646 return $this->zi_get_tax_id( round( $percent, 2 ) );
647 }
648 return '';
649 }
650
651 /**
652 * Retrieves the fees associated with an order and formats them for Zoho Inventory.
653 *
654 * @param WC_Order $order The WooCommerce order object.
655 *
656 * @return array Associative array containing the fee amount and description.
657 */
658 private function get_order_fees( $order ) {
659 $fees = $order->get_fees();
660 $data = array();
661 foreach ( $fees as $fee ) {
662 $amount = $fee->get_total();
663 if ( $amount > 0 ) {
664 $data['adjustment'] = $amount;
665 $data['adjustment_description'] = $fee->get_name();
666 }
667 }
668 return $data;
669 }
670
671 /**
672 * Prepare custom fields for the order.
673 *
674 * Iterates over the $custom_fields array and creates an associative array
675 * containing the custom field label and value.
676 *
677 * @param WC_Order $order The WooCommerce order object.
678 * @return array Associative array containing the custom field label and value.
679 */
680 private function prepare_custom_fields( $order ) {
681 $fields = array();
682 if ( is_array( $this->custom_fields ) ) {
683 foreach ( $this->custom_fields as $meta_key => $label ) {
684 $fields[] = array(
685 'label' => $label,
686 'value' => $order->get_meta( $meta_key ),
687 );
688 }
689 }
690 return $fields;
691 }
692
693 /**
694 * Prepare the reference number for the sales order.
695 *
696 * The reference number is used to identify the sales order in Zoho Inventory.
697 * It is generated by combining the order prefix (if set) with the transaction ID
698 * (if available) or the order ID.
699 *
700 * @param WC_Order $order The WooCommerce order object.
701 * @return string The reference number for the sales order.
702 */
703 private function prepare_reference_number( $order ) {
704 $transaction_id = $order->get_transaction_id();
705 if ( empty( $transaction_id ) ) {
706 $transaction_id = $order->get_meta( '_order_number', true );
707 }
708 if ( class_exists( 'WCJ_Order_Numbers' ) || class_exists( 'WC_Seq_Order_Number_Pro' ) ) {
709 return $this->order_prefix . $transaction_id;
710 }
711 if ( ! empty( $this->order_prefix ) ) {
712 return $this->order_prefix . '-' . $order->get_id();
713 }
714 return $order->get_id();
715 }
716
717 /**
718 * Void the sales order if cancelled in WooCommerce.
719 *
720 * @param int $order_id Order ID.
721 */
722 public function salesorder_void( $order_id ) {
723 if ( ! $order_id ) {
724 return;
725 }
726 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
727 if ( empty( $zoho_inventory_access_token ) ) {
728 return;
729 }
730
731 $order = wc_get_order( $order_id );
732 // return if order is already voided.
733 if ( $order->get_meta( 'zi_salesorder_void', true ) ) {
734 return;
735 }
736 $order_status = $order->get_status();
737
738 if ( 'cancelled' === $order_status || 'wc-merged' === $order_status ) {
739 $zi_sales_order_id = $order->get_meta( 'zi_salesorder_id', true );
740 $zoho_inventory_oid = $this->zoho_inventory_oid;
741 $zoho_inventory_url = $this->zoho_inventory_url;
742
743 $url = $zoho_inventory_url . 'inventory/v1/salesorders/' . $zi_sales_order_id . '/status/void?organization_id=' .
744 $zoho_inventory_oid;
745 $data = '';
746 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
747 $json = $execute_curl_call_handle->execute_curl_call_post( $url, $data );
748
749 $errmsg = $json->message;
750 $code = $json->code;
751 if ( '0' === $code || 0 === $code ) {
752 // Add order meta key "zi_salesorder_void" to true.
753 $order->update_meta_data( 'zi_salesorder_void', true );
754 $order->add_order_note( 'Zoho Order Void: ' . $errmsg );
755 $order->save();
756 return;
757 }
758 } else {
759 return;
760 }
761 }
762
763 /**
764 * Sync order from Woo to Zoho.
765 *
766 * @param string $pdt1 JSON string.
767 * @param int $order_id Order ID.
768 * @return string Error message
769 */
770 public function single_saleorder_zoho_inventory( $pdt1, $order_id ) {
771 // start logging.
772 // $fd = fopen( __DIR__ . '/order-sync-backend.txt', 'w+' );
773
774 $zoho_inventory_oid = $this->zoho_inventory_oid;
775 $zoho_inventory_url = $this->zoho_inventory_url;
776 $data = array(
777 'JSONString' => $pdt1,
778 'organization_id' => $zoho_inventory_oid,
779 );
780
781 // logging.
782 // fwrite($fd, PHP_EOL . 'Data log : ' . print_r($data, true));
783
784 if ( empty( $data['JSONString'] ) ) {
785 return '';
786 }
787 $ignore_auto_no = $this->enable_auto_number ? 'false' : 'true';
788 $url = $zoho_inventory_url . 'inventory/v1/salesorders?ignore_auto_number_generation=' . $ignore_auto_no;
789
790 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
791 $json = $execute_curl_call_handle->execute_curl_call_post( $url, $data );
792
793 // fwrite( $fd, PHP_EOL . 'Data log : ' . print_r( $json, true ) );
794 $response = array();
795 $code = $json->code;
796 $errmsg = $json->message;
797 // fwrite($fd, PHP_EOL . 'Code : ' . $code);
798
799 // Retry once if code is 400.
800 if ( 400 === $code || '400' === $code ) {
801 // Wait a brief moment before retry.
802 sleep( 1 );
803 $json = $execute_curl_call_handle->execute_curl_call_post( $url, $data );
804 $code = $json->code;
805 $errmsg = $json->message;
806 }
807
808 if ( '0' === $code || 0 === $code ) {
809 foreach ( $json->salesorder as $key => $value ) {
810
811 if ( 'salesorder_id' === $key ) {
812 $response['zi_salesorder_id'] = $value;
813 // $order->add_meta_data('zi_salesorder_id', $value, true);
814 }
815 }
816 } else {
817 // send email to admin with the error message.
818 // create message that contains the error message and order id.
819 $message = 'Error in Zoho Inventory Order Sync: ' . $errmsg;
820 // append the link to the order edit page.
821 $message .= '<br><br><a href="' . admin_url( 'admin.php?page=wc-orders&action=edit&id=' . $order_id ) . '">Click here to view the order in WP Admin</a>';
822 $message .= '<br><br>Order ID: ' . $order_id;
823 $message .= '<br><br>Request Body: ' . $pdt1;
824 $class_common = new CMBIRD_Common_Functions();
825 $class_common->send_email( 'Error in Zoho Order Sync', $message );
826 }
827 $response['message'] = $errmsg;
828 // fclose( $fd );
829
830 return $response;
831 }
832
833 /**
834 * Function for updating single sales order.
835 *
836 * @param int $order_id Order ID.
837 * @param string $zi_sales_order_id Zoho Sales Order ID.
838 * @param string $pdt1 JSON string.
839 * @return string Error message
840 */
841 public function single_saleorder_zoho_inventory_update( $order_id, $zi_sales_order_id, $pdt1 ) {
842 // $fd = fopen( __DIR__. '/single_saleorder_update.txt', 'w+' );
843
844 $response = array();
845 $zoho_inventory_oid = $this->zoho_inventory_oid;
846 $zoho_inventory_url = $this->zoho_inventory_url;
847
848 $url = $zoho_inventory_url . 'inventory/v1/salesorders/' . $zi_sales_order_id;
849 $data = array(
850 'JSONString' => $pdt1,
851 'organization_id' => $zoho_inventory_oid,
852 );
853
854 $order = wc_get_order( $order_id );
855 // fwrite($fd, PHP_EOL. print_r($data, true)); //logging response.
856 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
857 $json = $execute_curl_call_handle->execute_curl_call_put( $url, $data );
858
859 // $code = $json->code;
860 $errmsg = $json->message;
861 $response['message'] = $errmsg;
862 $response['zi_salesorder_id'] = $zi_sales_order_id;
863
864 // echo '<pre>'; print_r($errmsg);
865
866 $package_id = $order->get_meta( 'zi_package_id', true );
867
868 if ( ! empty( $package_id ) ) {
869 // fwrite($fd, PHP_EOL. 'inside package exists'); //logging response.
870 foreach ( $json->salesorder as $key => $value ) {
871 if ( 'date' === $key ) {
872 $ship_date = $value;
873 }
874
875 if ( 'line_items' === $key ) {
876 foreach ( $value as $kk => $vv ) {
877 $line_items[] = '{"so_line_item_id": "' . $vv->line_item_id . '","quantity": "' . $vv->quantity . '"}';
878 }
879 $impot = implode( ',', $line_items );
880
881 $json_package = '"date": "' . $ship_date . '","line_items": [' . $impot . ']';
882
883 $url_package = $zoho_inventory_url . 'inventory/v1/packages/' . $package_id;
884 $data3 = array(
885 'JSONString' => '{' . $json_package . '}',
886 'organization_id' => $zoho_inventory_oid,
887 );
888
889 $res_package = $execute_curl_call_handle->execute_curl_call_put( $url_package, $data3 );
890 }
891 }
892 }
893 // fclose( $fd ); //end of logging.
894 return $response;
895 }
896
897 /**
898 * Function to get all Zoho Taxes.
899 *
900 * @param int $percentage Tax percentage.
901 * @return string Tax ID.
902 */
903 private function zi_get_tax_id( $percentage ) {
904 // $fd = fopen( __DIR__ . '/zi_get_tax_id.txt', 'a+' );
905 // get all options that contain zoho_inventory_tax_rate_ in the name using global $wpdb.
906 global $wpdb;
907 $zoho_tax_rates = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'cmbird_zoho_inventory_tax_rate_%'" );
908 $input_tax_percentage = floor( $percentage * 10 ) / 10;
909 // fwrite( $fd, PHP_EOL . 'Input Tax Percentage: ' . $input_tax_percentage );
910 $tax_id = '';
911 // for each zoho_tax_rate, check if the tax percentage matches the input percentage. The percentage at the end of the value e.g. 69497000002395146##BTW@@Hoog Exclusief##tax##21.
912 foreach ( $zoho_tax_rates as $zoho_tax_rate ) {
913 $tax_rate = explode( '##', $zoho_tax_rate->option_value );
914 $tax_percentage = $tax_rate[3];
915 // Round the stored tax percentage to one decimal place for comparison.
916 $stored_tax_percentage = round( $tax_percentage, 1 );
917 // Compare the rounded tax percentages with a tolerance for floating-point precision errors.
918 if ( abs( $stored_tax_percentage - $input_tax_percentage ) < 0.01 ) {
919 $tax_id = $tax_rate[0];
920 break;
921 }
922 }
923 return $tax_id;
924 }
925
926 /**
927 * Get Zoho Inventory tax group ID by total tax percentage.
928 *
929 * This function attempts to find a matching tax group when multiple
930 * WooCommerce tax rates are applied (compound taxes). Tax groups in Zoho
931 * represent compound taxes as a single combined rate.
932 *
933 * @param float $total_percentage Total tax percentage from all WooCommerce tax rates.
934 * @return string Tax group ID if found, empty string otherwise.
935 */
936 private function zi_get_tax_group_id( $total_percentage ) {
937 $tax_groups = get_option( 'cmbird_zi_tax_groups', array() );
938
939 if ( empty( $tax_groups ) || ! is_array( $tax_groups ) ) {
940 return '';
941 }
942
943 foreach ( $tax_groups as $tax_group ) {
944 if ( ! isset( $tax_group['tax_id'] ) || ! isset( $tax_group['tax_percentage'] ) ) {
945 continue;
946 }
947
948 // Allow tolerance of 0.2 for compound tax matching.
949 if ( abs( $tax_group['tax_percentage'] - $total_percentage ) <= 0.5 ) {
950 return $tax_group['tax_id'];
951 }
952 }
953
954 return '';
955 }
956
957 /**
958 * Function to get Zoho Inventory IGST tax ID for inter-state transactions in India.
959 *
960 * @param int $percentage Tax percentage.
961 * @return string IGST Tax ID.
962 */
963 private function zi_get_igst_tax_id( $percentage ) {
964 // Get all options that contain zoho_inventory_tax_rate_ in the name for IGST.
965 global $wpdb;
966 $zoho_tax_rates = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'cmbird_zoho_inventory_tax_rate_%'" );
967 $input_tax_percentage = floor( $percentage * 10 ) / 10;
968 $tax_id = '';
969
970 // For each zoho_tax_rate, check if it's an IGST tax and the percentage matches.
971 foreach ( $zoho_tax_rates as $zoho_tax_rate ) {
972 $tax_rate = explode( '##', $zoho_tax_rate->option_value );
973 if ( count( $tax_rate ) >= 4 ) {
974 $tax_name = $tax_rate[1];
975 $tax_percentage = $tax_rate[3];
976
977 // Check if this is an IGST tax (contains "IGST" in the name).
978 if ( stripos( $tax_name, 'IGST' ) !== false ) {
979 // Round the stored tax percentage to one decimal place for comparison.
980 $stored_tax_percentage = round( $tax_percentage, 1 );
981 // Compare the rounded tax percentages with a tolerance for floating-point precision errors.
982 if ( abs( $stored_tax_percentage - $input_tax_percentage ) < 0.01 ) {
983 $tax_id = $tax_rate[0];
984 break;
985 }
986 }
987 }
988 }
989
990 // If no IGST tax found, fallback to regular tax_id.
991 if ( empty( $tax_id ) ) {
992 $tax_id = $this->zi_get_tax_id( $percentage );
993 }
994
995 return $tax_id;
996 }
997
998 /**
999 * TODO: Get Order Transaction Fees
1000 *
1001 * @param int $order_id Order ID.
1002 * @return float Transaction fees
1003 */
1004 protected function get_transaction_fees( $order_id ) {
1005 $order = wc_get_order( $order_id );
1006 $fees = $order->get_meta( '_stripe_fee' );
1007 if ( ! empty( $fees ) ) {
1008 return $fees;
1009 }
1010 $fees = $order->get_meta( '_paypal_transaction_fee' );
1011 if ( ! empty( $fees ) ) {
1012 return $fees;
1013 }
1014 return 0;
1015 }
1016
1017 /**
1018 * Create contact for order - handles both regular users and guest orders
1019 *
1020 * @param int $userid User ID (0 for guest orders).
1021 * @param int $order_id Order ID.
1022 * @param bool $is_guest_order Whether this is a guest order.
1023 * @return string Contact ID.
1024 */
1025 private function create_contact_for_order( $userid, $order_id, $is_guest_order ) {
1026 if ( $is_guest_order ) {
1027 // For guest orders, create contact using order data and store contact info in order meta.
1028 $order = wc_get_order( $order_id );
1029 return $this->create_guest_contact( $order );
1030 } else {
1031 // For regular users, use existing contact creation function.
1032 $contact_class_handle = new CMBIRD_Contact_ZI();
1033 return $contact_class_handle->cmbird_contact_create_function( $userid );
1034 }
1035 }
1036
1037 /**
1038 * Create contact person for order - handles both regular users and guest orders
1039 *
1040 * @param int $userid User ID (0 for guest orders).
1041 * @param int $order_id Order ID.
1042 * @param bool $is_guest_order Whether this is a guest order.
1043 * @return string Contact ID.
1044 */
1045 private function create_contact_person_for_order( $userid, $order_id, $is_guest_order ) {
1046 if ( $is_guest_order ) {
1047 // For guest orders, create contact person using order data.
1048 $order = wc_get_order( $order_id );
1049 return $this->create_guest_contact_person( $order );
1050 } else {
1051 // For regular users, use existing contact person creation function.
1052 $contact_class_handle = new CMBIRD_Contact_ZI();
1053 return $contact_class_handle->cmbird_create_contact_person( $userid );
1054 }
1055 }
1056
1057 /**
1058 * Create Zoho contact for guest order
1059 *
1060 * @param WC_Order $order Order object.
1061 * @return string Contact ID.
1062 */
1063 private function create_guest_contact( $order ) {
1064 // Billing Details.
1065 $contact_name = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name();
1066 $company_name = $order->get_billing_company();
1067 $billing_address = $order->get_billing_address_1();
1068 $billing_address2 = $order->get_billing_address_2();
1069 $billing_city = $order->get_billing_city();
1070 $billing_state = $order->get_billing_state();
1071 $billing_postcode = $order->get_billing_postcode();
1072 $billing_country = $order->get_billing_country();
1073
1074 // Shipping Details.
1075 $shipping_attention = trim( $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name() );
1076 $shipping_address = $order->get_shipping_address_1();
1077 $shipping_address2 = $order->get_shipping_address_2();
1078 $shipping_city = $order->get_shipping_city();
1079 $shipping_state = $order->get_shipping_state();
1080 $shipping_postcode = $order->get_shipping_postcode();
1081 $shipping_country = $order->get_shipping_country();
1082 // If shipping not available assign billing as shipping (same behavior as cmbird_contact_create_function()).
1083 if ( empty( $shipping_address ) ) {
1084 $shipping_address = $billing_address;
1085 }
1086 if ( empty( $shipping_address2 ) ) {
1087 $shipping_address2 = $billing_address2;
1088 }
1089 if ( empty( $shipping_postcode ) ) {
1090 $shipping_postcode = $billing_postcode;
1091 }
1092 if ( empty( $shipping_city ) ) {
1093 $shipping_city = $billing_city;
1094 }
1095 if ( empty( $shipping_country ) ) {
1096 $shipping_country = $billing_country;
1097 }
1098 if ( empty( $shipping_state ) ) {
1099 $shipping_state = $billing_state;
1100 }
1101 if ( '' === $shipping_attention ) {
1102 $shipping_attention = $contact_name;
1103 }
1104 // Customer Details.
1105 $customer_email = $order->get_billing_email();
1106 $customer_phone = $order->get_billing_phone();
1107
1108 // Get currency code.
1109 $currency_code = $order->get_currency();
1110 $multi_currency_handle = new CMBIRD_Multicurrency_Zoho();
1111 $currency_id = $multi_currency_handle->zoho_currency_data( $currency_code, '', $order );
1112
1113 // Build contact data.
1114 $data = array(
1115 'contact_name' => $contact_name,
1116 'company_name' => $company_name,
1117 'contact_type' => 'customer',
1118 'currency_id' => $currency_id,
1119 'contact_persons' => array(
1120 array(
1121 'first_name' => $order->get_billing_first_name(),
1122 'last_name' => $order->get_billing_last_name(),
1123 'email' => $customer_email,
1124 'phone' => $customer_phone,
1125 ),
1126 ),
1127 'billing_address' => array(
1128 'attention' => $contact_name,
1129 'address' => $billing_address,
1130 'street2' => $billing_address2,
1131 'city' => $billing_city,
1132 'state' => $billing_state,
1133 'zip' => $billing_postcode,
1134 'country' => $billing_country,
1135 ),
1136 'shipping_address' => array(
1137 'attention' => $shipping_attention,
1138 'address' => $shipping_address,
1139 'street2' => $shipping_address2,
1140 'city' => $shipping_city,
1141 'state' => $shipping_state,
1142 'zip' => $shipping_postcode,
1143 'country' => $shipping_country,
1144 ),
1145 );
1146 // append place_of_contact if zoho_inventory_domain is 'in'.
1147 if ( 'in' === $this->zoho_inventory_domain ) {
1148 $data['place_of_contact'] = $billing_state;
1149 }
1150
1151 // Create contact in Zoho (data must be JSON string in 'JSONString' key, as in cmbird_contact_create_function()).
1152 $zoho_inventory_oid = $this->zoho_inventory_oid;
1153 $zoho_inventory_url = $this->zoho_inventory_url;
1154 $url = $zoho_inventory_url . 'inventory/v1/contacts';
1155
1156 $post_data = array(
1157 'JSONString' => wp_json_encode( $data ),
1158 'organization_id' => $zoho_inventory_oid,
1159 );
1160
1161 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
1162 $json = $execute_curl_call_handle->execute_curl_call_post( $url, $post_data );
1163
1164 if ( isset( $json->code ) && ( 0 === $json->code || '0' === $json->code ) ) {
1165 $contact_id = $json->contact->contact_id;
1166
1167 // Store contact details in order meta.
1168 $order->update_meta_data( 'zi_contact_id', $contact_id );
1169
1170 if ( isset( $json->contact->billing_address->address_id ) ) {
1171 $order->update_meta_data( 'zi_billing_address_id', $json->contact->billing_address->address_id );
1172 }
1173
1174 if ( isset( $json->contact->shipping_address->address_id ) ) {
1175 $order->update_meta_data( 'zi_shipping_address_id', $json->contact->shipping_address->address_id );
1176 }
1177
1178 if ( isset( $json->contact->primary_contact_id ) ) {
1179 $order->update_meta_data( 'zi_primary_contact_id', $json->contact->primary_contact_id );
1180 }
1181
1182 if ( isset( $json->contact->created_time ) ) {
1183 $order->update_meta_data( 'zi_created_time', $json->contact->created_time );
1184 }
1185
1186 if ( isset( $json->contact->last_modified_time ) ) {
1187 $order->update_meta_data( 'zi_last_modified_time', $json->contact->last_modified_time );
1188 }
1189
1190 if ( isset( $json->contact->currency_id ) ) {
1191 $order->update_meta_data( 'zi_currency_id', $json->contact->currency_id );
1192 }
1193
1194 $order->save();
1195
1196 return $contact_id;
1197 }
1198
1199 return '';
1200 }
1201
1202 /**
1203 * Create Zoho contact person for guest order
1204 *
1205 * @param WC_Order $order Order object.
1206 * @return string Contact ID.
1207 */
1208 private function create_guest_contact_person( $order ) {
1209 // For guest orders, just return the main contact ID since it's already created with contact person.
1210 return $order->get_meta( 'zi_contact_id', true );
1211 }
1212 }
1213 $CMBIRD_Order_Sync_ZI = new CMBIRD_Order_Sync_ZI();
1214