LINE 預約同步 Google Calendar

把每一筆線上預約,自動寫進店家的私人 Google 行事曆。

適合單店、個人工作室、美業、教練、顧問與需要手機管理行程的服務型生意。完成後,新增預約會建立 Google 行事曆事件,取消預約也能同步刪除。

Reservation Sync
LINE 預約 客人送出預約
Webhook 送到 Apps Script
Calendar 自動建立事件
今日預約 已同步
11:00美甲保養 - 林小姐
14:30顧問諮詢 - 王先生
18:00私人課程 - 陳小姐
使用店家 Google 帳號 事件會寫入店家自己的主要行事曆。
用 token 保護 Webhook 預約系統送資料時會一起驗證密鑰。
支援取消同步 保留 event_id 後,可同步刪除事件。

Setup

6 個步驟完成 Google 行事曆串接

這份設定適合「一個店家帳號管理自己的預約」。如果未來要多店、多員工或多行事曆,可以再擴充為店家獨立 Calendar ID。

1

確認店家 Google 帳號

請店家先登入平常查看行程的 Google 帳號,這個帳號會成為 Apps Script 的執行者。

開啟 Google 行事曆
2

建立 Apps Script 專案

到 Apps Script 建立新專案,貼上本頁下方程式碼。建議專案名稱包含店名,之後比較好維護。

開啟 Apps Script
3

設定 SECRET_TOKEN

SECRET_TOKEN 改成網站端與 Apps Script 都知道的密鑰。密鑰不要公開放在頁面上。

建議使用 24 字元以上,混合英文與數字,並為每一個客戶獨立設定。
4

部署成 Web App

按「部署」新增部署作業,類型選「網頁應用程式」。執行身分選自己,存取權限依專案需求開放給可呼叫的對象。

部署後請複製結尾為 /exec 的 Web App URL,測試時不要拿到 /dev 網址。
5

網站端儲存 Web App URL

把 Web App URL 與 SECRET_TOKEN 填到 LINE 預約系統的設定檔。預約成功時,系統會把日期、時間、姓名、電話與服務名稱送過去。

6

新增與取消各測一次

先送出一筆測試預約,確認 Google 行事曆出現事件;再取消同一筆預約,確認事件會被刪除。

Data

預約系統需要送出的資料

新增與取消都走同一支 Webhook。新增預約會回傳 event_id,請務必存回資料庫,取消同步才找得到 Google 事件。

新增預約 action create

需要 date、time、service_name、name、phone、booking_id。

取消預約 action cancel

需要 event_id,系統會刪除對應的 Google 行事曆事件。

時區 Asia/Taipei

範例程式用 +08:00 建立時間,適合台灣店家。

Apps Script

可直接貼上的 Webhook 程式碼

這份版本預設寫入登入帳號的主要 Google 行事曆。若要指定某個行事曆,將 USE_DEFAULT_CALENDAR 改為 false,再填入 CALENDAR_ID

記得更換 SECRET_TOKEN
const CONFIG = {
  SECRET_TOKEN: 'replace-with-your-secret-token',
  DEFAULT_DURATION_MINUTES: 60,

  // true:寫入登入帳號的主要行事曆
  USE_DEFAULT_CALENDAR: true,

  // 若 USE_DEFAULT_CALENDAR 改為 false,請填入指定行事曆 ID
  CALENDAR_ID: ''
};

function doGet() {
  return jsonOutput({
    status: 'ok',
    message: 'Google Calendar webhook is ready.'
  });
}

function doPost(e) {
  try {
    const body = JSON.parse((e.postData && e.postData.contents) || '{}');

    if (body.token !== CONFIG.SECRET_TOKEN) {
      return jsonOutput({
        status: 'error',
        message: 'Token 驗證失敗'
      });
    }

    const action = body.action || 'create';
    const calendar = getTargetCalendar();

    if (action === 'cancel') {
      return cancelBookingEvent(calendar, body);
    }

    return createBookingEvent(calendar, body);
  } catch (error) {
    return jsonOutput({
      status: 'error',
      message: error.message
    });
  }
}

function getTargetCalendar() {
  if (CONFIG.USE_DEFAULT_CALENDAR) {
    return CalendarApp.getDefaultCalendar();
  }

  const calendar = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);
  if (!calendar) {
    throw new Error('找不到 Google 行事曆,請確認 CALENDAR_ID');
  }

  return calendar;
}

function createBookingEvent(calendar, body) {
  const date = body.date || '';
  const time = body.time || '';
  const serviceName = body.service_name || '預約服務';
  const customerName = body.name || '未填姓名';
  const phone = body.phone || '未填電話';
  const duration = Number(body.duration_minutes || CONFIG.DEFAULT_DURATION_MINUTES);

  const start = new Date(`${date}T${time}:00+08:00`);
  const end = new Date(start.getTime() + duration * 60 * 1000);
  const title = `[LINE預約] ${serviceName} - ${customerName}`;
  const description = [
    `服務項目:${serviceName}`,
    `客戶姓名:${customerName}`,
    `聯絡電話:${phone}`,
    `預約編號:${body.booking_id || ''}`
  ].join('\n');

  const event = calendar.createEvent(title, start, end, {
    description: description
  });

  return jsonOutput({
    status: 'success',
    action: 'create',
    event_id: event.getId()
  });
}

function cancelBookingEvent(calendar, body) {
  if (!body.event_id) {
    return jsonOutput({
      status: 'error',
      message: '缺少 event_id,無法取消 Google 行事曆事件'
    });
  }

  const event = calendar.getEventById(body.event_id);
  if (!event) {
    return jsonOutput({
      status: 'not_found',
      action: 'cancel',
      message: 'Google 行事曆事件不存在,可能已經刪除'
    });
  }

  event.deleteEvent();

  return jsonOutput({
    status: 'success',
    action: 'cancel',
    event_id: body.event_id
  });
}

function jsonOutput(payload) {
  return ContentService
    .createTextOutput(JSON.stringify(payload))
    .setMimeType(ContentService.MimeType.JSON);
}

Handoff

交付給預約系統的三項資料

Web App URL

部署完成後複製 /exec 網址,填回 LINE 預約系統設定。

SECRET_TOKEN

網站端送出資料時必須帶同一組 token,避免外部亂打 Webhook。

event_id 欄位

新增預約後要把回傳的 event_id 存起來,取消時才可同步刪除。

上線前檢查

請至少測試「新增預約」、「取消預約」、「token 錯誤」、「時間格式錯誤」四種情境。確認 Google 行事曆內容正確後,再接正式預約資料。

Reference

官方文件

若需要調整進階功能,例如邀請通知、提醒時間、指定行事曆或多員工排班,可從 Google 官方文件延伸。