Created
December 10, 2025 16:44
-
-
Save kish2011/d2585188e9dbd17a142a0238b345e6c9 to your computer and use it in GitHub Desktop.
Zoho Inventory Stock Sync (India Region)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Plugin Name: Zoho Inventory Stock Sync (India Region) | |
| Description: Syncs stock quantity + status from Zoho Inventory → WooCommerce using refresh token (Self Client). Supports variations. Handles all stock types safely. Self Client Code is stored securely and cleared automatically. | |
| Version: 3.2.0 | |
| Author: Kishore Sahoo | |
| */ | |
| if (!defined('ABSPATH')) exit; | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | CONFIG — ENTER YOUR CREDENTIALS | |
| |-------------------------------------------------------------------------- | |
| */ | |
| define('ZINV_ORG_ID', 'ZINV_ORG_ID'); | |
| define('ZINV_CLIENT_ID', 'ZINV_CLIENT_ID'); | |
| define('ZINV_CLIENT_SECRET', 'ZINV_CLIENT_SECRET'); | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | Utility Logging + Screen Output | |
| |-------------------------------------------------------------------------- | |
| */ | |
| function zinv_log($msg) { | |
| error_log("ZINV: " . $msg); | |
| echo "ZINV: " . $msg . "<br>"; | |
| @ob_flush(); | |
| @flush(); | |
| } | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | Save Self Client Code (Temporary 10-min code) — Optional Admin Input | |
| |-------------------------------------------------------------------------- | |
| */ | |
| function zinv_set_self_client_code($code) { | |
| update_option('zinv_self_client_code', sanitize_text_field($code)); | |
| } | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | Auto-generate REFRESH TOKEN | |
| |-------------------------------------------------------------------------- | |
| */ | |
| function zinv_generate_refresh_token() { | |
| $saved = get_option('zinv_refresh_token'); | |
| if (!empty($saved)) { | |
| zinv_log("Refresh token already stored."); | |
| return $saved; | |
| } | |
| $self_code = get_option('zinv_self_client_code'); | |
| if (empty($self_code)) { | |
| zinv_log("No Refresh Token + No Self Client Code stored. Please set Self Client Code in plugin settings."); | |
| return false; | |
| } | |
| zinv_log("Generating Refresh Token from Self Client Code..."); | |
| $response = wp_remote_post('https://accounts.zoho.in/oauth/v2/token', [ | |
| 'body' => [ | |
| 'code' => $self_code, | |
| 'client_id' => ZINV_CLIENT_ID, | |
| 'client_secret' => ZINV_CLIENT_SECRET, | |
| 'grant_type' => 'authorization_code', | |
| ] | |
| ]); | |
| if (is_wp_error($response)) { | |
| zinv_log("Refresh Token Error: " . $response->get_error_message()); | |
| return false; | |
| } | |
| $body = json_decode(wp_remote_retrieve_body($response), true); | |
| if (!isset($body['refresh_token'])) { | |
| zinv_log("Refresh Token Invalid Response: " . wp_remote_retrieve_body($response)); | |
| return false; | |
| } | |
| $refresh_token = $body['refresh_token']; | |
| update_option('zinv_refresh_token', $refresh_token); | |
| // ✅ Clear Self Client Code after successful use | |
| delete_option('zinv_self_client_code'); | |
| zinv_log("Refresh Token generated & saved. Self Client Code cleared."); | |
| return $refresh_token; | |
| } | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | 1. CRON schedule (Every 6 hours) | |
| |-------------------------------------------------------------------------- | |
| */ | |
| add_filter('cron_schedules', function ($schedules) { | |
| $schedules['zinv_6_hours'] = [ | |
| 'interval' => 6 * HOUR_IN_SECONDS, | |
| 'display' => 'Every 6 Hours' | |
| ]; | |
| return $schedules; | |
| }); | |
| if (!wp_next_scheduled('zinv_cron_event')) { | |
| wp_schedule_event(time(), 'zinv_6_hours', 'zinv_cron_event'); | |
| } | |
| add_action('zinv_cron_event', 'zinv_run_stock_sync'); | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | 2. Get Access Token | |
| |-------------------------------------------------------------------------- | |
| */ | |
| function zinv_get_access_token() { | |
| $refresh_token = get_option('zinv_refresh_token'); | |
| if (empty($refresh_token)) { | |
| $refresh_token = zinv_generate_refresh_token(); | |
| if (!$refresh_token) return false; | |
| } | |
| zinv_log("Getting Access Token..."); | |
| $response = wp_remote_post('https://accounts.zoho.in/oauth/v2/token', [ | |
| 'body' => [ | |
| 'grant_type' => 'refresh_token', | |
| 'client_id' => ZINV_CLIENT_ID, | |
| 'client_secret' => ZINV_CLIENT_SECRET, | |
| 'refresh_token' => $refresh_token | |
| ], | |
| 'timeout' => 30 | |
| ]); | |
| if (is_wp_error($response)) { | |
| zinv_log('Access token error — ' . $response->get_error_message()); | |
| return false; | |
| } | |
| $body = json_decode(wp_remote_retrieve_body($response), true); | |
| if (!isset($body['access_token'])) { | |
| zinv_log('Invalid OAuth response — ' . wp_remote_retrieve_body($response)); | |
| return false; | |
| } | |
| zinv_log("Access Token OK."); | |
| return $body['access_token']; | |
| } | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | 3. Fetch ALL Zoho Inventory Items (safe stock detection) | |
| |-------------------------------------------------------------------------- | |
| */ | |
| function zinv_fetch_all_zoho_items() { | |
| $access_token = zinv_get_access_token(); | |
| if (!$access_token) return false; | |
| $all_items = []; | |
| $page = 1; | |
| $per_page = 200; | |
| do { | |
| zinv_log("Fetching Zoho items page {$page}..."); | |
| $url = "https://www.zohoapis.in/inventory/v1/items?organization_id=" . ZINV_ORG_ID . | |
| "&page={$page}&per_page={$per_page}"; | |
| $resp = wp_remote_get($url, [ | |
| 'headers' => [ | |
| 'Authorization' => "Zoho-oauthtoken {$access_token}" | |
| ], | |
| 'timeout' => 30, | |
| ]); | |
| if (is_wp_error($resp)) { | |
| zinv_log("Error fetching page {$page} — " . $resp->get_error_message()); | |
| break; | |
| } | |
| $body = json_decode(wp_remote_retrieve_body($resp), true); | |
| if (empty($body['items'])) break; | |
| foreach ($body['items'] as $item) { | |
| if (!empty($item['sku'])) { | |
| // ✅ Safe stock detection | |
| $stock = 0; | |
| if (isset($item['available_stock'])) { | |
| $stock = (int)$item['available_stock']; | |
| } elseif (isset($item['quantity_available'])) { | |
| $stock = (int)$item['quantity_available']; | |
| } elseif (isset($item['actual_stock'])) { | |
| $stock = (int)$item['actual_stock']; | |
| } | |
| $all_items[$item['sku']] = ['stock' => $stock]; | |
| } | |
| } | |
| $page++; | |
| } while (count($body['items']) == $per_page); | |
| zinv_log("Fetched " . count($all_items) . " Zoho items."); | |
| return $all_items; | |
| } | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | 4. Sync to WooCommerce | |
| |-------------------------------------------------------------------------- | |
| */ | |
| function zinv_run_stock_sync() { | |
| zinv_log("Sync started."); | |
| $zoho_items = zinv_fetch_all_zoho_items(); | |
| if (!$zoho_items) { | |
| zinv_log("Could not fetch Zoho items."); | |
| return; | |
| } | |
| $products = wc_get_products([ | |
| 'limit' => -1, | |
| 'return' => 'ids', | |
| 'type' => ['simple', 'variation'] | |
| ]); | |
| foreach ($products as $id) { | |
| $product = wc_get_product($id); | |
| if (!$product) continue; | |
| $sku = $product->get_sku(); | |
| if (!$sku) continue; | |
| if (!isset($zoho_items[$sku])) { | |
| zinv_log("SKU not found in Zoho — {$sku}"); | |
| continue; | |
| } | |
| $qty = (int)$zoho_items[$sku]['stock']; | |
| $status = $qty > 0 ? 'instock' : 'outofstock'; | |
| $product->set_manage_stock(true); | |
| $product->set_stock_quantity($qty); | |
| $product->set_stock_status($status); | |
| $product->save(); | |
| zinv_log("Updated {$sku} → Qty {$qty}, Status {$status}"); | |
| } | |
| zinv_log("Sync completed."); | |
| } | |
| /* | |
| |-------------------------------------------------------------------------- | |
| | 5. Manual Trigger | |
| |-------------------------------------------------------------------------- | |
| */ | |
| add_action('init', function () { | |
| if ( | |
| isset($_GET['zinv_run_now']) && | |
| is_user_logged_in() && | |
| current_user_can('manage_options') | |
| ) { | |
| zinv_log("Manual sync started."); | |
| zinv_run_stock_sync(); | |
| wp_die("Zoho Stock Sync Finished.<br>See output above and debug.log"); | |
| } | |
| }); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment