Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ea9c429
docs(plans): add implementation plan for #46 UX-D terminology
poterpan Aug 29, 2026
6d95606
fix(admin-ui): 同步預覽的「移除」標題不再誤導 (#46 P0-7)
poterpan Aug 30, 2026
ba26fd7
refactor(copy): 核准 → 驗證,動作與狀態用詞統一 (#46 D1)
poterpan Aug 30, 2026
47868a9
refactor(copy): paid 狀態一律顯示「已繳待驗」,凍結筆數改為「已繳待驗、已驗證」 (#46 D2)
poterpan Aug 30, 2026
22818d8
refactor(copy): 支付渠道→繳費渠道、帳單頻道→繳費頻道、使用者→成員 (#46 D6/D8/D9)
poterpan Aug 30, 2026
a7ddbe9
feat(admin-ui): payments.source 加中文顯示對映 (#46 D11 之一)
poterpan Aug 30, 2026
f240d73
refactor(copy): 本期 → 此期/實際期別,開帳 → 開繳,按鈕名與文案對齊 (#46 D3/D5/D7)
poterpan Aug 30, 2026
6e6c323
refactor(copy): 成員繳費頁改稱「月份」,與 Discord 一致(後台維持「期別」) (#46 D4)
poterpan Aug 30, 2026
7cdc833
fix(web): 繳費頁金額補千分位,與 Discord 一致 (#46 D19 web 半邊)
poterpan Aug 30, 2026
53a020c
docs(worker): 標註 timezone 是死設定 (#46 D22)
poterpan Aug 30, 2026
6809f06
feat(admin-ui): 訂閱狀態/供應商改顯示中文(顯示層對映,DB 值不變) (#46 D11)
poterpan Aug 30, 2026
2ac3ab4
refactor(copy): 「渠道、截圖、備註至少填一項。」統一三個成員入口,並修正頁面標題與句末標點 (#46 D13/D20)
poterpan Aug 30, 2026
fb5315e
feat(worker): 後台可觸及的錯誤訊息改為中文(格式驗證維持英文) (#46 D14)
poterpan Aug 30, 2026
2506175
feat(worker): 狀態競態改回中文說明,不再像 crash(id/目標狀態保留在錯誤物件上) (#46 D15)
poterpan Aug 30, 2026
7c794be
fix(admin-ui): 方案金額補千分位;身分組欄位不再暗示自動發放 (#46 D19/D21)
poterpan Aug 30, 2026
dd6ba76
docs(copy): 標註身分組不自動發放、寫死訊息不受模板控制、timezone 死設定 (#46 D16/D22)
poterpan Aug 30, 2026
5a9c683
docs(readme): 同步 P0-7 的用詞(移除已退訂 → 訂閱已暫停/已取消) (#46)
poterpan Aug 30, 2026
b6c53e3
style(copy): 統一「…」慣例、成功訊息前綴與列表分隔 (#46 D17/D18)
poterpan Aug 30, 2026
ad15701
docs(readme): 測試徽章 426 → 431 (#46)
poterpan Aug 30, 2026
00d8de1
fix: 依 Codex 審查修正五項 (#46)
poterpan Aug 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
![Cloudflare Workers](https://img.shields.io/badge/Cloudflare-Workers-F38020?logo=cloudflare&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)
![React](https://img.shields.io/badge/React-18-61DAFB?logo=react&logoColor=black)
![Vitest](https://img.shields.io/badge/tests-426%20passing-0f6e63?logo=vitest&logoColor=white)
![Vitest](https://img.shields.io/badge/tests-431%20passing-0f6e63?logo=vitest&logoColor=white)
![Serverless](https://img.shields.io/badge/100%25-serverless-074340)

<br/>
Expand Down Expand Up @@ -65,23 +65,23 @@ pre-wired) and a multi-workspace-ready data model, so it generalizes well beyond
plans it couldn't match, cancelled subs that need a human) before you apply it; idempotent re-runs.
- 🧾 **Review queue + reconciliation** — an admin dashboard with per-plan / per-channel totals, a
one-click verify queue, manual back-fill, single-payment delete, undo-verify, and frozen period
amounts (price changes never rewrite history). A **重新同步本期帳單** action re-aligns an opened
amounts (price changes never rewrite history). A **重新同步此期帳單** action re-aligns an opened
period's bills to the current roster/price (preview before applying), optionally pinging newly-added members.
Tapping a member's name (or a submission alert) opens the **成員×期別合併審核**: the shared
screenshot once, every settled row, and one 一鍵全部核准 button. The queue, that view and the
screenshot once, every settled row, and one 一鍵全部驗證 button. The queue, that view and the
review dialog all work on a phone.
- 🔔 **Customizable notifications** — editable templates (with live preview + validation) for the
billing-opened notice, the batched overdue reminder, and the persistent pay message.
- 📲 **Submission alerts** — when a member submits a payment, push the owner a Bark and/or webhook
notice (Discord / Google Chat / Slack — body shape auto-detected by host) with a deep link
straight to that member's period review — one submit settles every subscription, so the link
opens all of them together with a 一鍵全部核准 button.
opens all of them together with a 一鍵全部驗證 button.
- ⏰ **Daily cron, idempotent** — opens billing, sends an overdue reminder **every day** until a
period has no unpaid bills left, and enforces screenshot retention — all deduped through
`notification_logs`.
- 🛡️ **Access-gated admin** — the whole admin host sits behind Cloudflare Access (email OTP); the
SPA and its API are same-origin so the Access JWT reaches the Worker.
- 🧪 **Real-runtime tests** — 426 Vitest cases run against actual Miniflare D1 + R2 (FK constraints
- 🧪 **Real-runtime tests** — 431 Vitest cases run against actual Miniflare D1 + R2 (FK constraints
enforced), not mocks.

## How a payment flows
Expand Down Expand Up @@ -226,7 +226,7 @@ payment screenshots) and fill in `wrangler.toml` accordingly — `database_id`,
- **Review queue** — Payments → status pills → the **已繳待驗** queue floats to the top → one-click
✅ verify (or open a row for screenshots, channel, reject, amount override, delete proof, **撤回驗證**
to undo a mistaken verify, or **delete the whole bill**).
- **重新同步本期帳單** — on Payments, re-align the selected opened period's bills to the current
- **重新同步此期帳單** — on Payments, re-align the selected opened period's bills to the current
roster/price (add missing · remove de-subscribed · reprice pending · freeze settled), with a preview
before applying and an option to ping newly-added members with the pay button.
- **發起繳費** — confirm the selected period's per-plan amounts (any change becomes the plan's new
Expand All @@ -244,11 +244,11 @@ payment screenshots) and fill in `wrangler.toml` accordingly — `database_id`,
**重置催繳發送紀錄**. The first two show the exact recipients before sending; all three take a
confirmation step and report the real counts — a non-2xx from Discord is reported as a failed
send, so the billing notice's `sent_at` is never moved forward and the overdue dedup slot is
released so the next run (cron included) can try again. Reopening/closing a period lives in 收回本期開繳 on
released so the next run (cron included) can try again. Reopening/closing a period lives in 收回此期開繳 on
the payments page, not here.
- **Submission alerts** — set a Bark device key and/or a webhook (Discord / Google Chat / Slack) under
Settings → 繳費通知; each new submission then pushes you a notice that opens that member's whole
period for review (shared screenshot once, every row listed, 一鍵全部核准), phone-friendly. Both
period for review (shared screenshot once, every row listed, 一鍵全部驗證), phone-friendly. Both
are optional and best-effort (a slow or failing endpoint never blocks the payment).
- **Daily cron** (01:00 UTC = 09:00 Asia/Taipei) — idempotently opens each period's bills, posts the
billing-opened notice (tagging plan roles), sends **an overdue reminder every day** listing the
Expand Down
33 changes: 17 additions & 16 deletions README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
![Cloudflare Workers](https://img.shields.io/badge/Cloudflare-Workers-F38020?logo=cloudflare&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)
![React](https://img.shields.io/badge/React-18-61DAFB?logo=react&logoColor=black)
![Vitest](https://img.shields.io/badge/tests-426%20passing-0f6e63?logo=vitest&logoColor=white)
![Vitest](https://img.shields.io/badge/tests-431%20passing-0f6e63?logo=vitest&logoColor=white)
![Serverless](https://img.shields.io/badge/100%25-serverless-074340)

<br/>
Expand Down Expand Up @@ -45,7 +45,7 @@ ChipPot 解決的是一個很具體的痛點:社團大量採購 OpenAI / Anthr

- 💳 **Discord 內繳費** — 常駐「繳費」按鈕 → 選渠道 → 完成。一次送出就把該成員**當期所有訂閱**
一起結清(多方案加總)。
- 🔗 **自助綁定** — 成員自己把 Discord 帳號接到名單:`/綁定`、繳費按鈕,或帳單頻道裡常駐的公開
- 🔗 **自助綁定** — 成員自己把 Discord 帳號接到名單:`/綁定`、繳費按鈕,或繳費頻道裡常駐的公開
**綁定 Discord** 按鈕。名單超過 Discord 選單的 25 筆上限時改用搜尋(`/綁定` 的「名字」自動完成,
或按鈕跳出的搜尋彈窗)。管理員也能手動指定 ID,或**解除綁定**讓成員重新綁。
- 🧾 **審核結果會回到成員手上** — 退回一定在頻道 @ 當事人並附上原因(成員唯一知道要重繳的管道,
Expand All @@ -59,19 +59,19 @@ ChipPot 解決的是一個很具體的痛點:社團大量採購 OpenAI / Anthr
代表訂閱(或恢復暫停中的訂閱),`FALSE` 代表暫停該訂閱,留空則完全不動。每次上傳都會先顯示完整
差異預覽(新成員、新增/暫停/恢復的訂閱、對不到的方案、需人工處理的已取消訂閱)再套用,可冪等重跑。
- 🧾 **審核佇列 + 對帳** — 後台看板有各方案/各渠道金額、一鍵驗證佇列、手動補登、單筆繳費刪除、
**撤回驗證**(誤按的驗證可還原),且**當期金額凍結**(改價不會回頭改歷史帳)。**重新同步本期帳單**
**撤回驗證**(誤按的驗證可還原),且**當期金額凍結**(改價不會回頭改歷史帳)。**重新同步此期帳單**
會把已開期別的帳單重新對齊目前名單/現價(先看差異預覽再套用),並可順便 @ 通知新加進來的成員。
點成員名字(或從繳費推播點進來)會開啟**成員×期別合併審核**:共用截圖只出現一次、當期每一筆都列出,
已繳待驗的可一鍵全部核准。佇列、這個畫面與審核彈窗在手機上都能用。
已繳待驗的可一鍵全部驗證。佇列、這個畫面與審核彈窗在手機上都能用。
- 🔔 **可自訂通知** — 開繳通知、整批逾期催繳、常駐繳費訊息三種文字皆可自訂模板(含即時預覽 + 格式驗證)。
- 📲 **繳費推播** — 成員送出繳費時,可推一則 Bark 和/或 webhook(Discord/Google Chat/Slack,body
格式依主機自動判斷)給擁有者,並帶上直接跳到該成員該期審核的深連結——一次送出會結清所有訂閱,
所以連結會把它們一起打開,可一鍵全部核准。
- ⏰ **每日 cron、冪等** — 自動開帳、進入催繳後**每天發一則**整批催繳(直到全部繳完)、執行
所以連結會把它們一起打開,可一鍵全部驗證。
- ⏰ **每日 cron、冪等** — 自動開繳、進入催繳後**每天發一則**整批催繳(直到全部繳完)、執行
截圖保存期清理,全部經 `notification_logs` 去重。
- 🛡️ **Access 保護的後台** — 整個後台主機在 Cloudflare Access 後(email OTP);SPA 與其 API 同源,
Access JWT 因此能到達 Worker。
- 🧪 **真環境測試** — 426 個 Vitest 案例跑在真正的 Miniflare D1 + R2(強制 FK 約束),不是 mock。
- 🧪 **真環境測試** — 431 個 Vitest 案例跑在真正的 Miniflare D1 + R2(強制 FK 約束),不是 mock。

## 一筆繳費怎麼跑

Expand All @@ -81,10 +81,10 @@ ChipPot 解決的是一個很具體的痛點:社團大量採購 OpenAI / Anthr
├─ 還沒綁定? → 「選你的名字」下拉 → 綁定 Discord id 後,接著繳費
│
▼
ephemeral:列出本期各方案 + 總額 + 渠道下拉
ephemeral:列出該期各方案 + 總額 + 渠道下拉
│ (/繳費 或網頁可另附截圖/備註)
▼
settleUserPeriod() — 把每筆 pending/rejected 標記為「已繳」,共用同一張截圖 key
settleUserPeriod() — 把每筆 pending/rejected 標記為「已繳待驗」,共用同一張截圖 key
│
▼
後台看板 → 審核佇列 → ✅ 驗證(自動帶入申報渠道)
Expand All @@ -98,7 +98,7 @@ ChipPot 解決的是一個很具體的痛點:社團大量採購 OpenAI / Anthr
```
Discord ─┐ ┌─ D1 (chippot-db) — SQLite 帳本
網頁上傳 ─┼─► Cloudflare Worker ───────┤─ R2 (chippot-proofs) — 私有截圖
後台 UI ─┤ core + adapters └─ Cron(每日 01:00 UTC) — 開帳 / 催繳 / 保存期
後台 UI ─┤ core + adapters └─ Cron(每日 01:00 UTC) — 開繳 / 催繳 / 保存期
Cron ─┘
```

Expand Down Expand Up @@ -199,6 +199,7 @@ pnpm --filter @chippot/worker register
Discord guild/頻道 id、可發起繳費的管理員白名單(`admin_discord_ids`)、三種可自訂的通知模板,
以及選填的**繳費推播**(Bark 裝置金鑰和/或 incoming webhook;推播裡的審核深連結由 `ADMIN_ORIGIN`
組出,不必另外設定)。
`timezone` 目前是死設定——時區固定為 Asia/Taipei(`core/time.ts`),改這個欄位沒有任何效果。
- **Discord** — 把 app 的 Interactions Endpoint 設成 Worker 的 `/interactions`,再用上面的腳本註冊 guild 指令。

## 後台與營運
Expand All @@ -208,26 +209,26 @@ pnpm --filter @chippot/worker register
**申報渠道**(對帳最常看的一欄,不必再點進詳情;「來源」移到詳情彈窗),沒設定 R2 時整欄隱藏「憑證」。
點開某筆可看截圖、渠道、退回、改金額、刪截圖,也可**撤回驗證**(還原誤按的驗證)或**刪除整筆帳單**。
- **成員×期別合併審核** — 點表格裡的成員名字(或從繳費推播的深連結進來),把該成員當期的帳單收在
同一頁:共用截圖只顯示一次、每一筆都列出且可單筆核准/退回,已繳待驗的可一鍵掃過去核准(還有
同一頁:共用截圖只顯示一次、每一筆都列出且可單筆驗證/退回,已繳待驗的可一鍵掃過去驗證(還有
待繳/已退回的會明講需要逐筆處理)。手機版把繳費表改成堆疊卡片、彈窗改成底部抽屜。
- **重新同步本期帳單** — 在繳費審核頁,把目前篩選的**已開期別**帳單對齊現在的名單/價格
(補缺漏 · 移除已退訂 · 待繳改現價 · 已繳/已驗證凍結);先出差異預覽再套用,並可勾選用繳費按鈕
- **重新同步此期帳單** — 在繳費審核頁,把目前篩選的**已開期別**帳單對齊現在的名單/價格
(補缺漏 · 移除訂閱已暫停/已取消者 · 待繳改現價 · 已繳待驗/已驗證凍結);先出差異預覽再套用,並可勾選用繳費按鈕
@ 通知新加進來的成員。選「全部期別」時按鈕停用。
- **發起繳費** — 確認所選期別各方案金額(任何更動就是該方案的新定價),先看「會建立/改價哪些帳單、
定價 before→after、是否會發通知」的預覽,確認後才送出。預設期別是**目前收款中的那一期**,
「預開下期」是要自己勾的次要選項。可從後台「設定」或 Discord 的 `/發起繳費` 觸發
(後者可帶 `期別`,方案超過 5 個時會直接請你改用後台——開表單與送出表單都會重驗一次)。
Discord 沒有回應成功時回報的是「通知發送失敗」而不是已發送:帳單與開繳狀態照實留著,可到推播狀態補送。
- **綁定按鈕** — 設定 → 工具 → 在帳單頻道張貼一則常駐的公開**綁定 Discord** 按鈕,讓成員主動綁
- **綁定按鈕** — 設定 → 工具 → 在繳費頻道張貼一則常駐的公開**綁定 Discord** 按鈕,讓成員主動綁
(第一次繳費時綁定仍然保留為 fallback)。
- **推播狀態** — 看板顯示開繳/逾期通知是否已發,並提供 **重發開繳通知**(只重貼公告,不建帳單、
不會讓期別短暫變回未開繳)、**催繳未繳成員**(@ 全部未繳者,與只 @ 逾期者的每日 cron 不同)
與 **重置催繳發送紀錄**。前兩者送出前會先列出實際名單,三者都要再確認一次,並回報真實筆數:
Discord 沒回成功就說發送失敗——開繳通知的 `sent_at` 不會被往前推,催繳也會把佔用的去重名額還回去,
下次(含每日 cron)才送得出去。
要把期別改回未開繳請用「繳費審核 → 收回本期開繳」。
要把期別改回未開繳請用「繳費審核 → 收回此期開繳」。
- **繳費通知** — 在設定 → 繳費通知填 Bark 裝置金鑰和/或 webhook(Discord/Google Chat/Slack),
之後每筆新送出都會推你一則,點開就是該成員當期的合併審核(共用截圖一次、每筆列出、可一鍵核准),
之後每筆新送出都會推你一則,點開就是該成員當期的合併審核(共用截圖一次、每筆列出、可一鍵驗證),
手機上也好操作。存檔前可先按**送出測試**確認打得通。兩者都選填、best-effort(端點慢或掛掉都不會
卡住成員繳費)。
- **每日 cron**(01:00 UTC = 台北 09:00)— 冪等地開出各期帳單、發開繳通知(tag 方案身分組)、
Expand Down
Loading