決定要寫入哪一個 Google 帳號
請用店家真正要查看行程的 Google 帳號。因為這版會寫入該帳號的主要私人行事曆,所以登入哪個帳號,預約就會出現在哪個帳號的主要日曆。
打開 Google 行事曆確認帳號單一店家 / 私人主要 Google 行事曆
這份教學先教店家把預約寫進自己正在用的主要 Google 行事曆。完成後,網站預約成功會自動出現在私人行事曆裡,取消預約也可以同步刪除。
Apps Script 會用店家登入的 Google 帳號執行,所以可以直接寫入那個帳號的主要行事曆。預約系統只需要把預約資料送到 Web App 網址。
寫入店家平常看的私人主要行事曆。
接收新增與取消預約的資料。
取得給網站後端呼叫的 Webhook URL。
Store Setup
請用店家真正要查看行程的 Google 帳號。因為這版會寫入該帳號的主要私人行事曆,所以登入哪個帳號,預約就會出現在哪個帳號的主要日曆。
打開 Google 行事曆確認帳號進入 Google 行事曆,手動新增一筆測試行程。如果可以新增、可以看到,代表 Apps Script 之後也能用這個帳號把預約寫進來。
把下方 Apps Script 範例貼到編輯器,只要先替換 SECRET_TOKEN。這組 token 是網站後端呼叫時用的密碼,不要公開。
右上角按「部署」→「新增部署作業」→ 類型選「網頁應用程式」。執行身分選「我」,存取權選「任何人」。授權完成後,複製結尾是 /exec 的 Web App URL。
工程端用 Web App URL 送一筆測試資料,如果 Google 行事曆出現一筆標題為「[預約]」開頭的行程,就代表新增同步成功。
Webhook Code
這段預設寫入私人主要行事曆,並同時支援新增預約與取消預約。取消時需要網站後端傳回當初建立事件得到的 event_id。
const CONFIG = {
SECRET_TOKEN: '請換成一組只有你知道的密碼',
DEFAULT_DURATION_MINUTES: 60,
// 預設寫入這個 Google 帳號的主要私人行事曆。
USE_DEFAULT_CALENDAR: true,
// 如果之後想改成專用日曆,才需要填 CALENDAR_ID 並把 USE_DEFAULT_CALENDAR 改成 false。
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 = `[預約] ${serviceName} - ${customerName}`;
const description = [
`服務項目:${serviceName}`,
`預約姓名:${customerName}`,
`手機號碼:${phone}`,
`網站預約 ID:${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);
}
Cancel Sync
有,Apps Script 這份範例已經包含取消事件。但網站預約系統要多做兩個動作,取消才會真的同步到 Google 行事曆。
Apps Script 新增成功會回傳 event_id,預約系統必須把它存到預約資料表。
使用者取消預約時,網站後端要送 action: "cancel" 和原本的 event_id。
Apps Script 找到事件後會執行 deleteEvent(),私人行事曆上的該筆預約就會消失。
Handoff
部署後產生的網址,通常以 /exec 結尾。
Apps Script 裡設定的密碼,用來避免陌生請求亂寫私人行事曆。
確認 Web App 是用店家要查看行程的 Google 帳號部署。
新增預約後,私人 Google 行事曆上應該出現一筆事件;取消同一筆預約後,該事件應該從 Google 行事曆消失。
Optional
如果你的需求不是整合私人主要行事曆,而是要把預約和私人行程分開,可以另外建立一個「預約專用」日曆。建立後到該日曆的「設定與共用」→「整合日曆」複製日曆 ID,然後把 Apps Script 的 USE_DEFAULT_CALENDAR 改成 false,並填入 CALENDAR_ID。