在真的開始收錢之前,先替系統打一層地基:藍新送來的付款通知,系統要能確認是真的、同一筆只算一次、晚到或順序顛倒也不會算錯、處理到一半當機也不會漏;付款和發票各記各的。這張不做任何能真的被扣款的按鈕。

#258 金流(藍新):訂閱扣款與綁卡的骨架——付款與發票狀態分開、webhook 驗簽/去重/亂序/補償

狀態
卡住
誰在做
宜芳分派
做到哪
研究底稿交了,程式還沒開始。PR #283(Draft,沒合)把驗簽、重複、亂序、當機恢復、退款、個資保存的風險和一張「假服務商」測試清單整理好,但它自己講明不是 ADR。這張要求的第一步——一份給負責人過目的 ADR(三張表的欄位、驗簽/去重/亂序/補償的介面)——還沒寫。另外 #284(PR #285,已上線)在管理台做了一個假的帳務展示台,點得出類似的規則,但那不是這張的成品(ADR-0129 第六節)。
卡在
卡在負責人兩件事:一是還沒有 ADR 可以讓他逐項說「同意」(要先有人寫);二是藍新那邊——特店帳號、測試(sandbox)帳號、串接文件、定期定額的實際費率都還沒拿到(10/03 工程把這幾件回派給負責人)。負責人 10/04 已寄信問藍新(題目在 #316 第五節,主要是發票與合約費用),回信還沒到。骨架本身照本單可以不等藍新文件,但要等 ADR 被接受。
下一步
宜芳:決定這張分派給誰寫 ADR——拿 #283 的底稿當材料,把 10/04(#316)、10/05(#326)已經裁掉的幾題收進去,送給負責人逐項回「同意」並留日期;同時跟負責人確認藍新申請到哪一步(測試帳號、串接文件、定期定額費率、是藍新代排扣款還是我們每期去請款)。
提醒:一、本文〈還沒裁〉有三題已經被後來的裁決回答、本文沒改:欠費寬限(#316:扣款失敗 21 天照常,之後唯讀)、發票自動還是人工開(#316:扣款成功那一刻開)、有沒有年繳(#316:有);10/03 工程回派負責人的「續訂失敗寬限天數」也已被 21 天回答。寫 ADR 時直接收進去,不要再問一次。二、本文自己有兩句打架:〈要做什麼〉第 3 點要畫面上有「確認中」與逾時提示,〈測試方式〉第 5 點又說本單不產出畫面——建議把畫面留到接真金流那張。三、標題寫「綁卡的骨架」,但綁卡其實在 #257(假的綁卡已由 PR #287 上線),這張不處理綁卡。四、管理台「功能展示」的假帳務台(#284)看起來很像這張做完了,其實不是:它只在瀏覽器記憶體裡跑,ADR-0129 明講不接受這張的正式方案。五、本文〈已經裁定的〉列了「不重用案件請款的 firm_receipts」「上線要另案批准」兩條,出處是研究文件的一段,但那段標題就寫「不是新增產品裁決」,所以這裡沒當定案(工程上沒有爭議,寫 ADR 時請負責人一起點頭即可)。六、#326 定了加席要另扣一筆,代表扣款不只每期訂閱一種,ADR 的「同一期只扣一次」要把這種也算進去。

這張要解決什麼

之後要按月或按年付訂閱費的付款人(例如事務所負責人);這張的直接受益者是之後接真金流的工程,和管帳的負責人,付款人在藍新的頁面刷完卡被導回 Slate;藍新另外在背景送付款通知給 Slate。通知可能重送好幾次、可能晚到、可能順序顛倒,Slate 自己也可能處理到一半當機——要能 不管通知怎麼來,每一筆錢只算一次、狀態不會被舊消息打回去、原始通知不會遺失;付款和發票是兩本帳。這層在開收費之前先做好、測好,之後拿到藍新文件只要接一個轉接頭(adapter),不用重寫

一步一步 12 步

  1. 宜芳(PM) 把這張分派出去,請接手的工程先寫一份 ADR(Proposed):三張表——原始通知、付款事件、發票——各記什麼;驗簽、去重、亂序、補償四件事的介面;還有「同一位付款人同一期只能成功扣一次」要怎麼擋
    docs/adr/ 多一份狀態寫 Proposed 的 ADR,用 PR 送上來給負責人看。被接受之前,不寫資料表、不開對外的通知端點
  2. 負責人 讀那份 ADR,一項一項回「同意」或改,寫在 ADR 或 issue 上並留日期
    ADR 改成 Accepted,檔頭附負責人原話和日期(ADR-0096:找不到原話就不算 Accepted)。這一步過了,工程才開始寫骨架
  3. 負責人 (跟上面兩步同時進行)向藍新申請特店帳號、測試帳號與串接文件,問清楚定期定額的實際費率、是藍新代排每期扣款還是我們每期主動請款、通知裡有沒有穩定的通知 ID 與事件順序欄位
    文件到手之後,另一張單才能接上真的簽名格式和 API;費率到手之後,#254 的扣點表要用含稅總額重算。這一步不擋骨架
  4. 工程(宜芳分派的人) 照 Accepted 的 ADR 做骨架:一個假的服務商(test provider)、付款狀態機、發票狀態機、接收通知的驗簽/去重/亂序/補償,每一條先寫會失敗的測試(紅燈)再實作
    一張骨架 PR:全部用假服務商驅動,不接藍新、不接 ezPay、不開任何付款按鈕;新端點附「付款人 A 讀不到付款人 B 的帳單」的隔離測試
  5. 付款人(未來;這個畫面在接真金流那張才做) 在藍新頁面刷完卡,被導回 Slate
    畫面只顯示「確認中」,不會因為網址上帶著 success 之類的字就當成已付款、解鎖功能。要等後端真的收到並處理過藍新的通知,才變成已付款
    付款確認中(示意,這張不做)
    付款確認中
    正在等藍新的付款通知;收到之前不算付款成功
    等太久會顯示逾時提示——逾時之後怎麼處理(人工查單、客服)還沒定
  6. 藍新(服務商) 在背景送一封付款通知(webhook)給 Slate
    Slate 先驗簽:沒設密鑰、簽名不對、時間戳過期,一律拒絕——不是記個警告照樣處理;被拒絕的通知不改任何帳
  7. Slate 後端 驗簽通過後,先把整封通知原封不動存進「原始通知」表,存好才開始處理
    處理到一半當機,通知還在;可以用一支函式或腳本把它重放一次,重放不會把同一筆錢算兩次
  8. Slate 後端 檢查這封通知以前有沒有收過
    同一個通知 ID、內容一樣:回藍新「收到了」(免得它一直重送),但不再處理一次。同一個 ID 但內容不一樣(例如金額不同):標成衝突、不改帳、留給人查,不會靜靜把舊的蓋掉
  9. Slate 後端 判斷先後:看通知自己帶的事件時間或版本,不看哪一封先到
    較舊的通知晚到時,不會把狀態打回去。例:先收到「付款成功」,後來才到一封其實發生得更早的「付款失敗」——狀態維持付款成功
  10. Slate 後端 把付款狀態往前推:等待中 → 已付款/失敗/已退款
    付款狀態只有通知能改。發票是另一條獨立的狀態(未開 → 已開/作廢),照負責人 10/04 的裁決在扣款成功那一刻開(開票本身是 #259 的事)。付款人畫面從「確認中」變成「已付款」
  11. 宜芳或負責人(想先看懂這些規則長什麼樣) 用管理員帳號開「管理台 → 功能展示」,打開「啟用帳務展示」,按「載入範例」,對帳單按「送成功」,再到事件紀錄按「重送相同事件」「同 ID +1 分」,最後回帳單按「送失敗」
    假帳單只入帳一次;重送的那筆顯示「重複(未重做)」;同 ID 不同金額顯示「衝突(未套用)」;晚到的失敗不會把已付款打回去。這是 #284 做的假展示台,資料只在瀏覽器裡、重新整理就消失,拿來看懂規則用,不是這張的成品
    管理台 → 功能展示:帳務生命週期模擬台(#284,已上線)
    啟用帳務展示(Feature flag),預設關
    帳務生命週期模擬台
    模擬模式:不會扣款、不會寄信、不會開真發票;重新整理資料消失。請勿輸入真個資。
    載入範例
    重置模擬台
    帳單:期別 · 金額;狀態、已付、已退
    送成功
    送失敗
    模擬驗證失敗
    部分退款
    開立假發票
    事件紀錄:種類/ID、狀態、訊息、操作(重送相同事件、同 ID +1 分、重試)
    收件回條:收到不等於已入帳
  12. 宜芳(PM) 照下面的驗收步驟看骨架 PR,過了才請審查者合併
    合併之後正式站還是沒有任何能真的扣款或開通訂閱的按鈕;接真藍新、開收費開關,各是之後另開的單

已經定的 16 條

還沒定的 10 題

工程做法本身負責人還沒點頭:付款和發票分兩條狀態機、通知先存再處理、同 ID 不同內容算衝突、不靠導回網址判斷成功。本文照這個做法寫,但出處(研究文件第 7 題)自己寫明是「建議契約,批准前不啟用」
  • 照 #283 的整理接受
  • 逐項修改後接受
建議:寫成 ADR 後請負責人逐項回「同意」並留日期;沒點頭前不寫表、不開端點(#283)
誰裁:負責人
同一位付款人、同一期,出現兩封不同的「付款成功」(例如逾時後重新請款,或付款人等不及又按一次)要怎麼擋?通知 ID 去重擋不住這種
  • 自動擋第二筆
  • 先向藍新查單再決定
  • 轉人工處理
建議:設計審查三位都同意要多一層「同一付款人+同一訂閱+同一期只能成功一次」的業務鍵,或至少留一條主動向藍新查單對帳的路;加席另扣的單筆也要有自己的鍵(#258 10/02 留言、#283)
誰裁:負責人
定期定額是藍新代排每期扣款,還是 Slate 每期主動去請款?
  • 藍新代排
  • Slate 主動請款(要另開一張做扣款排程)
誰裁:負責人(問藍新)
藍新定期定額的實際手續費率是多少?
  • 向藍新業務確認
建議:(這段講成本或毛利,公開頁不放——看 GitHub #258)
誰裁:負責人(問藍新)
退款怎麼處理:全額還是部分、誰核准、發票作廢還是開折讓;要不要多一個「拒付(chargeback)」狀態;退款或拒付時,對應還沒用的點數(含加購點數)要不要收回
  • 作廢重開
  • 開立折讓
  • 依退款類型分開處理
建議:減席不退錢已經定了(#316);其他等會計師回覆 #316 第五節「各種退款走作廢還是折讓」
誰裁:負責人+會計師
訂閱到期日怎麼算、以台北時間還是 UTC 為準?
  • 台北時間
  • UTC
建議:repo 的慣例是「今天」一律用台北日期(CLAUDE.md〈已知陷阱〉),建議照台北
誰裁:負責人
付款人等太久、通知一直沒來(確認中逾時),之後怎麼辦?
  • 人工向藍新查單
  • 請付款人聯絡客服
  • 系統自動查單
誰裁:負責人
原始通知可能含個資或卡片資訊:哪些欄位可以存、存多久、誰能看?
  • 照律師/會計意見定
建議:拿到答案前先擋,不自己訂保存年限;日誌只記去敏後的識別碼(#283 第 8 點)
誰裁:負責人+律師
回了「收到」之後處理卡住或失敗:誰負責重試、告警、人工重放,多久要升級?
  • 指定一位維運負責人與時限
建議:ADR 接受前就寫明(#283 給 PM 的第 1 題)
誰裁:負責人
付款成功後發點數,是不是一定要等看到「已付款」通知才發(不能在等待中、或使用者被導回的那一刻就發)?
  • 寫進 ADR 當規則
  • 留給點數帳本那張(#243)定
建議:設計審查建議在 ADR 寫明(#258 10/02 留言);機構帳本與付款人帳本先不互接已定(#326)
誰裁:負責人

怎麼驗收 9 項

這張不做

相關的單