{"id":2925,"date":"2025-05-31T20:51:25","date_gmt":"2025-05-31T13:51:25","guid":{"rendered":"https:\/\/opendb.vn\/?p=2925"},"modified":"2025-05-31T20:51:25","modified_gmt":"2025-05-31T13:51:25","slug":"huong-dan-tu-dong-bo-du-lieu-don-hang-tu-wordpress-woocommerce-ve-google-sheet-su-dung-google-appscript","status":"publish","type":"post","link":"https:\/\/manhtv.io.vn\/index.php\/2025\/05\/31\/huong-dan-tu-dong-bo-du-lieu-don-hang-tu-wordpress-woocommerce-ve-google-sheet-su-dung-google-appscript\/","title":{"rendered":"H\u01b0\u1edbng d\u1eabn: T\u1ef1 \u0111\u1ed3ng b\u1ed9 d\u1eef li\u1ec7u \u0111\u01a1n h\u00e0ng t\u1eeb wordpress woocommerce v\u1ec1 google sheet s\u1eed d\u1ee5ng Google AppScript"},"content":{"rendered":"\n<p>H\u01b0\u1edbng d\u1eabn chi ti\u1ebft gi\u00fap b\u1ea1n t\u1ef1 code m\u1ed9t t\u00ednh n\u0103ng <a href=\"https:\/\/opendb.vn\/huong-dan-tu-dong-bo-du-lieu-don-hang-tu-wordpress-woocommerce-ve-google-sheet-su-dung-google-appscript\/\">\u0111\u1ed3ng b\u1ed9 th\u00f4ng tin \u0111\u01a1n h\u00e0ng WooCommerce v\u1ec1 Google Sheet<\/a> s\u1eed d\u1ee5ng REST API c\u1ee7a woocommerce v\u00e0 ho\u00e0n to\u00e0n mi\u1ec5n ph\u00ed Ch\u00fang ta s\u1ebd c\u00f9ng nhau x\u00e2y d\u1ef1ng m\u1ed9t phi\u00ean b\u1ea3n c\u01a1 b\u1ea3n s\u1eed d\u1ee5ng REST API c\u1ee7a WooCommerce gi\u00fap t\u1ef1 \u0111\u1ed9ng ho\u00e1 m\u1ed9t ph\u1ea7n c\u00f4ng vi\u1ec7c kinh doanh c\u1ee7a b\u1ea1n. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>T\u1ed5ng quan c\u00e1c b\u01b0\u1edbc:<\/strong><\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Chu\u1ea9n b\u1ecb ph\u00eda WooCommerce:<\/strong>\n<ul class=\"wp-block-list\">\n<li>K\u00edch ho\u1ea1t REST API.<\/li>\n\n\n\n<li>T\u1ea1o API Keys (Consumer Key &amp; Consumer Secret).<\/li>\n\n\n\n<li>\u0110\u1ea3m b\u1ea3o website c\u1ee7a b\u1ea1n s\u1eed d\u1ee5ng HTTPS.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Vi\u1ebft m\u00e3 Google Apps Script:<\/strong>\n<ul class=\"wp-block-list\">\n<li>T\u1ea1o menu t\u00f9y ch\u1ec9nh trong Google Sheet.<\/li>\n\n\n\n<li>Thi\u1ebft l\u1eadp n\u01a1i l\u01b0u tr\u1eef th\u00f4ng tin c\u1ea5u h\u00ecnh (URL website, API Keys).<\/li>\n\n\n\n<li>Vi\u1ebft h\u00e0m g\u1ecdi API WooCommerce.<\/li>\n\n\n\n<li>Vi\u1ebft h\u00e0m x\u1eed l\u00fd d\u1eef li\u1ec7u JSON tr\u1ea3 v\u1ec1.<\/li>\n\n\n\n<li>Vi\u1ebft h\u00e0m ghi d\u1eef li\u1ec7u l\u00ean Google Sheet.<\/li>\n\n\n\n<li>X\u1eed l\u00fd l\u1ed7i c\u01a1 b\u1ea3n.<\/li>\n\n\n\n<li>X\u1eed l\u00fd ph\u00e2n trang (n\u1ebfu c\u00f3 nhi\u1ec1u \u0111\u01a1n h\u00e0ng).<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 1: Chu\u1ea9n b\u1ecb <a href=\"https:\/\/opendb.vn\/huong-dan-tu-dong-bo-du-lieu-don-hang-tu-wordpress-woocommerce-ve-google-sheet-su-dung-google-appscript\/\">t\u1ea3i \u0111\u01a1n h\u00e0ng t\u1ef1 \u0111\u1ed9ng t\u1eeb WooCommerce<\/a><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>K\u00edch ho\u1ea1t REST API:<\/strong>\n<ul class=\"wp-block-list\">\n<li>Trong WordPress admin, \u0111i t\u1edbi <strong>WooCommerce &gt; Settings &gt; Advanced &gt; REST API<\/strong>.<\/li>\n\n\n\n<li>Nh\u1ea5n <strong>&#8220;Add key&#8221;<\/strong>.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>T\u1ea1o API Keys:<\/strong>\n<ul class=\"wp-block-list\">\n<li><strong>Description:<\/strong> \u0110\u1eb7t t\u00ean cho key (v\u00ed d\u1ee5: &#8220;Google Sheet Sync&#8221;).<\/li>\n\n\n\n<li><strong>User:<\/strong> Ch\u1ecdn ng\u01b0\u1eddi d\u00f9ng qu\u1ea3n tr\u1ecb (ho\u1eb7c ng\u01b0\u1eddi d\u00f9ng c\u00f3 quy\u1ec1n truy c\u1eadp \u0111\u01a1n h\u00e0ng).<\/li>\n\n\n\n<li><strong>Permissions:<\/strong> Ch\u1ecdn <strong>&#8220;Read&#8221;<\/strong> (\u0110\u1ecdc) n\u1ebfu b\u1ea1n ch\u1ec9 mu\u1ed1n l\u1ea5y d\u1eef li\u1ec7u. N\u1ebfu sau n\u00e0y mu\u1ed1n c\u1eadp nh\u1eadt \u0111\u01a1n h\u00e0ng t\u1eeb Sheet th\u00ec c\u1ea7n &#8220;Read\/Write&#8221;. <em>\u0110\u1ec3 an to\u00e0n, h\u00e3y b\u1eaft \u0111\u1ea7u v\u1edbi &#8220;Read&#8221;.<\/em><\/li>\n\n\n\n<li>Nh\u1ea5n <strong>&#8220;Generate API key&#8221;<\/strong>.<\/li>\n\n\n\n<li><strong>Quan tr\u1ecdng:<\/strong> Sao ch\u00e9p ngay l\u1eadp t\u1ee9c <strong>Consumer key<\/strong> v\u00e0 <strong>Consumer secret<\/strong>. Ch\u00fang s\u1ebd kh\u00f4ng hi\u1ec3n th\u1ecb l\u1ea1i. H\u00e3y l\u01b0u tr\u1eef ch\u00fang \u1edf m\u1ed9t n\u01a1i an to\u00e0n.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>HTTPS:<\/strong> \u0110\u1ea3m b\u1ea3o website WordPress c\u1ee7a b\u1ea1n \u0111ang s\u1eed d\u1ee5ng HTTPS. WooCommerce REST API y\u00eau c\u1ea7u HTTPS \u0111\u1ec3 ho\u1ea1t \u0111\u1ed9ng an to\u00e0n. <\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1145\" height=\"512\" src=\"http:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_SdNj_Js72.webp\" alt=\"\" class=\"wp-image-2934\" srcset=\"https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_SdNj_Js72.webp 1145w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_SdNj_Js72-300x134.webp 300w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_SdNj_Js72-1024x458.webp 1024w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_SdNj_Js72-768x343.webp 768w\" sizes=\"auto, (max-width: 1145px) 100vw, 1145px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 2: Vi\u1ebft m\u00e3 Google Apps Script<\/h3>\n\n\n\n<p>M\u1edf Google Sheet c\u1ee7a b\u1ea1n (ho\u1eb7c t\u1ea1o m\u1ed9t sheet m\u1edbi), sau \u0111\u00f3 v\u00e0o <strong>Extensions &gt; Apps Script<\/strong>. X\u00f3a m\u1ecdi m\u00e3 m\u1eb7c \u0111\u1ecbnh v\u00e0 d\u00e1n m\u00e3 sau v\u00e0o:<\/p>\n\n\n\n<p>M\u00e3:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ =================================================================================\n\/\/ C\u1ea4U H\u00ccNH BAN \u0110\u1ea6U (B\u1ea0N C\u1ea6N THAY \u0110\u1ed4I C\u00c1C GI\u00c1 TR\u1eca N\u00c0Y)\n\/\/ =================================================================================\nconst WOO_SITE_URL = 'YOUR_WOOCOMMERCE_SITE_URL'; \/\/ V\u00ed d\u1ee5: 'https:\/\/mywoo.com' (KH\u00d4NG c\u00f3 d\u1ea5u \/ \u1edf cu\u1ed1i)\nconst WOO_CONSUMER_KEY = 'YOUR_CONSUMER_KEY';     \/\/ D\u00e1n Consumer Key c\u1ee7a b\u1ea1n v\u00e0o \u0111\u00e2y\nconst WOO_CONSUMER_SECRET = 'YOUR_CONSUMER_SECRET'; \/\/ D\u00e1n Consumer Secret c\u1ee7a b\u1ea1n v\u00e0o \u0111\u00e2y\n\nconst SHEET_NAME = 'WooCommerce Orders';\nconst ORDERS_PER_PAGE = 20; \/\/ Gi\u1eef \u1edf m\u1ee9c v\u1eeba ph\u1ea3i \u0111\u1ec3 tr\u00e1nh timeout khi fetch nhi\u1ec1u trang\nconst LAST_FETCHED_ORDER_DATE_KEY = 'lastFetchedOrderDateGmt'; \/\/ Key \u0111\u1ec3 l\u01b0u tr\u1eef trong PropertiesService\n\n\/\/ =================================================================================\n\/\/ H\u00c0M CH\u1ea0Y KHI M\u1ede SHEET\n\/\/ =================================================================================\nfunction onOpen() {\n  SpreadsheetApp.getUi()\n      .createMenu('WooCommerce Sync')\n      .addItem('Fetch\/Update Orders', 'syncOrders') \/\/ \u0110\u1ed5i t\u00ean h\u00e0m g\u1ecdi\n      .addItem('Full Resync (Clear &amp; Fetch All)', 'fullResyncConfirmation')\n      .addItem('Setup Auto Update Trigger', 'setupAutoUpdateTriggerDialog')\n      .addToUi();\n}\n\nfunction fullResyncConfirmation() {\n  const ui = SpreadsheetApp.getUi();\n  const response = ui.alert(\n    'Confirm Full Resync',\n    'This will clear all existing order data and fetch everything from the beginning. This is useful if you want to reset STT. Continue?',\n    ui.ButtonSet.YES_NO\n  );\n  if (response == ui.Button.YES) {\n    clearSheetAndState(); \/\/ X\u00f3a sheet v\u00e0 tr\u1ea1ng th\u00e1i \u0111\u00e3 l\u01b0u\n    syncOrders(); \/\/ Ch\u1ea1y full sync\n  }\n}\n\n\/\/ =================================================================================\n\/\/ H\u00c0M X\u00d3A D\u1eee LI\u1ec6U V\u00c0 TR\u1ea0NG TH\u00c1I \u0110\u00c3 L\u01afU\n\/\/ =================================================================================\nfunction clearSheetAndState() {\n  const ui = SpreadsheetApp.getUi();\n  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);\n  if (sheet) {\n    sheet.clear(); \/\/ X\u00f3a to\u00e0n b\u1ed9 n\u1ed9i dung sheet\n    Logger.log(`Sheet \"${SHEET_NAME}\" cleared.`);\n  }\n  \/\/ X\u00f3a ng\u00e0y fetch cu\u1ed1i c\u00f9ng \u0111\u00e3 l\u01b0u \u0111\u1ec3 k\u00edch ho\u1ea1t full sync\n  PropertiesService.getScriptProperties().deleteProperty(LAST_FETCHED_ORDER_DATE_KEY);\n  Logger.log(`Property \"${LAST_FETCHED_ORDER_DATE_KEY}\" deleted.`);\n  ui.alert('Sheet Cleared', 'Order data and last fetch state have been cleared. Next fetch will be a full resync.', ui.ButtonSet.OK);\n}\n\n\n\/\/ =================================================================================\n\/\/ H\u00c0M CH\u00cdNH \u0110\u1ec2 \u0110\u1ed2NG B\u1ed8 \u0110\u01a0N H\u00c0NG (FULL SYNC HO\u1eb6C INCREMENTAL)\n\/\/ =================================================================================\nfunction syncOrders() {\n  const ui = SpreadsheetApp.getUi();\n\n  if (!WOO_SITE_URL || WOO_SITE_URL === 'YOUR_WOOCOMMERCE_SITE_URL' || !WOO_CONSUMER_KEY || WOO_CONSUMER_KEY === 'YOUR_CONSUMER_KEY' || !WOO_CONSUMER_SECRET || WOO_CONSUMER_SECRET === 'YOUR_CONSUMER_SECRET') {\n    ui.alert('Configuration Error', 'Please ensure WOO_SITE_URL, WOO_CONSUMER_KEY, and WOO_CONSUMER_SECRET are correctly set at the top of the script.', ui.ButtonSet.OK);\n    return;\n  }\n\n  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);\n  if (!sheet) {\n    sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(SHEET_NAME);\n    Logger.log(`Sheet \"${SHEET_NAME}\" created.`);\n  }\n\n  const scriptProperties = PropertiesService.getScriptProperties();\n  const lastFetchedDateGmt = scriptProperties.getProperty(LAST_FETCHED_ORDER_DATE_KEY);\n  let isIncrementalSync = false;\n  let apiEndpoint = `\/wp-json\/wc\/v3\/orders?per_page=${ORDERS_PER_PAGE}&amp;orderby=date&amp;order=asc`; \/\/ Lu\u00f4n l\u1ea5y ASC \u0111\u1ec3 STT \u0111\u00fang\n\n  if (lastFetchedDateGmt &amp;&amp; sheet.getLastRow() &gt; 1) { \/\/ N\u1ebfu c\u00f3 ng\u00e0y \u0111\u00e3 l\u01b0u v\u00e0 sheet c\u00f3 d\u1eef li\u1ec7u (h\u01a1n 1 d\u00f2ng ti\u00eau \u0111\u1ec1)\n    isIncrementalSync = true;\n    apiEndpoint += `&amp;after=${lastFetchedDateGmt}`; \/\/ Ch\u1ec9 l\u1ea5y \u0111\u01a1n h\u00e0ng m\u1edbi h\u01a1n ng\u00e0y \u0111\u00e3 l\u01b0u\n    SpreadsheetApp.getActiveSpreadsheet().toast('Fetching new orders...', 'WooCommerce Sync', -1);\n    Logger.log(`Incremental sync. Fetching orders after: ${lastFetchedDateGmt}`);\n  } else {\n    SpreadsheetApp.getActiveSpreadsheet().toast('Performing full sync of orders...', 'WooCommerce Sync', -1);\n    Logger.log('Full sync. Fetching all orders (or sheet was empty\/cleared).');\n    if (sheet.getLastRow() &gt; 0) { \/\/ N\u1ebfu l\u00e0 full sync m\u00e0 sheet c\u00f3 d\u1eef li\u1ec7u th\u00ec x\u00f3a \u0111i\n        sheet.clear();\n    }\n  }\n\n  try {\n    let allNewOrdersData = &#91;];\n    let page = 1;\n    let moreOrdersExist = true;\n    let latestOrderDateInBatch = null;\n\n    while (moreOrdersExist) {\n      const currentApiEndpoint = `${apiEndpoint}&amp;page=${page}`;\n      const response = callWooCommerceApi(currentApiEndpoint, 'GET');\n\n      if (response.getResponseCode() === 200) {\n        const orders = JSON.parse(response.getContentText());\n        if (orders.length &gt; 0) {\n          allNewOrdersData.push(...orders);\n          \/\/ T\u00ecm ng\u00e0y t\u1ea1o m\u1edbi nh\u1ea5t trong batch n\u00e0y (quan tr\u1ecdng cho incremental sync)\n          orders.forEach(order =&gt; {\n            const orderDate = new Date(order.date_created_gmt + 'Z');\n            if (!latestOrderDateInBatch || orderDate &gt; latestOrderDateInBatch) {\n              latestOrderDateInBatch = orderDate;\n            }\n          });\n          SpreadsheetApp.getActiveSpreadsheet().toast(`Fetched page ${page} (${orders.length} orders)... Total new: ${allNewOrdersData.length}`, 'WooCommerce Sync', 10);\n          page++;\n          if (orders.length &lt; ORDERS_PER_PAGE) {\n            moreOrdersExist = false;\n          }\n        } else {\n          moreOrdersExist = false;\n        }\n      } else {\n        Logger.log(`Error fetching orders (Page ${page}): ${response.getResponseCode()} - ${response.getContentText()}`);\n        ui.alert('API Error', `Failed to fetch orders (Page ${page}). Status: ${response.getResponseCode()}. Response: ${response.getContentText().substring(0, 300)}... Check logs for details.`, ui.ButtonSet.OK);\n        SpreadsheetApp.getActiveSpreadsheet().toast('Error fetching orders.', 'WooCommerce Sync');\n        return;\n      }\n      Utilities.sleep(500);\n    }\n\n    if (allNewOrdersData.length &gt; 0) {\n      writeOrdersToSheet(allNewOrdersData, isIncrementalSync, sheet);\n      \/\/ C\u1eadp nh\u1eadt ng\u00e0y fetch cu\u1ed1i c\u00f9ng, s\u1eed d\u1ee5ng ISO string \u0111\u1ec3 \u0111\u1ea3m b\u1ea3o \u0111\u1ecbnh d\u1ea1ng chu\u1ea9n v\u00e0 m\u00fai gi\u1edd UTC\n      \/\/ Th\u00eam 1 gi\u00e2y \u0111\u1ec3 \u0111\u1ea3m b\u1ea3o kh\u00f4ng l\u1ea5y tr\u00f9ng \u0111\u01a1n h\u00e0ng \u1edf l\u1ea7n fetch sau n\u1ebfu c\u00f3 nhi\u1ec1u \u0111\u01a1n trong c\u00f9ng 1 gi\u00e2y.\n      if (latestOrderDateInBatch) {\n        const nextFetchDate = new Date(latestOrderDateInBatch.getTime() + 1000); \/\/ Th\u00eam 1 gi\u00e2y\n        scriptProperties.setProperty(LAST_FETCHED_ORDER_DATE_KEY, nextFetchDate.toISOString().slice(0, -5)); \/\/ YYYY-MM-DDTHH:MM:SS (b\u1ecf .SSSZ)\n        Logger.log(`Updated last fetched date to: ${scriptProperties.getProperty(LAST_FETCHED_ORDER_DATE_KEY)}`);\n      }\n      SpreadsheetApp.getActiveSpreadsheet().toast(`Successfully processed ${allNewOrdersData.length} orders!`, 'WooCommerce Sync', 10);\n    } else {\n      SpreadsheetApp.getActiveSpreadsheet().toast(isIncrementalSync ? 'No new orders found.' : 'No orders found for full sync.', 'WooCommerce Sync', 10);\n    }\n\n  } catch (e) {\n    Logger.log(`Error in syncOrders: ${e.toString()}\\n${e.stack}`);\n    ui.alert('Script Error', `An error occurred: ${e.message}. Check logs.`, ui.ButtonSet.OK);\n    SpreadsheetApp.getActiveSpreadsheet().toast('Script error.', 'WooCommerce Sync');\n  }\n}\n\n\/\/ =================================================================================\n\/\/ H\u00c0M G\u1eccI WOOCOMMERCE API (Kh\u00f4ng thay \u0111\u1ed5i)\n\/\/ =================================================================================\nfunction callWooCommerceApi(endpoint, method, payload = null) {\n  const url = WOO_SITE_URL + endpoint;\n  const options = {\n    method: method.toLowerCase(),\n    headers: {\n      'Authorization': 'Basic ' + Utilities.base64Encode(WOO_CONSUMER_KEY + ':' + WOO_CONSUMER_SECRET)\n    },\n    contentType: 'application\/json',\n    muteHttpExceptions: true\n  };\n\n  if (payload &amp;&amp; (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT')) {\n    options.payload = JSON.stringify(payload);\n  }\n\n  Logger.log(`Calling API: ${method} ${url}`);\n  const response = UrlFetchApp.fetch(url, options);\n  Logger.log(`API Response Code: ${response.getResponseCode()}`);\n  return response;\n}\n\n\/\/ =================================================================================\n\/\/ H\u00c0M GHI D\u1eee LI\u1ec6U \u0110\u01a0N H\u00c0NG L\u00caN SHEET (Thay \u0111\u1ed5i nhi\u1ec1u)\n\/\/ =================================================================================\nfunction writeOrdersToSheet(ordersData, isIncrementalSync, sheet) {\n  if (!sheet) {\n    Logger.log(`Sheet \"${SHEET_NAME}\" not found for writing.`);\n    SpreadsheetApp.getUi().alert('Error', `Sheet \"${SHEET_NAME}\" not found. Cannot write data.`);\n    return;\n  }\n\n  \/\/ \u0110\u1ecbnh ngh\u0129a c\u00e1c c\u1ed9t (KH\u00d4NG bao g\u1ed3m STT \u1edf \u0111\u00e2y, STT s\u1ebd \u0111\u01b0\u1ee3c th\u00eam t\u1ef1 \u0111\u1ed9ng)\n  const columnMappings = {\n    'Order ID': order =&gt; order.id,\n    'Order Number': order =&gt; order.number,\n    'Status': order =&gt; order.status,\n    'Date Created': order =&gt; order.date_created_gmt ? new Date(order.date_created_gmt + 'Z').toLocaleString() : (order.date_created ? new Date(order.date_created + 'Z').toLocaleString() : ''),\n    'Customer ID': order =&gt; order.customer_id || 'Guest',\n    'Billing Name': order =&gt; `${order.billing.first_name || ''} ${order.billing.last_name || ''}`.trim(),\n    'Billing Email': order =&gt; order.billing.email,\n    'Billing Phone': order =&gt; order.billing.phone,\n    'Shipping Name': order =&gt; `${order.shipping.first_name || ''} ${order.shipping.last_name || ''}`.trim(),\n    'Payment Method': order =&gt; order.payment_method_title,\n    'Total Amount': order =&gt; parseFloat(order.total) || 0,\n    'Currency': order =&gt; order.currency,\n    'Product Names': order =&gt; order.line_items.map(item =&gt; `${item.name} (Qty: ${item.quantity})`).join('\\n'),\n    'Total Items': order =&gt; order.line_items.reduce((sum, item) =&gt; sum + item.quantity, 0)\n  };\n  const dataHeaders = Object.keys(columnMappings); \/\/ Ti\u00eau \u0111\u1ec1 d\u1eef li\u1ec7u\n  const fullHeaders = &#91;\"STT\", ...dataHeaders];     \/\/ Ti\u00eau \u0111\u1ec1 \u0111\u1ea7y \u0111\u1ee7 cho sheet (bao g\u1ed3m STT)\n\n  const dataToWrite = &#91;];\n  let startRow;\n  let currentStt = 0;\n\n  if (isIncrementalSync) {\n    const lastSheetRow = sheet.getLastRow();\n    if (lastSheetRow &lt; 1) { \/\/ Sheet tr\u1ed1ng ho\u00e0n to\u00e0n, kh\u00f4ng th\u1ec3 l\u00e0 incremental\n        isIncrementalSync = false; \/\/ Chuy\u1ec3n sang full sync\n        Logger.log(\"Sheet was empty, forcing full sync mode for writing.\");\n    } else {\n        const lastSttValue = sheet.getRange(lastSheetRow, 1).getValue(); \/\/ Gi\u1ea3 s\u1eed STT \u1edf c\u1ed9t 1\n        currentStt = (typeof lastSttValue === 'number' &amp;&amp; lastSttValue &gt; 0) ? lastSttValue : 0;\n        startRow = lastSheetRow + 1;\n        Logger.log(`Incremental write. Starting STT from ${currentStt + 1}. Start row: ${startRow}`);\n    }\n  }\n  \n  if (!isIncrementalSync) { \/\/ Full sync ho\u1eb7c sheet tr\u1ed1ng\/m\u1edbi\n    if (sheet.getLastRow() &gt; 0) sheet.clearContents(); \/\/ X\u00f3a n\u1ed9i dung c\u0169 n\u1ebfu c\u00f3, kh\u00f4ng x\u00f3a sheet object\n    sheet.getRange(1, 1, 1, fullHeaders.length).setValues(&#91;fullHeaders]); \/\/ Ghi ti\u00eau \u0111\u1ec1 \u0111\u1ea7y \u0111\u1ee7\n    sheet.getRange(\"A1\").setFontWeight(\"bold\"); \/\/ In \u0111\u1eadm STT\n    sheet.getRange(1, 1, 1, fullHeaders.length).setFontWeight(\"bold\"); \/\/ In \u0111\u1eadm c\u1ea3 d\u00f2ng ti\u00eau \u0111\u1ec1\n    currentStt = 0;\n    startRow = 2; \/\/ D\u1eef li\u1ec7u b\u1eaft \u0111\u1ea7u t\u1eeb d\u00f2ng 2\n    Logger.log(\"Full write. Headers written. Starting STT from 1. Start row: 2\");\n  }\n\n\n  ordersData.forEach(order =&gt; {\n    currentStt++;\n    const rowDataValues = dataHeaders.map(header =&gt; {\n      try {\n        return columnMappings&#91;header](order);\n      } catch (e) {\n        Logger.log(`Error processing field \"${header}\" for order ID ${order.id}: ${e.message}`);\n        return `Error processing field`;\n      }\n    });\n    dataToWrite.push(&#91;currentStt, ...rowDataValues]); \/\/ Th\u00eam STT v\u00e0o \u0111\u1ea7u m\u1ed7i h\u00e0ng d\u1eef li\u1ec7u\n  });\n\n  if (dataToWrite.length &gt; 0) {\n    sheet.getRange(startRow, 1, dataToWrite.length, fullHeaders.length).setValues(dataToWrite);\n    \/\/ \u00c1p d\u1ee5ng \u0111\u1ecbnh d\u1ea1ng v\u00e0 t\u1ef1 \u0111\u1ed9ng \u0111i\u1ec1u ch\u1ec9nh \u0111\u1ed9 r\u1ed9ng c\u1ed9t\n    try {\n      fullHeaders.forEach((header, index) =&gt; {\n        sheet.autoResizeColumn(index + 1);\n      });\n      const currencyColumnIndex = fullHeaders.indexOf('Total Amount') + 1;\n      if (currencyColumnIndex &gt; 0) {\n        sheet.getRange(startRow, currencyColumnIndex, dataToWrite.length, 1).setNumberFormat('#,##0.00');\n      }\n      const dateColumnIndex = fullHeaders.indexOf('Date Created') + 1;\n      if (dateColumnIndex &gt; 0) {\n        sheet.getRange(startRow, dateColumnIndex, dataToWrite.length, 1).setNumberFormat('yyyy-mm-dd hh:mm:ss');\n      }\n    } catch (e) {\n      Logger.log(\"Error during formatting\/resizing columns: \" + e);\n    }\n  } else {\n    Logger.log(\"No new data rows to write.\");\n  }\n}\n\n\/\/ =================================================================================\n\/\/ H\u00c0M \u0110\u1ec2 THI\u1ebeT L\u1eacP TRIGGER T\u1ef0 \u0110\u1ed8NG C\u1eacP NH\u1eacT\n\/\/ =================================================================================\nfunction triggerAutoSync() {\n  Logger.log(\"Auto-sync trigger fired.\");\n  syncOrders();\n}\n\nfunction setupAutoUpdateTriggerDialog() {\n  const ui = SpreadsheetApp.getUi();\n  const result = ui.prompt(\n      'Setup Auto Update',\n      'Enter update frequency in minutes (e.g., 15, 30, 60). Minimum 15 minutes recommended for frequent updates, or higher for less frequent.',\n      ui.ButtonSet.OK_CANCEL);\n\n  if (result.getSelectedButton() == ui.Button.OK) {\n    const minutes = parseInt(result.getResponseText());\n    if (isNaN(minutes) || minutes &lt; 1) {\n      ui.alert('Invalid Input', 'Please enter a valid number of minutes (e.g., 15 or more).', ui.ButtonSet.OK);\n      return;\n    }\n    \n    \/\/ X\u00f3a c\u00e1c trigger c\u0169 c\u00f3 t\u00ean h\u00e0m l\u00e0 'triggerAutoSync' \u0111\u1ec3 tr\u00e1nh tr\u00f9ng l\u1eb7p\n    const existingTriggers = ScriptApp.getProjectTriggers();\n    for (let i = 0; i &lt; existingTriggers.length; i++) {\n      if (existingTriggers&#91;i].getHandlerFunction() === 'triggerAutoSync') {\n        ScriptApp.deleteTrigger(existingTriggers&#91;i]);\n        Logger.log(`Deleted existing trigger ID: ${existingTriggers&#91;i].getUniqueId()}`);\n      }\n    }\n\n    \/\/ T\u1ea1o trigger m\u1edbi\n    ScriptApp.newTrigger('triggerAutoSync')\n        .timeBased()\n        .everyMinutes(minutes)\n        .create();\n    ui.alert('Trigger Created', `Automatic order sync scheduled to run every ${minutes} minutes. You can manage triggers via \"Edit &gt; Current project's triggers\".`, ui.ButtonSet.OK);\n    Logger.log(`New trigger created to run \"triggerAutoSync\" every ${minutes} minutes.`);\n  }\n}\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"542\" src=\"http:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_nmmb_IDoJ.webp\" alt=\"\" class=\"wp-image-2935\" srcset=\"https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_nmmb_IDoJ.webp 1920w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_nmmb_IDoJ-300x85.webp 300w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_nmmb_IDoJ-1024x289.webp 1024w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_nmmb_IDoJ-768x217.webp 768w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_nmmb_IDoJ-1536x434.webp 1536w\" sizes=\"auto, (max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 3: C\u00e1ch s\u1eed d\u1ee5ng Script<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"931\" height=\"481\" src=\"http:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_egHK_yksu.webp\" alt=\"\" class=\"wp-image-2936\" srcset=\"https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_egHK_yksu.webp 931w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_egHK_yksu-300x155.webp 300w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_egHK_yksu-768x397.webp 768w\" sizes=\"auto, (max-width: 931px) 100vw, 931px\" \/><\/figure>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>C\u1ea5u h\u00ecnh Script:<\/strong>\n<ul class=\"wp-block-list\">\n<li>M\u1edf l\u1ea1i tr\u00ecnh so\u1ea1n th\u1ea3o Apps Script (Extensions &gt; Apps Script).<\/li>\n\n\n\n<li>\u1ede \u0111\u1ea7u file m\u00e3, thay \u0111\u1ed5i c\u00e1c gi\u00e1 tr\u1ecb sau:\n<ul class=\"wp-block-list\">\n<li><code><strong>YOUR_WOOCOMMERCE_SITE_URL<\/strong><\/code>: B\u1eb1ng URL website WordPress c\u1ee7a b\u1ea1n (v\u00ed d\u1ee5: <code>https:\/\/shopcuaban.com<\/code>). <strong>KH\u00d4NG<\/strong> c\u00f3 d\u1ea5u <code>\/<\/code> \u1edf cu\u1ed1i.<\/li>\n\n\n\n<li><code><strong>YOUR_CONSUMER_KEY<\/strong><\/code>: B\u1eb1ng Consumer Key b\u1ea1n \u0111\u00e3 t\u1ea1o \u1edf B\u01b0\u1edbc 1.<\/li>\n\n\n\n<li><code><strong>YOUR_CONSUMER_SECRET<\/strong><\/code>: B\u1eb1ng Consumer Secret b\u1ea1n \u0111\u00e3 t\u1ea1o \u1edf B\u01b0\u1edbc 1.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>B\u1ea1n c\u00f3 th\u1ec3 thay \u0111\u1ed5i <code><strong>SHEET_NAME<\/strong><\/code> n\u1ebfu mu\u1ed1n \u0111\u1eb7t t\u00ean sheet kh\u00e1c.<\/li>\n\n\n\n<li>B\u1ea1n c\u00f3 th\u1ec3 \u0111i\u1ec1u ch\u1ec9nh <code><strong>ORDERS_PER_PAGE<\/strong><\/code> (s\u1ed1 l\u01b0\u1ee3ng \u0111\u01a1n h\u00e0ng l\u1ea5y m\u1ed7i l\u1ea7n g\u1ecdi API, t\u1ed1i \u0111a th\u01b0\u1eddng l\u00e0 100, nh\u01b0ng gi\u00e1 tr\u1ecb nh\u1ecf h\u01a1n nh\u01b0 20-50 c\u00f3 th\u1ec3 \u1ed5n \u0111\u1ecbnh h\u01a1n cho c\u00e1c server y\u1ebfu).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>L\u01b0u Script:<\/strong> Nh\u1ea5n v\u00e0o bi\u1ec3u t\u01b0\u1ee3ng l\u01b0u (h\u00ecnh \u0111\u0129a m\u1ec1m).<\/li>\n\n\n\n<li><strong>Ch\u1ea1y L\u1ea7n \u0111\u1ea7u (C\u1ea5p quy\u1ec1n):<\/strong>\n<ul class=\"wp-block-list\">\n<li>Quay l\u1ea1i Google Sheet c\u1ee7a b\u1ea1n.<\/li>\n\n\n\n<li>C\u00f3 th\u1ec3 b\u1ea1n c\u1ea7n <strong>t\u1ea3i l\u1ea1i (refresh)<\/strong> trang Google Sheet \u0111\u1ec3 menu &#8220;WooCommerce Sync&#8221; xu\u1ea5t hi\u1ec7n.<\/li>\n\n\n\n<li>Nh\u1ea5p v\u00e0o <strong>WooCommerce Sync &gt; Fetch Orders<\/strong>.<\/li>\n\n\n\n<li>L\u1ea7n \u0111\u1ea7u ti\u00ean ch\u1ea1y, Google s\u1ebd y\u00eau c\u1ea7u b\u1ea1n c\u1ea5p quy\u1ec1n cho script.\n<ul class=\"wp-block-list\">\n<li>Nh\u1ea5n &#8220;Continue&#8221;.<\/li>\n\n\n\n<li>Ch\u1ecdn t\u00e0i kho\u1ea3n Google c\u1ee7a b\u1ea1n.<\/li>\n\n\n\n<li>B\u1ea1n c\u00f3 th\u1ec3 th\u1ea5y c\u1ea3nh b\u00e1o &#8220;Google hasn\u2019t verified this app&#8221;. Nh\u1ea5n v\u00e0o &#8220;Advanced&#8221; (N\u00e2ng cao) r\u1ed3i ch\u1ecdn &#8220;Go to [T\u00ean d\u1ef1 \u00e1n c\u1ee7a b\u1ea1n] (unsafe)&#8221; (\u0110i t\u1edbi [T\u00ean d\u1ef1 \u00e1n c\u1ee7a b\u1ea1n] (kh\u00f4ng an to\u00e0n)).<\/li>\n\n\n\n<li>Xem l\u1ea1i c\u00e1c quy\u1ec1n v\u00e0 nh\u1ea5n &#8220;Allow&#8221; (Cho ph\u00e9p). Script c\u1ea7n quy\u1ec1n \u0111\u1ec3 k\u1ebft n\u1ed1i v\u1edbi d\u1ecbch v\u1ee5 b\u00ean ngo\u00e0i (website c\u1ee7a b\u1ea1n) v\u00e0 ch\u1ec9nh s\u1eeda Google Sheet. <\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>L\u1ea7n Ch\u1ea1y \u0110\u1ea7u Ti\u00ean (ho\u1eb7c sau khi mu\u1ed1n reset):<\/strong> <\/li>\n\n\n\n<li><strong>C\u1eadp nh\u1eadt \u0110\u01a1n H\u00e0ng M\u1edbi (Th\u1ee7 c\u00f4ng):<\/strong>\n<ul class=\"wp-block-list\">\n<li>Ch\u1ecdn <strong>WooCommerce Sync &gt; Fetch\/Update Orders<\/strong>. Script s\u1ebd ch\u1ec9 l\u1ea5y c\u00e1c \u0111\u01a1n h\u00e0ng m\u1edbi k\u1ec3 t\u1eeb l\u1ea7n fetch th\u00e0nh c\u00f4ng cu\u1ed1i c\u00f9ng v\u00e0 n\u1ed1i v\u00e0o cu\u1ed1i b\u1ea3ng v\u1edbi STT ti\u1ebfp t\u1ee5c.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">B\u01b0\u1edbc 4. Thi\u1ebft l\u1eadp t\u1ef1 \u0111\u1ed9ng c\u1eadp nh\u1eadt<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"679\" height=\"295\" src=\"http:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_B0Ve_UoJp.webp\" alt=\"\" class=\"wp-image-2937\" srcset=\"https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_B0Ve_UoJp.webp 679w, https:\/\/manhtv.io.vn\/wp-content\/uploads\/2025\/05\/Huong-d_image_image_B0Ve_UoJp-300x130.webp 300w\" sizes=\"auto, (max-width: 679px) 100vw, 679px\" \/><\/figure>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Ch\u1ecdn <strong>WooCommerce Sync &gt; Setup Auto Update Trigger<\/strong>.<\/li>\n\n\n\n<li>Nh\u1eadp s\u1ed1 ph\u00fat b\u1ea1n mu\u1ed1n script t\u1ef1 \u0111\u1ed9ng ch\u1ea1y (v\u00ed d\u1ee5: <code>15<\/code>, <code>30<\/code>, <code>60<\/code>).<\/li>\n\n\n\n<li>Nh\u1ea5n OK. M\u1ed9t trigger s\u1ebd \u0111\u01b0\u1ee3c t\u1ea1o.<\/li>\n\n\n\n<li>B\u1ea1n c\u00f3 th\u1ec3 qu\u1ea3n l\u00fd c\u00e1c trigger n\u00e0y b\u1eb1ng c\u00e1ch v\u00e0o tr\u00ecnh so\u1ea1n th\u1ea3o Apps Script, ch\u1ecdn bi\u1ec3u t\u01b0\u1ee3ng \u0111\u1ed3ng h\u1ed3 (Triggers) \u1edf thanh b\u00ean tr\u00e1i. <\/li>\n<\/ol>\n\n\n\n<p>Sau khi tri\u1ec3n khai c\u00e1c b\u01b0\u1edbc tr\u00ean, b\u1ea1n \u0111\u00e3 t\u1ef1 code \u0111\u01b0\u1ee3c <a href=\"https:\/\/opendb.vn\/huong-dan-tu-dong-bo-du-lieu-don-hang-tu-wordpress-woocommerce-ve-google-sheet-su-dung-google-appscript\/\">t\u00ednh n\u0103ng l\u1ea5y \u0111\u01a1n h\u00e0ng t\u1ef1 \u0111\u1ed9ng t\u1eeb website wordpress<\/a> s\u1eed d\u1ee5ng woocommerce v\u1ec1 google sheet. C\u00e1c \u0111\u01a1n h\u00e0ng s\u1ebd t\u1ef1 \u0111\u1ed9ng t\u1ea3i m\u1edbi v\u1ec1 danh s\u00e1ch sau 1 gi\u1edd &#8211; 6 gi\u1edd hay 1 ng\u00e0y tu\u1ef3 theo thi\u1ebft l\u1eadp c\u1ee7a b\u1ea1n \u1edf b\u01b0\u1edbc 4. <\/p>\n\n\n\n<p>C\u1ea3m \u01a1n b\u1ea1n \u0111\u00e3 \u0111\u1ecdc b\u00e0i vi\u1ebft t\u1eeb Opendb.vn &#8211; \u0110\u0103ng k\u00fd \u0111\u1ec3 nh\u1eadn tin &amp; b\u00e0i vi\u1ebft m\u1edbi gi\u00fap t\u1ef1 \u0111\u1ed9ng ho\u00e1 c\u00f4ng vi\u1ec7c hi\u1ec7u qu\u1ea3 nh\u00e9! <\/p>\n","protected":false},"excerpt":{"rendered":"<p>H\u01b0\u1edbng d\u1eabn chi ti\u1ebft gi\u00fap b\u1ea1n t\u1ef1 code m\u1ed9t t\u00ednh n\u0103ng \u0111\u1ed3ng b\u1ed9 th\u00f4ng tin \u0111\u01a1n h\u00e0ng WooCommerce v\u1ec1 Google Sheet s\u1eed d\u1ee5ng REST API c\u1ee7a woocommerce v\u00e0 ho\u00e0n to\u00e0n mi\u1ec5n ph\u00ed Ch\u00fang ta s\u1ebd c\u00f9ng nhau x\u00e2y d\u1ef1ng m\u1ed9t phi\u00ean b\u1ea3n c\u01a1 b\u1ea3n s\u1eed d\u1ee5ng REST API c\u1ee7a WooCommerce gi\u00fap t\u1ef1 \u0111\u1ed9ng ho\u00e1 m\u1ed9t [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":2934,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-container-style":"default","site-container-layout":"default","site-sidebar-layout":"default","disable-article-header":"default","disable-site-header":"default","disable-site-footer":"default","disable-content-area-spacing":"default","footnotes":""},"categories":[47,24],"tags":[],"class_list":["post-2925","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-google-appscript","category-huong-dan"],"_links":{"self":[{"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/posts\/2925","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/comments?post=2925"}],"version-history":[{"count":0,"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/posts\/2925\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/media\/2934"}],"wp:attachment":[{"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/media?parent=2925"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/categories?post=2925"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/manhtv.io.vn\/index.php\/wp-json\/wp\/v2\/tags?post=2925"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}