• Trang chủ
  • Tôi là ai
  • Tôi làm
  • Tôi học
  • Kiếm tiền từ Google Adsense
  • Cách sống
  • Yêu công nghệ
  • Marketing

Đỗ Mạnh Hùng - Blog của Đỗ Mạnh Hùng SEO Expert, Mmomaster

kuking

You are here: Home / Tôi học / Tự đồng đưa đơn hàng từ wordpress vào Google Sheet

Tự đồng đưa đơn hàng từ wordpress vào Google Sheet

September 10, 2025 by Đỗ Mạnh Hùng

Hướng dẫn cho quý bạn Tự đồng bộ dữ liệu đơn hàng từ wordpress woocommerce về google sheet sử dụng Google AppScript. Vào Google Sheet rồi bạn hoàn toàn có thể quản lý đơn hàng, gửi email cho khách, chăm sóc khách hàng mà không cần một CRM nào phức tạp, cách làm đơn giản như sau

Tổng quan các bước:

  1. Chuẩn bị phía WooCommerce:
    • Kích hoạt REST API.
    • Tạo API Keys (Consumer Key & Consumer Secret).
    • Đảm bảo website của bạn sử dụng HTTPS.
  2. Viết mã Google Apps Script:
    • Tạo menu tùy chỉnh trong Google Sheet.
    • Thiết lập nơi lưu trữ thông tin cấu hình (URL website, API Keys).
    • Viết hàm gọi API WooCommerce.
    • Viết hàm xử lý dữ liệu JSON trả về.
    • Viết hàm ghi dữ liệu lên Google Sheet.
    • Xử lý lỗi cơ bản.
    • Xử lý phân trang (nếu có nhiều đơn hàng).

Triển khai các bước:

Chuẩn bị tải đơn hàng tự động từ WooCommerce

  1. Kích hoạt REST API:
    • Trong WordPress admin, đi tới WooCommerce > Settings > Advanced > REST API.
    • Nhấn “Add key”.
  2. Tạo API Keys:
    • Description: Đặt tên cho key (ví dụ: “Google Sheet Sync”).
    • User: Chọn người dùng quản trị (hoặc người dùng có quyền truy cập đơn hàng).
    • Permissions: Chọn “Read” (Đọc) nếu bạn chỉ muốn lấy dữ liệu. Nếu sau này muốn cập nhật đơn hàng từ Sheet thì cần “Read/Write”. Để an toàn, hãy bắt đầu với “Read”.
    • Nhấn “Generate API key”.
    • Quan trọng: Sao chép ngay lập tức Consumer key và Consumer secret. Chúng sẽ không hiển thị lại. Hãy lưu trữ chúng ở một nơi an toàn.
  3. HTTPS: Đảm bảo website WordPress của bạn đang sử dụng HTTPS. WooCommerce REST API yêu cầu HTTPS để hoạt động an toàn.

Bước 2: Viết mã Google Apps Script

Mở Google Sheet của bạn (hoặc tạo một sheet mới), sau đó vào Extensions > Apps Script. Xóa mọi mã mặc định và dán mã bên dưới sau vào:

Mã:

// =================================================================================
// CẤU HÌNH BAN ĐẦU (BẠN CẦN THAY ĐỔI CÁC GIÁ TRỊ NÀY)
// =================================================================================
const WOO_SITE_URL = 'YOUR_WOOCOMMERCE_SITE_URL'; // Ví dụ: 'https://mywoo.com' (KHÔNG có dấu / ở cuối)
const WOO_CONSUMER_KEY = 'YOUR_CONSUMER_KEY'; // Dán Consumer Key của bạn vào đây
const WOO_CONSUMER_SECRET = 'YOUR_CONSUMER_SECRET'; // Dán Consumer Secret của bạn vào đây

const SHEET_NAME = ‘WooCommerce Orders’;
const ORDERS_PER_PAGE = 20; // Giữ ở mức vừa phải để tránh timeout khi fetch nhiều trang
const LAST_FETCHED_ORDER_DATE_KEY = ‘lastFetchedOrderDateGmt’; // Key để lưu trữ trong PropertiesService

