Skip to content

Instantly share code, notes, and snippets.

@kish2011
Created December 10, 2025 16:44
Show Gist options
  • Select an option

  • Save kish2011/d2585188e9dbd17a142a0238b345e6c9 to your computer and use it in GitHub Desktop.

Select an option

Save kish2011/d2585188e9dbd17a142a0238b345e6c9 to your computer and use it in GitHub Desktop.
Zoho Inventory Stock Sync (India Region)
/*
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