ROCKI

LEARN · ARTICLE

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

發布 2026-11-09更新 2026-11-09作者 Rock Lee
用試算表接 LINE 訂單:寫入、加總與兩個實測坑 封面示意圖

作者:Rock Lee 實測日期:2026-11-09(Asia/Taipei);試算表畫面依 2026-10-22 的實際資料重繪

為什麼寫這篇

小店用 LINE 接單很常見,麻煩的是事後對帳:誰點了什麼、今天賣了多少,常常要從聊天室一則一則抄。Google 試算表免費、店家自己就會看,Apps Script 又是試算表內建的程式環境,看起來把兩者接起來就好。

我們用一家虛構店家「小潮咖啡」做了一套示範:客人在 LINE 打「拿鐵 2 杯」,訂單自動寫進試算表,再回覆小計與今日累計。這篇整理 Apps Script 這一段怎麼寫、怎麼部署,以及實測時踩到的兩個坑。先講結論:Apps Script 很適合當「寫入試算表」的那一層,但不適合直接接 LINE 的 webhook,中間要隔一層能驗簽的小服務。

LINE 訊息經過驗簽小服務,再交給 Apps Script 寫進試算表(示意圖,AI 生成)
LINE 訊息經過驗簽小服務,再交給 Apps Script 寫進試算表(示意圖,AI 生成)

實測環境

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

步驟

1. 先把整條流程畫出來

整條流程:LINE 訊息 → 驗簽小服務 → Apps Script → 試算表 → 回覆(自製流程圖)
整條流程:LINE 訊息 → 驗簽小服務 → Apps Script → 試算表 → 回覆(自製流程圖)

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

2. 寫 doPost:驗密鑰、檢查欄位、加鎖再寫

Code.gs 的 doPost 主流程(密鑰已遮)
Code.gs 的 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
本機用假物件跑 Code.gs:7 個案例的回傳與試算表內容(2026-11-09 現場執行)
本機用假物件跑 Code.gs:7 個案例的回傳與試算表內容(2026-11-09 現場執行)

這樣改程式時可以先在本機驗邏輯,確定沒問題才貼進 Apps Script。

4. 部署成網路應用程式

  1. 打開訂單試算表 →「擴充功能」→「Apps Script」,把 Code.gs 整份貼上、儲存。
  2. 右上「部署」→「新增部署作業」→ 類型選「網路應用程式」。
  3. 執行身分選「我」,誰可以存取選「任何人」,按「部署」。
  4. 第一次會跳授權畫面,這是你自己的腳本,照畫面允許即可。
  5. 複製 https://script.google.com/macros/s/你的部署ID/exec 這個網址,填進驗簽小服務的設定。

之後改程式,請用「部署 → 管理部署作業 → 編輯 → 版本選『新版本』」。不要再按「新增部署作業」,那會產生一個新網址,前面的小服務就要跟著改。

5. 確認線上的 /exec 會回 302,呼叫端要會處理

今天對已部署的網址做唯讀請求:

對 /exec 送 GET:不跟隨轉址一律 302,跟隨後才是 200(2026-11-09 現場量測)
對 /exec 送 GET:不跟隨轉址一律 302,跟隨後才是 200(2026-11-09 現場量測)
  • 不跟隨轉址:3 次都是 302,0.75~1.04 秒
  • 跟隨轉址:3 次都是 200,0.86~1.27 秒,中間轉址 1 次
  • 用 curl -L 送 POST:跟到轉址後的網址還是用 POST,結果拿到 405 錯誤頁

正確做法是:POST 拿到 302 之後,改用 GET 去讀 Location 指向的網址,才會拿到 doPost 的 JSON 結果。

POST 拿到 302 後,改用 GET 讀結果,才拿到 JSON(2026-11-09 現場量測)
POST 拿到 302 後,改用 GET 讀結果,才拿到 JSON(2026-11-09 現場量測)

大部分 HTTP 套件(例如瀏覽器的 fetch)遇到 302 會自動改成 GET;自己寫 curl 或設定工具時要特別注意。

6. 看結果:原始資料和摘要分頁

訂單分頁:依 2026-10-22 試算表實際資料重繪,userId 已遮
訂單分頁:依 2026-10-22 試算表實際資料重繪,userId 已遮

原始分頁只負責記錄。店家想看的「今天賣多少」,另外開一個摘要分頁用公式算:

  • 今日營收:SUMIFS 篩今天的日期
  • 今日訂單數:依訂單編號去重
  • 熱銷品項、每小時訂單:QUERY 分組加總

公式裡一律排除 userId 以 TEST 開頭的自測列和 LIFF-preview 預覽列,不刪原始資料,只在統計時排除。

摘要分頁長相:依 2026-10-22 某個時點的實際訂單資料繪製,非 Google 試算表截圖
摘要分頁長相:依 2026-10-22 某個時點的實際訂單資料繪製,非 Google 試算表截圖

結果

  • Code.gs 的寫入、讀取、批次三種動作,在本機 7 個案例全部照預期。
  • 線上 /exec 的 GET 一來一回約 0.9~1.3 秒(只回固定 JSON、不碰試算表)。實際寫入要再加上開表、加鎖、寫一列的時間,這次沒有在線上量寫入延遲。
  • 小店用量下,配額不是問題。官方配額頁寫的是:對外連線(UrlFetch)一般帳號每天 20,000 次、Workspace 帳號 100,000 次;單次執行上限 6 分鐘;每位使用者同時執行 30 個。

踩坑紀錄

  1. Apps Script 拿不到 request header,驗不了 LINE 簽章。 LINE 會在 header 放 x-line-signature,要求你用 channel secret 驗證。但 doPost 的事件物件只有網址參數、路徑、內文這些欄位,沒有 header。

!官方文件列出的 doPost 事件物件欄位:沒有 request headers

  1. /exec 一律回 302,LINE 會把它記成錯誤。 LINE 的錯誤統計把非 2xx 的回應都算錯誤,並要求 2 秒內回應。所以我們不讓 LINE 直接打 Apps Script,而是讓前面的小服務驗簽、先回 200,再轉給 Apps Script 寫入。
  2. POST 跟隨轉址會變 405。 見步驟 5,要改用 GET 讀結果。
  3. 改程式後忘記發新版本。 只按儲存,線上網址跑的還是舊版。
  4. 測試資料混進營收。 自測時用 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 包住寫入;沒有就標紅。

驗收:每個案例「預期/實際」對照表,全部一致才算通過;不附任何密鑰字串。