// =================================================================================
// HÀM CHẠY KHI MỞ SHEET
// =================================================================================
function onOpen() {
SpreadsheetApp.getUi()
.createMenu(‘WooCommerce Sync’)
.addItem(‘Fetch/Update Orders’, ‘syncOrders’) // Đổi tên hàm gọi
.addItem(‘Full Resync (Clear & Fetch All)’, ‘fullResyncConfirmation’)
.addItem(‘Setup Auto Update Trigger’, ‘setupAutoUpdateTriggerDialog’)
.addToUi();
}

function fullResyncConfirmation() {
const ui = SpreadsheetApp.getUi();
const response = ui.alert(
‘Confirm Full Resync’,
‘This will clear all existing order data and fetch everything from the beginning. This is useful if you want to reset STT. Continue?’,
ui.ButtonSet.YES_NO
);
if (response == ui.Button.YES) {
clearSheetAndState(); // Xóa sheet và trạng thái đã lưu
syncOrders(); // Chạy full sync
}
}

// =================================================================================
// HÀM XÓA DỮ LIỆU VÀ TRẠNG THÁI ĐÃ LƯU
// =================================================================================
function clearSheetAndState() {
const ui = SpreadsheetApp.getUi();
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (sheet) {
sheet.clear(); // Xóa toàn bộ nội dung sheet
Logger.log(`Sheet “${SHEET_NAME}” cleared.`);
}
// Xóa ngày fetch cuối cùng đã lưu để kích hoạt full sync
PropertiesService.getScriptProperties().deleteProperty(LAST_FETCHED_ORDER_DATE_KEY);
Logger.log(`Property “${LAST_FETCHED_ORDER_DATE_KEY}” deleted.`);
ui.alert(‘Sheet Cleared’, ‘Order data and last fetch state have been cleared. Next fetch will be a full resync.’, ui.ButtonSet.OK);
}

// =================================================================================
// HÀM CHÍNH ĐỂ ĐỒNG BỘ ĐƠN HÀNG (FULL SYNC HOẶC INCREMENTAL)
// =================================================================================
function syncOrders() {
const ui = SpreadsheetApp.getUi();

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’) {
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);
return;
}

let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(SHEET_NAME);
Logger.log(`Sheet “${SHEET_NAME}” created.`);
}

const scriptProperties = PropertiesService.getScriptProperties();
const lastFetchedDateGmt = scriptProperties.getProperty(LAST_FETCHED_ORDER_DATE_KEY);
let isIncrementalSync = false;
let apiEndpoint = `/wp-json/wc/v3/orders?per_page=${ORDERS_PER_PAGE}&orderby=date&order=asc`; // Luôn lấy ASC để STT đúng

if (lastFetchedDateGmt && sheet.getLastRow() > 1) { // Nếu có ngày đã lưu và sheet có dữ liệu (hơn 1 dòng tiêu đề)
isIncrementalSync = true;
apiEndpoint += `&after=${lastFetchedDateGmt}`; // Chỉ lấy đơn hàng mới hơn ngày đã lưu
SpreadsheetApp.getActiveSpreadsheet().toast(‘Fetching new orders…’, ‘WooCommerce Sync’, -1);
Logger.log(`Incremental sync. Fetching orders after: ${lastFetchedDateGmt}`);
} else {
SpreadsheetApp.getActiveSpreadsheet().toast(‘Performing full sync of orders…’, ‘WooCommerce Sync’, -1);
Logger.log(‘Full sync. Fetching all orders (or sheet was empty/cleared).’);
if (sheet.getLastRow() > 0) { // Nếu là full sync mà sheet có dữ liệu thì xóa đi
sheet.clear();
}
}

