LEARN · ARTICLE
用試算表接 LINE 訂單:寫入、加總與兩個實測坑

作者:Rock Lee 實測日期:2026-11-09(Asia/Taipei);試算表畫面依 2026-10-22 的實際資料重繪
為什麼寫這篇
小店用 LINE 接單很常見,麻煩的是事後對帳:誰點了什麼、今天賣了多少,常常要從聊天室一則一則抄。Google 試算表免費、店家自己就會看,Apps Script 又是試算表內建的程式環境,看起來把兩者接起來就好。
我們用一家虛構店家「小潮咖啡」做了一套示範:客人在 LINE 打「拿鐵 2 杯」,訂單自動寫進試算表,再回覆小計與今日累計。這篇整理 Apps Script 這一段怎麼寫、怎麼部署,以及實測時踩到的兩個坑。先講結論:Apps Script 很適合當「寫入試算表」的那一層,但不適合直接接 LINE 的 webhook,中間要隔一層能驗簽的小服務。

實測環境
| 項目 | 內容 |
|---|---|
| 示範店家 | 小潮咖啡(虛構),菜單 6 項:拿鐵 120、美式 90、卡布奇諾 130、抹茶拿鐵 140、肉桂捲 85、司康 75 |
| 寫入層 | Google Apps Script 網路應用程式(綁在訂單試算表上) |
| 驗簽與回覆 | 另一個小服務(我們用 Vercel 函式)驗 LINE 簽章、呼叫 Reply API |
| 本機測試 | Node.js 24,用假物件取代 Google 服務,直接跑同一份 Code.gs |
| 線上測試 | 對已部署的 /exec 網址做唯讀請求(不帶密鑰、不寫入) |
步驟
1. 先把整條流程畫出來

試算表第一列是欄位名稱:timestamp|userId|item|qty|price|subtotal。Apps Script 只負責一件事:收到一筆訂單,檢查後寫一列,回傳這筆小計和累計金額。驗 LINE 簽章、回覆客人,交給前面那層小服務。
2. 寫 doPost:驗密鑰、檢查欄位、加鎖再寫

重點有三個:
- 先比對密鑰。
/exec網址是公開的,任何人拿到都能送資料,所以請求內容要帶一個只有你的小服務知道的secret,對不上就回unauthorized。密鑰建議放在「專案設定 → 指令碼屬性」,不要寫死在程式裡。 - 檢查數量和價格。
qty必須大於 0、price不可為負,否則回bad input,不寫入。 - 寫入前加鎖。 用
LockService.getScriptLock().waitLock(10000),兩筆訂單同時進來時,後到的會等前一筆寫完(最多等 10 秒),避免算錯列數。
我們後來又加了兩個動作:action:'read' 只回傳指定客人的訂單(userId 必須是 U 加 32 位小寫十六進位,一次最多 200 筆);action:'batch' 把一張單的多個品項一次寫入,最多 30 列。加批次的原因很實際:原本一個品項呼叫一次、每次約 2 秒,一張 7 項的單要 15 秒才收到收據。
3. 在本機先跑一遍,不用碰真的試算表
Apps Script 只能在 Google 的編輯器裡跑,改一次就要部署一次,很慢。我們寫了一個小腳本,用假的 SpreadsheetApp、LockService 等物件把 Code.gs 包起來,在本機直接呼叫 doPost。今天重跑 7 個案例,全部照預期:
- 拿鐵 ×2 → 小計 240、累計 240;肉桂捲 ×3 → 小計 255、累計 495
- 密鑰錯 →
unauthorized;數量 0 →bad input - 批次三個品項 →
written: 3 - 讀取最近 2 筆 → 回 2 筆;userId 格式錯 →
bad userId

這樣改程式時可以先在本機驗邏輯,確定沒問題才貼進 Apps Script。
4. 部署成網路應用程式
- 打開訂單試算表 →「擴充功能」→「Apps Script」,把 Code.gs 整份貼上、儲存。
- 右上「部署」→「新增部署作業」→ 類型選「網路應用程式」。
- 執行身分選「我」,誰可以存取選「任何人」,按「部署」。
- 第一次會跳授權畫面,這是你自己的腳本,照畫面允許即可。
- 複製
https://script.google.com/macros/s/你的部署ID/exec這個網址,填進驗簽小服務的設定。
之後改程式,請用「部署 → 管理部署作業 → 編輯 → 版本選『新版本』」。不要再按「新增部署作業」,那會產生一個新網址,前面的小服務就要跟著改。
5. 確認線上的 /exec 會回 302,呼叫端要會處理
今天對已部署的網址做唯讀請求:

- 不跟隨轉址:3 次都是 302,0.75~1.04 秒
- 跟隨轉址:3 次都是 200,0.86~1.27 秒,中間轉址 1 次
- 用
curl -L送 POST:跟到轉址後的網址還是用 POST,結果拿到 405 錯誤頁
正確做法是:POST 拿到 302 之後,改用 GET 去讀 Location 指向的網址,才會拿到 doPost 的 JSON 結果。

大部分 HTTP 套件(例如瀏覽器的 fetch)遇到 302 會自動改成 GET;自己寫 curl 或設定工具時要特別注意。
6. 看結果:原始資料和摘要分頁

原始分頁只負責記錄。店家想看的「今天賣多少」,另外開一個摘要分頁用公式算:
- 今日營收:
SUMIFS篩今天的日期 - 今日訂單數:依訂單編號去重
- 熱銷品項、每小時訂單:
QUERY分組加總
公式裡一律排除 userId 以 TEST 開頭的自測列和 LIFF-preview 預覽列,不刪原始資料,只在統計時排除。

結果
- Code.gs 的寫入、讀取、批次三種動作,在本機 7 個案例全部照預期。
- 線上 /exec 的 GET 一來一回約 0.9~1.3 秒(只回固定 JSON、不碰試算表)。實際寫入要再加上開表、加鎖、寫一列的時間,這次沒有在線上量寫入延遲。
- 小店用量下,配額不是問題。官方配額頁寫的是:對外連線(UrlFetch)一般帳號每天 20,000 次、Workspace 帳號 100,000 次;單次執行上限 6 分鐘;每位使用者同時執行 30 個。
踩坑紀錄
- Apps Script 拿不到 request header,驗不了 LINE 簽章。 LINE 會在 header 放
x-line-signature,要求你用 channel secret 驗證。但 doPost 的事件物件只有網址參數、路徑、內文這些欄位,沒有 header。
!官方文件列出的 doPost 事件物件欄位:沒有 request headers
- /exec 一律回 302,LINE 會把它記成錯誤。 LINE 的錯誤統計把非 2xx 的回應都算錯誤,並要求 2 秒內回應。所以我們不讓 LINE 直接打 Apps Script,而是讓前面的小服務驗簽、先回 200,再轉給 Apps Script 寫入。
- POST 跟隨轉址會變 405。 見步驟 5,要改用 GET 讀結果。
- 改程式後忘記發新版本。 只按儲存,線上網址跑的還是舊版。
- 測試資料混進營收。 自測時用
TEST開頭的 userId,統計公式統一排除,比事後刪列安全。
常見問題
Q1:可以完全不用中間那層小服務嗎? 技術上能讓 LINE 直接打 /exec,但驗不了簽章、又會收到 302,等於靠官方沒寫明的行為運作。我們不建議。另外要注意,我們示範用的 Vercel 免費方案(Hobby)官方寫明只限非商業的個人使用,真的給店家用要換方案或換主機,並自己確認條款。
Q2:Apps Script 要錢嗎? 一般 Google 帳號就能用,配額見上面「結果」。超過配額才需要考慮 Workspace,這篇沒有查 Workspace 價格。
Q3:密鑰放哪裡比較安全? 放「指令碼屬性」,程式用 PropertiesService 讀。不要貼進聊天室、不要放進公開的程式碼庫。
Q4:客人同時下單會不會寫亂? 有加 LockService 鎖,後到的會排隊。我們只在本機驗過邏輯,沒有在線上做同時大量寫入的壓力測試。
看實作成果
對應示範:小潮咖啡 LINE 點餐(虛構或自製,非客戶案件)。
複製這段給你的 AI agent
目標:在本機替一份 Google Apps Script(Code.gs,訂單寫入試算表用)做邏輯測試,只讀與本機執行,不部署、不改線上試算表。 前提與禁止事項: - 不要讀取、印出或寫入任何密鑰、token、試算表 ID;若 Code.gs 內有寫死的密鑰,測試時在記憶體裡替換成假值,不要改原檔。 - 不要開啟 Apps Script 編輯器、不要按部署、不要對線上 /exec 送 POST。 - 不要付款、不要寄信或發訊息。 步驟: 1. 讀 Code.gs,列出 doPost 支援的 action 與每種錯誤回傳(例如 unauthorized、bad input)。 2. 用 Node.js vm 模組載入 Code.gs,以假物件取代 SpreadsheetApp、ContentService、LockService、PropertiesService。 3. 跑至少 6 個案例:正常下單 2 筆、密鑰錯、數量 0、批次多品項、讀取指定 userId、userId 格式錯。 4. 回報每個案例的回傳 JSON,以及假試算表最後的列數與內容(userId 請遮成 U••••)。 5. 檢查程式有沒有用 LockService 包住寫入;沒有就標紅。 驗收:每個案例「預期/實際」對照表,全部一致才算通過;不附任何密鑰字串。