本篇要解決的問題
最近裝的一個 App,因為變成必須要付費訂閱,才能有自動繳停車費的功能。為了避免停車費過期而多收錢,所以讓 ChatGPT 寫一個簡單的 n8n Workflow,匯入後修改一下設定資料,就可以每天自動通知還有沒有未繳的停車費。
August 希望做到以下幾件事:
- 不用每天自己打開停車費網站查詢。
- 可以同時查詢汽車、機車。
- 可以設定多台車。
- 一次查詢多個縣市。
- 每天固定時間自動執行。
- 有未繳停車費時,主動透過 Telegram 通知。
- 某個縣市 API 發生錯誤時,也要告訴我,不能直接把它當成「沒有欠費」。
而且,以上全部的需求,都可以放在 同一個 n8n Workflow 裡,不需要汽車、機車各做一份。
Workflow JSON 下載、匯入
直接複製以下 n8n Workflow,接著進入 n8n,直接貼上:
{
"name": "全台路邊停車費自動查詢|多車輛+21縣市+Telegram v4",
"nodes": [
{
"parameters": {},
"id": "9d4d1385-782c-4c2d-9c50-5b1a80cf6d83",
"name": "手動測試",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-1160, 160]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * *"
}
]
}
},
"id": "ec576177-01d7-468f-8777-196e1656a71d",
"name": "每天 09:00",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [-1160, -40]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "4728964e-002f-4430-838c-6e44d2deda12",
"name": "telegramChatId",
"value": "請改成你的 Telegram Chat ID",
"type": "string"
},
{
"id": "990ecf7e-feaa-4a91-9118-f378adb5d4d9",
"name": "notifyWhenClear",
"value": false,
"type": "boolean"
},
{
"id": "c69ea629-43b8-466a-bbb0-a5610d2d83e7",
"name": "vehiclesJson",
"value": "[\n {\n \"label\": \"我的汽車\",\n \"carId\": \"ABC-1234\",\n \"carType\": \"C\"\n },\n {\n \"label\": \"我的機車\",\n \"carId\": \"XYZ-5678\",\n \"carType\": \"M\"\n }\n]",
"type": "string"
},
{
"id": "fc5463ba-96f0-49a6-a280-3863bdd345bc",
"name": "citiesJson",
"value": "[\n {\n \"name\": \"臺北市\",\n \"urlTemplate\": \"https://trafficapi.pma.gov.taipei/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"新北市\",\n \"urlTemplate\": \"https://trafficapi.traffic.ntpc.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"桃園市\",\n \"urlTemplate\": \"https://bill-epark.tycg.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"臺中市\",\n \"urlTemplate\": \"http://tcparkingapi.taichung.gov.tw:8081/NationalParkingPayBillInquiry.Api/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true,\n \"note\": \"來源未涵蓋:尚未確認此 :8081 API 是否支援 HTTPS,因此暫保留原 HTTP endpoint。\"\n },\n {\n \"name\": \"臺南市\",\n \"urlTemplate\": \"https://parkingbill.tainan.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"高雄市\",\n \"urlTemplate\": \"https://kpp.tbkc.gov.tw/parking/V1/parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"基隆市\",\n \"urlTemplate\": \"https://park.klcg.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"新竹市\",\n \"urlTemplate\": \"https://his.futek.com.tw:5443/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"新竹縣\",\n \"urlTemplate\": \"https://hcpark.hchg.gov.tw/NationalParkingPayBillInquiry/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"苗栗縣\",\n \"urlTemplate\": \"https://miaoliparking.jotangi.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"彰化縣\",\n \"urlTemplate\": \"https://chpark.chcg.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"南投縣\",\n \"urlTemplate\": \"https://parking.nantou.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"雲林縣\",\n \"urlTemplate\": \"https://parking.yunlin.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"嘉義縣\",\n \"urlTemplate\": \"https://8voc0wuf1g.execute-api.ap-southeast-1.amazonaws.com/default/chiayi/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"嘉義市\",\n \"urlTemplate\": \"https://parking.chiayi.gov.tw/cypark/api/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"屏東縣\",\n \"urlTemplate\": \"https://8voc0wuf1g.execute-api.ap-southeast-1.amazonaws.com/default/pingtung/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"宜蘭縣\",\n \"urlTemplate\": \"https://billparking.e-land.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"花蓮縣\",\n \"urlTemplate\": \"https://hl.parchere.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"臺東縣\",\n \"urlTemplate\": \"https://tt.guoyun.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"澎湖縣\",\n \"urlTemplate\": \"https://parking.penghu.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n },\n {\n \"name\": \"金門縣\",\n \"urlTemplate\": \"https://km.guoyun.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}\",\n \"enabled\": true\n }\n]",
"type": "string"
}
]
},
"options": {}
},
"id": "cfc69392-3221-4479-b078-437991a0b749",
"name": "設定(只改這裡)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-920, 60]
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const cfg = $input.first().json;\n\nfunction parseJsonField(value, fieldName) {\n if (Array.isArray(value)) return value;\n try {\n return JSON.parse(value);\n } catch (e) {\n throw new Error(`${fieldName} 不是合法 JSON:${e.message}`);\n }\n}\n\nconst vehicles = parseJsonField(cfg.vehiclesJson, 'vehiclesJson');\nconst cities = parseJsonField(cfg.citiesJson, 'citiesJson');\n\nif (!Array.isArray(vehicles) || vehicles.length === 0) {\n throw new Error('vehiclesJson 至少要有一台車。');\n}\n\nconst enabledCities = Array.isArray(cities)\n ? cities.filter(c => c?.enabled !== false && c?.name && c?.urlTemplate)\n : [];\n\nif (enabledCities.length === 0) {\n throw new Error('citiesJson 至少要啟用一個縣市。');\n}\n\nconst allowedTypes = new Set(['C', 'M', 'O']);\nconst output = [];\n\nfor (const vehicle of vehicles) {\n const carId = String(vehicle?.carId ?? '').trim();\n const carType = String(vehicle?.carType ?? '').trim().toUpperCase();\n const label = String(vehicle?.label ?? carId).trim();\n\n if (!carId) throw new Error(`車輛缺少 carId:${JSON.stringify(vehicle)}`);\n if (!allowedTypes.has(carType)) {\n throw new Error(`${carId} 的 carType 必須是 C、M 或 O,目前為:${carType}`);\n }\n\n for (const city of enabledCities) {\n const queryUrl = String(city.urlTemplate)\n .replace('{CarID}', encodeURIComponent(carId))\n .replace('{CarType}', encodeURIComponent(carType));\n\n output.push({\n json: {\n label,\n carId,\n carType,\n cityName: city.name,\n queryUrl,\n telegramChatId: cfg.telegramChatId ?? '',\n notifyWhenClear: Boolean(cfg.notifyWhenClear)\n }\n });\n }\n}\n\nreturn output;"
},
"id": "fe4cf409-253c-453c-bb87-34849dfcc386",
"name": "建立 車輛 × 縣市 查詢清單",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [-680, 60]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "da0e1f61-1d54-4afb-82af-752252f5c50f",
"name": "Loop Over Items(每次 1 筆)",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [-420, 60]
},
{
"parameters": {
"method": "GET",
"url": "={{ $json.queryUrl }}",
"options": {
"response": {
"response": {
"fullResponse": true,
"neverError": true,
"responseFormat": "json"
}
},
"timeout": 10000
}
},
"id": "0707ce96-44ea-4630-a5fd-184d25e128bc",
"name": "查詢停車費 API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [-160, 200],
"onError": "continueRegularOutput"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const row = $json ?? {};\n\nconst base = {\n cityName: row.cityName ?? '',\n label: row.label ?? row.carId ?? '',\n carId: row.carId ?? '',\n carType: row.carType ?? '',\n queryUrl: row.queryUrl ?? '',\n telegramChatId: row.telegramChatId ?? '',\n notifyWhenClear: Boolean(row.notifyWhenClear)\n};\n\n// Network/TLS/DNS errors continued by the HTTP Request node arrive as `error`.\nif (row.error) {\n return {\n json: {\n ...base,\n status: 'ERROR',\n apiMessage: row.error.message || String(row.error),\n httpStatus: null,\n hasUnpaid: false,\n totalCount: 0,\n totalAmount: 0,\n details: [],\n isQueryFailed: true\n }\n };\n}\n\nconst httpStatus = Number(row.statusCode ?? 0);\nlet res = row.body ?? {};\n\nif (typeof res === 'string') {\n try {\n res = JSON.parse(res);\n } catch {\n return {\n json: {\n ...base,\n status: 'ERROR',\n apiMessage: `API 回傳非 JSON 內容${httpStatus ? `(HTTP ${httpStatus})` : ''}`,\n httpStatus: httpStatus || null,\n hasUnpaid: false,\n totalCount: 0,\n totalAmount: 0,\n details: [],\n isQueryFailed: true\n }\n };\n }\n}\n\nif (httpStatus && (httpStatus < 200 || httpStatus >= 300)) {\n return {\n json: {\n ...base,\n status: 'ERROR',\n apiMessage:\n res?.Message ||\n res?.message ||\n `HTTP ${httpStatus}`,\n httpStatus,\n hasUnpaid: false,\n totalCount: 0,\n totalAmount: 0,\n details: [],\n isQueryFailed: true\n }\n };\n}\n\nconst status = String(res?.Status ?? '').toUpperCase();\nconst apiMessage = String(res?.Message ?? '');\n\nlet result = res?.Result ?? null;\nif (typeof result === 'string' && result.trim()) {\n try {\n result = JSON.parse(result);\n } catch {\n return {\n json: {\n ...base,\n status: 'ERROR',\n apiMessage: 'Result 欄位不是合法 JSON',\n httpStatus: httpStatus || null,\n hasUnpaid: false,\n totalCount: 0,\n totalAmount: 0,\n details: [],\n isQueryFailed: true\n }\n };\n }\n}\n\nconst bills = Array.isArray(result?.Bills) ? result.Bills : [];\nconst reminders = Array.isArray(result?.Reminders) ? result.Reminders : [];\nconst details = [];\n\nfor (const bill of bills) {\n details.push({\n kind: '停車單',\n amount: Number(bill?.PayAmount ?? bill?.Amount ?? 0),\n parkingDate: bill?.ParkingDate ?? '',\n dueDate: bill?.PayLimitDate ?? '',\n billNo: bill?.BillNo ?? ''\n });\n}\n\nfor (const reminder of reminders) {\n details.push({\n kind: '催繳單',\n amount: Number(\n reminder?.PayAmount ??\n (Number(reminder?.Amount ?? 0) + Number(reminder?.ExtraCharge ?? 0))\n ),\n parkingDate: reminder?.ParkingDate ?? '',\n dueDate: reminder?.ReminderLimitDate ?? reminder?.PayLimitDate ?? '',\n billNo: reminder?.ReminderNo ?? reminder?.BillNo ?? ''\n });\n}\n\nconst totalCount = Number(result?.TotalCount ?? details.length ?? 0);\nconst computedAmount = details.reduce((sum, d) => sum + Number(d.amount || 0), 0);\nconst totalAmount = Number(result?.TotalAmount ?? computedAmount);\n\nconst success = status === 'SUCCESS';\n\nreturn {\n json: {\n ...base,\n status,\n apiMessage,\n httpStatus: httpStatus || null,\n hasUnpaid: success && !!result && (totalCount > 0 || details.length > 0),\n totalCount,\n totalAmount,\n details,\n isQueryFailed: !success\n }\n};"
},
"id": "0a7b65f9-6d37-49d4-9fd4-f4f749f73c46",
"name": "整理單筆結果",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [320, 120]
},
{
"parameters": {
"amount": 1,
"unit": "seconds"
},
"id": "aa5bc5ca-c35c-4f8a-97b4-1b29fef023a8",
"name": "等待 1 秒(API 節流)",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [320, 200],
"webhookId": "61c9dc0a-3b6d-49d5-b022-e6a9054e3dd3"
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const rows = $input.all().map(i => i.json);\nconst first = rows[0] ?? {};\n\nconst unpaidRows = rows.filter(r => r.hasUnpaid);\nconst failedRows = rows.filter(r => r.isQueryFailed);\n\nconst totalCount = unpaidRows.reduce((s, r) => s + Number(r.totalCount || 0), 0);\nconst totalAmount = unpaidRows.reduce((s, r) => s + Number(r.totalAmount || 0), 0);\n\nconst lines = [];\n\nif (unpaidRows.length) {\n lines.push('🚗 路邊停車費提醒', '');\n\n for (const row of unpaidRows) {\n const vehicleIcon = row.carType === 'M' ? '🛵' : '🚙';\n lines.push(`${vehicleIcon}【${row.label}|${row.carId}】`);\n lines.push(`${row.cityName}:${row.totalCount} 筆,合計 $${row.totalAmount}`);\n\n for (const d of row.details.slice(0, 10)) {\n const parts = [\n d.kind,\n `$${d.amount}`,\n d.parkingDate ? `停車日 ${d.parkingDate}` : null,\n d.dueDate ? `期限 ${d.dueDate}` : null\n ].filter(Boolean);\n lines.push(`- ${parts.join('|')}`);\n }\n\n if (row.details.length > 10) {\n lines.push(`- 另有 ${row.details.length - 10} 筆未列出`);\n }\n lines.push('');\n }\n\n lines.push(`總計:${totalCount} 筆,$${totalAmount}`);\n} else {\n lines.push('✅ 目前沒有查到待繳路邊停車費');\n}\n\nif (failedRows.length) {\n lines.push('', `⚠️ 有 ${failedRows.length} 個查詢失敗,這些縣市「不能視為沒有欠費」:`);\n for (const row of failedRows.slice(0, 20)) {\n lines.push(`- ${row.label}|${row.cityName}|${row.apiMessage || row.status || '未知錯誤'}`);\n }\n if (failedRows.length > 20) {\n lines.push(`- 另有 ${failedRows.length - 20} 個失敗未列出`);\n }\n}\n\nconst shouldNotify =\n unpaidRows.length > 0 ||\n failedRows.length > 0 ||\n Boolean(first.notifyWhenClear);\n\nreturn [{\n json: {\n telegramChatId: first.telegramChatId ?? '',\n shouldNotify,\n hasUnpaid: unpaidRows.length > 0,\n totalCount,\n totalAmount,\n failedQueries: failedRows.length,\n message: lines.join('\\n')\n }\n}];"
},
"id": "4b6dabed-b0b9-4e24-bd69-249d9cac4bbf",
"name": "彙總全部結果",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [-160, -120]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "60315e8a-26c6-47fe-b311-0864f215d707",
"leftValue": "={{ $json.shouldNotify }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "c44271f5-b4ec-4a02-bd5a-37e9b53cc1ab",
"name": "需要通知?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [80, -120]
},
{
"parameters": {
"chatId": "={{ $json.telegramChatId }}",
"text": "={{ $json.message }}",
"additionalFields": {}
},
"id": "427558ca-fc5c-4d35-aa31-3e1d2286e556",
"name": "Telegram 通知",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [320, -160]
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"id": "e11fc337-e4bd-406f-9f9b-9cebc88e6e96",
"name": "合併查詢參數與 HTTP 回應",
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [80, 120]
}
],
"connections": {
"手動測試": {
"main": [
[
{
"node": "設定(只改這裡)",
"type": "main",
"index": 0
}
]
]
},
"每天 09:00": {
"main": [
[
{
"node": "設定(只改這裡)",
"type": "main",
"index": 0
}
]
]
},
"設定(只改這裡)": {
"main": [
[
{
"node": "建立 車輛 × 縣市 查詢清單",
"type": "main",
"index": 0
}
]
]
},
"建立 車輛 × 縣市 查詢清單": {
"main": [
[
{
"node": "Loop Over Items(每次 1 筆)",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items(每次 1 筆)": {
"main": [
[
{
"node": "彙總全部結果",
"type": "main",
"index": 0
}
],
[
{
"node": "查詢停車費 API",
"type": "main",
"index": 0
},
{
"node": "合併查詢參數與 HTTP 回應",
"type": "main",
"index": 0
}
]
]
},
"查詢停車費 API": {
"main": [
[
{
"node": "合併查詢參數與 HTTP 回應",
"type": "main",
"index": 1
}
]
]
},
"整理單筆結果": {
"main": [
[
{
"node": "等待 1 秒(API 節流)",
"type": "main",
"index": 0
}
]
]
},
"等待 1 秒(API 節流)": {
"main": [
[
{
"node": "Loop Over Items(每次 1 筆)",
"type": "main",
"index": 0
}
]
]
},
"彙總全部結果": {
"main": [
[
{
"node": "需要通知?",
"type": "main",
"index": 0
}
]
]
},
"需要通知?": {
"main": [
[
{
"node": "Telegram 通知",
"type": "main",
"index": 0
}
],
[]
]
},
"合併查詢參數與 HTTP 回應": {
"main": [
[
{
"node": "整理單筆結果",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"active": false,
"settings": {
"executionOrder": "v1",
"timezone": "Asia/Taipei"
},
"versionId": "1aa3b0b1-0acf-40f7-b71c-03a93d7beb1a",
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}
如果是想用匯入的方式,就把上面的程式碼複製後存成 JSON,進入 n8n:
Workflows
→ Import from File
→ 選擇剛才下載的 JSON
不管是用複製貼上的,還是匯入,之後只需要修改 「設定(只改這裡)」 這個 Node 就可以,說明如下。
車輛設定
找到:
vehiclesJson
例如:
[
{
"label": "我的汽車",
"carId": "ABC-1234",
"carType": "C"
},
{
"label": "我的機車",
"carId": "XYZ-5678",
"carType": "M"
}
]
carType 的設定:
| 值 | 車種 |
|---|---|
C |
汽車 |
M |
機車 |
O |
其他 |
如果有多台車,繼續往下增加即可。
例如:
[
{
"label": "汽車",
"carId": "ABC-1234",
"carType": "C"
},
{
"label": "我的機車",
"carId": "XYZ-5678",
"carType": "M"
},
{
"label": "家人的機車",
"carId": "AAA-8888",
"carType": "M"
}
]
Telegram 設定
接著修改:
telegramChatId
填入自己的 Telegram Chat ID。
如果不知道自己的 Telegram Chat ID 是多少,只需要加入這個帳號,進行對話後就會看得到了:User Info。
最後進入:
Telegram 通知
設定自己的 Telegram Bot Credential。
如果不希望「沒有欠費」時也收到通知,可以保持:
notifyWhenClear = false
這樣平常沒有欠費就完全不打擾。
如果沒有用 Telegram,或是想接收別的通知,那只需要把上面 Workflow 的 JSON,貼給 ChatGPT,請它改成你習慣的通知方式即可。
n8n Workflow 說明
這段是詳細說明上面那段 JSON 的各個部份,是在做什麼的。
整個 Workflow 最主要可以分成幾個部分。
每天自動執行
Workflow 預設:
每天 09:00
自動執行一次。
另外也保留:
手動測試
剛設定完成時,可以先按手動執行確認是否正常。
設定車輛與縣市
「設定(只改這裡)」Node 集中放:
telegramChatId
notifyWhenClear
vehiclesJson
citiesJson
所以日後新增汽車、機車,基本上只需要改這一個地方。
citiesJson 則放各縣市停車費查詢端點。
目前這份 Workflow 已經預先放入 21 個縣市的設定;如果某個 API 日後有更換網址,也只需要修改這裡,不必重新改整個流程。
建立查詢清單
接下來的 Code Node 會把:
車輛
×
縣市
自動組合成每一筆查詢。
例如:
我的汽車 → 臺北市
我的汽車 → 新北市
我的汽車 → 桃園市
我的機車 → 臺北市
我的機車 → 新北市
我的機車 → 桃園市
...
所以新增車輛時,不需要增加新的 HTTP Request Node。
逐筆查詢停車費
接著使用:
Loop Over Items
一次只處理一筆查詢。
流程是:
查詢 API
↓
整理結果
↓
等待 1 秒
↓
下一筆
August 刻意加上 等待 1 秒,避免短時間內大量呼叫各縣市 API。
整理待繳停車費
每一個縣市回傳結果之後,Workflow 會統一整理成:
縣市
車牌
車種
待繳筆數
待繳金額
停車日期
繳費期限
停車單 / 催繳單
所以即使不同縣市的資料來源不同,後面的通知流程仍然可以使用相同格式。
最後統一通知
全部縣市查詢完成後,再一次彙整。
例如:
🚗 路邊停車費提醒
🚙【我的汽車|ABC-1234】
臺北市:2 筆,合計 $80
- 停車單|$40|停車日 2026-09-10|期限 2026-09-25
- 停車單|$40|停車日 2026-09-11|期限 2026-09-26
🛵【我的機車|XYZ-5678】
新北市:1 筆,合計 $20
- 停車單|$20|停車日 2026-09-12|期限 2026-09-27
總計:3 筆,$100
這樣不用收到幾十則 Telegram 訊息,而是每天最多收到一份整理好的結果。
另外,如果某個 API 查詢失敗,也會另外標示:
⚠️ 有 2 個查詢失敗,
這些縣市不能視為沒有欠費:
- 我的汽車|某縣市|查詢失敗
- 我的機車|某縣市|Timeout
這點很重要,因為「API 查不到」跟「沒有停車費」是兩件完全不同的事情。
最後把 Workflow 啟用後,基本上就不用再管它了。
之後只要有未繳的路邊停車費,就會自動收到 Telegram 通知。
Top comments (0)