try {
let allNewOrdersData = [];
let page = 1;
let moreOrdersExist = true;
let latestOrderDateInBatch = null;

while (moreOrdersExist) {
const currentApiEndpoint = `${apiEndpoint}&page=${page}`;
const response = callWooCommerceApi(currentApiEndpoint, ‘GET’);

if (response.getResponseCode() === 200) {
const orders = JSON.parse(response.getContentText());
if (orders.length > 0) {
allNewOrdersData.push(…orders);
// Tìm ngày tạo mới nhất trong batch này (quan trọng cho incremental sync)
orders.forEach(order => {
const orderDate = new Date(order.date_created_gmt + ‘Z’);
if (!latestOrderDateInBatch || orderDate > latestOrderDateInBatch) {
latestOrderDateInBatch = orderDate;
}
});
SpreadsheetApp.getActiveSpreadsheet().toast(`Fetched page ${page} (${orders.length} orders)… Total new: ${allNewOrdersData.length}`, ‘WooCommerce Sync’, 10);
page++;
if (orders.length < ORDERS_PER_PAGE) {
moreOrdersExist = false;
}
} else {
moreOrdersExist = false;
}
} else {
Logger.log(`Error fetching orders (Page ${page}): ${response.getResponseCode()} – ${response.getContentText()}`);
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);
SpreadsheetApp.getActiveSpreadsheet().toast(‘Error fetching orders.’, ‘WooCommerce Sync’);
return;
}
Utilities.sleep(500);
}

if (allNewOrdersData.length > 0) {
writeOrdersToSheet(allNewOrdersData, isIncrementalSync, sheet);
// Cập nhật ngày fetch cuối cùng, sử dụng ISO string để đảm bảo định dạng chuẩn và múi giờ UTC
// Thêm 1 giây để đảm bảo không lấy trùng đơn hàng ở lần fetch sau nếu có nhiều đơn trong cùng 1 giây.
if (latestOrderDateInBatch) {
const nextFetchDate = new Date(latestOrderDateInBatch.getTime() + 1000); // Thêm 1 giây
scriptProperties.setProperty(LAST_FETCHED_ORDER_DATE_KEY, nextFetchDate.toISOString().slice(0, -5)); // YYYY-MM-DDTHH:MM:SS (bỏ .SSSZ)
Logger.log(`Updated last fetched date to: ${scriptProperties.getProperty(LAST_FETCHED_ORDER_DATE_KEY)}`);
}
SpreadsheetApp.getActiveSpreadsheet().toast(`Successfully processed ${allNewOrdersData.length} orders!`, ‘WooCommerce Sync’, 10);
} else {
SpreadsheetApp.getActiveSpreadsheet().toast(isIncrementalSync ? ‘No new orders found.’ : ‘No orders found for full sync.’, ‘WooCommerce Sync’, 10);
}

} catch (e) {
Logger.log(`Error in syncOrders: ${e.toString()}\n${e.stack}`);
ui.alert(‘Script Error’, `An error occurred: ${e.message}. Check logs.`, ui.ButtonSet.OK);
SpreadsheetApp.getActiveSpreadsheet().toast(‘Script error.’, ‘WooCommerce Sync’);
}
}

// =================================================================================
// HÀM GỌI WOOCOMMERCE API (Không thay đổi)
// =================================================================================
function callWooCommerceApi(endpoint, method, payload = null) {
const url = WOO_SITE_URL + endpoint;
const options = {
method: method.toLowerCase(),
headers: {
‘Authorization’: ‘Basic ‘ + Utilities.base64Encode(WOO_CONSUMER_KEY + ‘:’ + WOO_CONSUMER_SECRET)
},
contentType: ‘application/json’,
muteHttpExceptions: true
};

if (payload && (method.toUpperCase() === ‘POST’ || method.toUpperCase() === ‘PUT’)) {
options.payload = JSON.stringify(payload);
}

Logger.log(`Calling API: ${method} ${url}`);
const response = UrlFetchApp.fetch(url, options);
Logger.log(`API Response Code: ${response.getResponseCode()}`);
return response;
}

// =================================================================================
// HÀM GHI DỮ LIỆU ĐƠN HÀNG LÊN SHEET (Thay đổi nhiều)
// =================================================================================
function writeOrdersToSheet(ordersData, isIncrementalSync, sheet) {
if (!sheet) {
Logger.log(`Sheet “${SHEET_NAME}” not found for writing.`);
SpreadsheetApp.getUi().alert(‘Error’, `Sheet “${SHEET_NAME}” not found. Cannot write data.`);
return;
}

// Định nghĩa các cột (KHÔNG bao gồm STT ở đây, STT sẽ được thêm tự động)
const columnMappings = {
‘Order ID’: order => order.id,
‘Order Number’: order => order.number,
‘Status’: order => order.status,
‘Date Created’: order => order.date_created_gmt ? new Date(order.date_created_gmt + ‘Z’).toLocaleString() : (order.date_created ? new Date(order.date_created + ‘Z’).toLocaleString() : ”),
‘Customer ID’: order => order.customer_id || ‘Guest’,
‘Billing Name’: order => `${order.billing.first_name || ”} ${order.billing.last_name || ”}`.trim(),
‘Billing Email’: order => order.billing.email,
‘Billing Phone’: order => order.billing.phone,
‘Shipping Name’: order => `${order.shipping.first_name || ”} ${order.shipping.last_name || ”}`.trim(),
‘Payment Method’: order => order.payment_method_title,
‘Total Amount’: order => parseFloat(order.total) || 0,
‘Currency’: order => order.currency,
‘Product Names’: order => order.line_items.map(item => `${item.name} (Qty: ${item.quantity})`).join(‘\n’),
‘Total Items’: order => order.line_items.reduce((sum, item) => sum + item.quantity, 0)
};
const dataHeaders = Object.keys(columnMappings); // Tiêu đề dữ liệu
const fullHeaders = [“STT”, …dataHeaders]; // Tiêu đề đầy đủ cho sheet (bao gồm STT)

const dataToWrite = [];
let startRow;
let currentStt = 0;

if (isIncrementalSync) {
const lastSheetRow = sheet.getLastRow();
if (lastSheetRow < 1) { // Sheet trống hoàn toàn, không thể là incremental
isIncrementalSync = false; // Chuyển sang full sync
Logger.log(“Sheet was empty, forcing full sync mode for writing.”);
} else {
const lastSttValue = sheet.getRange(lastSheetRow, 1).getValue(); // Giả sử STT ở cột 1
currentStt = (typeof lastSttValue === ‘number’ && lastSttValue > 0) ? lastSttValue : 0;
startRow = lastSheetRow + 1;
Logger.log(`Incremental write. Starting STT from ${currentStt + 1}. Start row: ${startRow}`);
}
}

if (!isIncrementalSync) { // Full sync hoặc sheet trống/mới
if (sheet.getLastRow() > 0) sheet.clearContents(); // Xóa nội dung cũ nếu có, không xóa sheet object
sheet.getRange(1, 1, 1, fullHeaders.length).setValues([fullHeaders]); // Ghi tiêu đề đầy đủ
sheet.getRange(“A1”).setFontWeight(“bold”); // In đậm STT
sheet.getRange(1, 1, 1, fullHeaders.length).setFontWeight(“bold”); // In đậm cả dòng tiêu đề
currentStt = 0;
startRow = 2; // Dữ liệu bắt đầu từ dòng 2
Logger.log(“Full write. Headers written. Starting STT from 1. Start row: 2”);
}

ordersData.forEach(order => {
currentStt++;
const rowDataValues = dataHeaders.map(header => {
try {
return columnMappings[header](order);
} catch (e) {
Logger.log(`Error processing field “${header}” for order ID ${order.id}: ${e.message}`);
return `Error processing field`;
}
});
dataToWrite.push([currentStt, …rowDataValues]); // Thêm STT vào đầu mỗi hàng dữ liệu
});

if (dataToWrite.length > 0) {
sheet.getRange(startRow, 1, dataToWrite.length, fullHeaders.length).setValues(dataToWrite);
// Áp dụng định dạng và tự động điều chỉnh độ rộng cột
try {
fullHeaders.forEach((header, index) => {
sheet.autoResizeColumn(index + 1);
});
const currencyColumnIndex = fullHeaders.indexOf(‘Total Amount’) + 1;
if (currencyColumnIndex > 0) {
sheet.getRange(startRow, currencyColumnIndex, dataToWrite.length, 1).setNumberFormat(‘#,##0.00’);
}
const dateColumnIndex = fullHeaders.indexOf(‘Date Created’) + 1;
if (dateColumnIndex > 0) {
sheet.getRange(startRow, dateColumnIndex, dataToWrite.length, 1).setNumberFormat(‘yyyy-mm-dd hh:mm:ss’);
}
} catch (e) {
Logger.log(“Error during formatting/resizing columns: ” + e);
}
} else {
Logger.log(“No new data rows to write.”);
}
}

// =================================================================================
// HÀM ĐỂ THIẾT LẬP TRIGGER TỰ ĐỘNG CẬP NHẬT
// =================================================================================
function triggerAutoSync() {
Logger.log(“Auto-sync trigger fired.”);
syncOrders();
}

function setupAutoUpdateTriggerDialog() {
const ui = SpreadsheetApp.getUi();
const result = ui.prompt(
‘Setup Auto Update’,
‘Enter update frequency in minutes (e.g., 15, 30, 60). Minimum 15 minutes recommended for frequent updates, or higher for less frequent.’,
ui.ButtonSet.OK_CANCEL);

if (result.getSelectedButton() == ui.Button.OK) {
const minutes = parseInt(result.getResponseText());
if (isNaN(minutes) || minutes < 1) {
ui.alert(‘Invalid Input’, ‘Please enter a valid number of minutes (e.g., 15 or more).’, ui.ButtonSet.OK);
return;
}

// Xóa các trigger cũ có tên hàm là ‘triggerAutoSync’ để tránh trùng lặp
const existingTriggers = ScriptApp.getProjectTriggers();
for (let i = 0; i < existingTriggers.length; i++) {
if (existingTriggers[i].getHandlerFunction() === ‘triggerAutoSync’) {
ScriptApp.deleteTrigger(existingTriggers[i]);
Logger.log(`Deleted existing trigger ID: ${existingTriggers[i].getUniqueId()}`);
}
}

// Tạo trigger mới
ScriptApp.newTrigger(‘triggerAutoSync’)
.timeBased()
.everyMinutes(minutes)
.create();
ui.alert(‘Trigger Created’, `Automatic order sync scheduled to run every ${minutes} minutes. You can manage triggers via “Edit > Current project’s triggers”.`, ui.ButtonSet.OK);
Logger.log(`New trigger created to run “triggerAutoSync” every ${minutes} minutes.`);
}
}

Ảnh:

Bước 3: Cách sử dụng Script

  1. Cấu hình Script:
    • Mở lại trình soạn thảo Apps Script (Extensions > Apps Script).
    • Ở đầu file mã, thay đổi các giá trị sau:
      • YOUR_WOOCOMMERCE_SITE_URL: Bằng URL website WordPress của bạn (ví dụ: https://shopcuaban.com). KHÔNG có dấu / ở cuối.
      • YOUR_CONSUMER_KEY: Bằng Consumer Key bạn đã tạo ở Bước 1.
      • YOUR_CONSUMER_SECRET: Bằng Consumer Secret bạn đã tạo ở Bước 1.
    • Bạn có thể thay đổi SHEET_NAME nếu muốn đặt tên sheet khác.
    • Bạn có thể điều chỉnh ORDERS_PER_PAGE (số lượng đơn hàng lấy mỗi lần gọi API, tối đa thường là 100, nhưng giá trị nhỏ hơn như 20-50 có thể ổn định hơn cho các server yếu).
  2. Lưu Script: Nhấn vào biểu tượng lưu (hình đĩa mềm).
  3. Chạy Lần đầu (Cấp quyền):
    • Quay lại Google Sheet của bạn.
    • Có thể bạn cần tải lại (refresh) trang Google Sheet để menu “WooCommerce Sync” xuất hiện.
    • Nhấp vào WooCommerce Sync > Fetch Orders.
    • Lần đầu tiên chạy, Google sẽ yêu cầu bạn cấp quyền cho script.
      • Nhấn “Continue”.
      • Chọn tài khoản Google của bạn.
      • Bạn có thể thấy cảnh báo “Google hasn’t verified this app”. Nhấn vào “Advanced” (Nâng cao) rồi chọn “Go to [Tên dự án của bạn] (unsafe)” (Đi tới [Tên dự án của bạn] (không an toàn)).
      • Xem lại các quyền và nhấn “Allow” (Cho phép). Script cần quyền để kết nối với dịch vụ bên ngoài (website của bạn) và chỉnh sửa Google Sheet.
  4. Lần Chạy Đầu Tiên (hoặc sau khi muốn reset):
  5. Cập nhật Đơn Hàng Mới (Thủ công):
    • Chọn WooCommerce Sync > Fetch/Update Orders. Script sẽ chỉ lấy các đơn hàng mới kể từ lần fetch thành công cuối cùng và nối vào cuối bảng với STT tiếp tục.

Bước 4. Thiết lập tự động cập nhật

  1. Chọn WooCommerce Sync > Setup Auto Update Trigger.
  2. Nhập số phút bạn muốn script tự động chạy (ví dụ: 15, 30, 60).
  3. Nhấn OK. Một trigger sẽ được tạo.
  4. Bạn có thể quản lý các trigger này bằng cách vào trình soạn thảo Apps Script, chọn biểu tượng đồng hồ (Triggers) ở thanh bên trái.

Đọc thêm đê:

Viết content một lần dùng nhiều lần hữu ích
Giải mã bí mật đằng sau content tỷ views Lê Tuấn Khang.
DISC của Đỗ Mạnh Hùng (Hùng SEO)
Những gì thực sự quan trọng khi làm SEO cho tìm kiếm 2023
Podcast mới nhất của Google năm 2023 (Search Off the Record)
Cách đánh lừa Google và đưa bảng kiến ​​thức lên kết quả tìm kiếm

Filed Under: Tôi học

About Đỗ Mạnh Hùng

Tôi quê tại Thanh Hà, Hải Dương (Quê hương gốc của Vải Thiều), đam mê về công nghệ và marketing online. Thích chia sẻ, tính hình hài hước. Nếu bạn có câu hỏi nào về SEO hãy kết bạn với tôi Facebook

Primary Sidebar

Kết bạn

  • Facebook
  • LinkedIn
  • Twitter
  • YouTube

Categories

  • Cách sống
  • Giới thiệu
  • Google Adsense
  • Marketing
  • Sức khỏe
  • Tin mới
  • Tôi học
  • Tôi làm
  • Yêu công nghệ

Footer

Về tôi

www.domanhhung.com Là website blog chia sẻ kiến thức của Đỗ Mạnh Hùng (Hùng SEO). Những kinh nghiệm trong quá trình làm việc, học tập, chiêm nghiệm về digital marketing, bán hàng, SEO, sách, IOT được tôi chia sẻ ở đây. Rất vui được làm quen với các độc giả.

Thông tin liên hệ:

  • Facebook: https://fb.com/domanhhung87
  • Linkedin: https://www.linkedin.com/in/domanhhung87/
  • Youtube: Youtube Hùng SEO
  • Địa chỉ: Hà Đông, Hà Nội

Thông tin khác

  • Quy định sử dụng
  • Chính sách bảo mật

Copyright © 2025 · Blog cá nhân của Đỗ Mạnh Hùng. Mọi chi tiết xin vui lòng liên hệ với tác giả Blog.