AutoBanking
出帳管控與自動對帳

工程規格書 v1.0 — 四階段實作計畫
YoungSaaS 開發團隊|2026 年 4 月

1. 現況架構與風險缺口

1.1 現行撥款流程

現行撥款流程
紅色 = 人工操作,無系統防護的步驟
// 現行狀態:EgopayApprM.status
const STATUS = {
  PENDING:  0,   // 待執行
  SUCCESS:  1,   // 成功(人工點確認)
  FAILED:   2,   // 失敗
};
// 問題:0→1 之間沒有中間狀態,無法區分「還沒匯」和「已匯未確認」

1.2 風險點(具體 code path)

風險發生位置根因
重複匯款 popup.js FillForm button 無 status check,點兩次就填兩次。多人同帳號操作無 lock。
狀態斷層 EgopayApprM status 欄位 只有 0/1/2,缺 3=匯款中。人工匯完到人工點確認之間是黑洞。
手動回填錯誤 ApprC::update_status() transaction_time / bank / fee 全靠人工輸入,無 source of truth 可驗。
出帳無記錄 AutoBankingC::receive_esun_bank() 出帳交易被 if (!$in) continue; 過濾掉,不寫 DB。

1.3 現有基礎設施

auto_banking/ — Chrome Extension (MV3)
  background.js — Refresh (15s) + StateCheck (5s) 雙 alarm Extension
  popup.js — 撥款單列表 + FillForm 按鈕
  src/banks/{esun,first,fubon,taishin,sinopac,tcb,skbank,hwatai,tfcc}.js — 各銀行 content script
  src/content-main.js — 共用 message handler + StateCheck handler

gamepp-web/ — Laravel HQ 後端 HQ
  app/Http/Controllers/AutoBankingC.php — 接收交易 + payment 配對
  app/Http/Controllers/ApprC.php — 撥款審核 + update_status
  app/Models/EgopayApprM.php — 撥款單 model (status 0/1/2)
  bank_transactions table — 已有 out, balance 欄位(schema 預留,未使用)

2. 目標架構

系統架構
紫色 = HQ 後端, 藍色 = Extension, 綠色 = DB, 橘色 = 配對引擎

2.1 改造後撥款流程

目標流程
綠色 = 系統自動,人員只需執行「放行」這一步

2.2 階段依賴

Phase 1 完整網銀明細 → Phase 2 撥款狀態控管 → Phase 3 撥款自動對帳 → Phase 4 異常單系統
每個 Phase 的產出是下一個 Phase 的 input。Phase 2 有 feat/ticket-state-management branch 半成品可用。

Phase 1:完整網銀明細

資料基礎層。Extension 上傳入帳+出帳所有交易,HQ 完整儲存,餘額連貫性驗證。

1-A. Extension 端變更

交易上傳(9 家銀行 content script)

// 現行:只上傳入帳
if (tx.in > 0) records.push(tx);

// 改為:全部上傳,加 direction 標記
tx.direction = tx.in > 0 ? 'in' : 'out';
records.push(tx);

去重邏輯調整(background.js)

// 現行去重 key(48h 滾動窗口)
const dedupKey = `${date}_${time}_${account}_${amount}`;

// 改為:入帳/出帳分開去重
const dedupKey = `${date}_${time}_${account}_${direction}_${direction === 'in' ? amount_in : amount_out}`;
影響檔案:
src/banks/*.js — 移除 in_record 過濾 修改
background.js — 去重 key 加 direction 修改

1-B. HQ 端變更

AutoBankingC 處理出帳

// AutoBankingC::receive_esun_bank()
// 現行:出帳跳過
if (!$record['in']) continue;

// 改為:出帳也寫入 bank_transactions
if ($record['direction'] === 'out') {
    BankTransaction::create([
        'bank'     => $bank,
        'out'      => $record['out'],
        'account'  => $record['account'],
        'balance'  => $record['balance'],
        'accounting_datetime' => $record['datetime'],
    ]);
    continue; // 不進 payment 配對
}

DB Schema 變更

-- bank_transactions 新增索引
ALTER TABLE bank_transactions
  ADD INDEX idx_bank_out_acctime (bank, out, accounting_datetime);

餘額連貫性驗證

// 驗證邏輯(同帳戶按時間排序)
// prev.balance + curr.in - curr.out == curr.balance
// 不連貫 → 標記 balance_gap 警告(不硬擋)

1-C. 交易上傳 Sequence

交易上傳流程
影響檔案:
AutoBankingC.php — 出帳寫入 + 餘額驗證 修改 HQ
bank_transactions — 加 index migration

Phase 2:撥款狀態控管

控管層。撥款單五階段狀態追蹤 + 三道防重複閘門 + 出帳↔撥款自動配對。

2-A. 狀態機設計

撥款單狀態機
// Extension 端 ticket 狀態(chrome.storage.local)
const TICKET_STATE = {
  UNFILLED:   'unfilled',    // 初始 → 5min 後 needs_speed → 15min 後 overdue
  FILLED:     'filled',      // FillForm 填入後
  RELEASING:  'releasing',   // 放行確認頁偵測到
  RELEASED:   'released',    // 放行結果頁偵測到成功
  CONFIRMED:  'confirmed',   // 出帳 bank_tx 配對成功
};

// HQ 端 EgopayApprM.status
const HQ_STATUS = {
  PENDING:    0,   // 待執行
  SUCCESS:    1,   // 成功
  FAILED:     2,   // 失敗
  FILLING:    3,   // 匯款中(新增)
};

2-B. 防重複匯款三道閘門

閘門觸發時機檢查邏輯行為
1. FillForm 鎖定 popup.js 點擊填入 ticket_state !== 'unfilled' 隱藏按鈕,不可再填
2. Pre-check API FillForm 執行前 POST /api/auto_banking/pre_check
EgopayApprM.status === 0
status !== 0 → 拒絕,回傳目前狀態
3. 出帳比對 放行前 同帳號+同金額+近 10 min 出帳 彈出警告,需人員確認

2-C. FillForm → Confirm 完整 Sequence

FillForm 完整流程

2-D. 放行偵測(各銀行 content script)

銀行偵測方式狀態
玉山 E.SUN 確認頁 DOM 解析 → 匹配 ticket → releasing
結果頁輪詢成功 → released
已完成
第一銀行 放行頁 DOM 偵測 已完成
富邦 / 台新 / 永豐
合庫 / 新光
各銀行放行確認頁 + 結果頁 DOM 結構待分析
需截圖 HTML 結構後實作
待開發
華泰 / 淡水一信 已有 API balance check,放行頁待分析 待開發

2-E. 出帳 ↔ 撥款配對邏輯

// background.js: matchOutRecordsToTickets()
// 每 5 秒 StateCheck 觸發

for (const ticket of releasedTickets) {
  const match = outRecords.find(tx =>
    tx.account === ticket.targetAccount &&
    Math.abs(tx.out - ticket.amount) <= 30 &&  // 容許匯費差額
    withinMinutes(tx.datetime, ticket.releaseTime, 30)
  );

  if (match) {
    updateTicketState(ticket.id, 'confirmed');
    // → 呼叫 HQ API 同步 status 3→1
  }
}

2-F. 新增 HQ API

EndpointMethod用途RequestResponse
/api/auto_banking/pre_check POST FillForm 前檢查 {ticket_id} {ok, status, locked_by}
/api/auto_banking/mark_filling POST 標記匯款中 {ticket_id} {ok} status→3
/api/auto_banking/confirm POST 出帳配對確認 {ticket_id, bank_tx_id} {ok} status→1 + 回填
影響檔案:
src/banks/{fubon,taishin,sinopac,tcb,skbank,hwatai,tfcc}.js — 放行偵測 新增 Extension
popup.js — 狀態標籤 + 鎖定 修改 Extension
background.js — matchOutRecords + API 呼叫 修改 Extension
ApprC.php — pre_check / mark_filling / confirm 新增 HQ
EgopayApprM.php — status=3 常數 修改 HQ
routes/api.php — 3 條新 route 修改 HQ

可用半成品: feat/ticket-state-management branch(玉山/第一已實作)

Phase 3:撥款自動對帳

自動化層。出帳配對成功後,自動回填所有人工欄位。

3-A. 自動回填邏輯

// ApprC::confirm() 被呼叫時,自動回填

$bankTx = BankTransaction::find($bank_tx_id);

$ticket->transaction_time = $bankTx->accounting_datetime;
$ticket->bank             = $bankTx->bank;
$ticket->fee              = $bankTx->out - $appr->amount;
$ticket->operator_id      = auth()->id();  // 獨立 HQ 帳號

$appr->status = 1;
$appr->bank_transaction_id = $bankTx->id;  // 建立連結
$appr->save();
$ticket->save();

3-B. 自動化前後對照

欄位現行自動化來源驗證方式
transaction_time 人工複製貼上 bank_transactions.accounting_datetime 時間差 < 30 min
bank 人工下拉選擇 bank_transactions.bank 必須 match
fee 人工輸入 (通常 15) bank_tx.out - appr.amount 差額 0~30 合理範圍
operator_id 人工下拉選擇 auth()->id()

3-C. DB Schema 變更

-- egopay_apprs 新增欄位:連結出帳記錄
ALTER TABLE egopay_apprs
  ADD COLUMN bank_transaction_id BIGINT UNSIGNED NULL AFTER status,
  ADD INDEX idx_bank_tx (bank_transaction_id);
前置條件: Phase 1 出帳資料已完整 + Phase 2 配對機制已運作 + 每位操作人員需有獨立 HQ 帳號(非共用帳號)
影響檔案:
ApprC.php — confirm() 加自動回填 修改 HQ
TicketM.php — transaction_time 等欄位更新 修改 HQ
EgopayApprM.php — 加 bank_transaction_id 欄位 migration HQ

Phase 4:異常單系統

偵測層。基於完整入帳+出帳資料,自動偵測異常交易。

4-A. 異常偵測引擎規格

異常類型偵測條件比對欄位動作
重複匯款 同帳號+同金額 出帳 > 撥款單數 account, out, datetime 自動建立異常單
時間錯誤 金額+帳號+銀行 Match
時間 Unmatch (>30min)
amount, account, bank, datetime 標記待確認
銀行錯誤 金額+帳號+時間 Match
銀行欄位不一致
amount, account, datetime, bank 標記待確認
未開單 bank_tx 入帳無對應 payment in, account, datetime 列入未開單清單
餘額缺口 prev.balance + in - out ≠ curr.balance balance, in, out 標記帳戶缺口

4-B. 重複匯款處置流程

// 偵測到重複匯款後的處置選項
enum DuplicateResolution {
  REFUND_BANK,        // 匯回原帳號
  SELL_ORDER_REFUND,  // 賣單還款(客戶多賣一次幣)
  BUY_ORDER_REFUND,   // 買單還款 + 開新賣單撮合
}

4-C. 查詢工具強化

影響檔案:
AnomalyDetectionService.php — 異常偵測引擎 新增 HQ
AnomalyController.php — 異常單 CRUD + Dashboard 新增 HQ
anomalies table — 異常單 migration

5. 實施總覽

5-A. 全階段影響矩陣

PhaseExtension 端HQ 端DB Migration前置條件
1 9 banks content script
background.js 去重
AutoBankingC.php 加 index
2 7 banks 放行偵測
popup.js 狀態鎖定
ApprC.php 3 API
EgopayApprM.php
Phase 1
3 ApprC.php 回填
TicketM.php
加欄位 Phase 2 + 獨立帳號
4 偵測引擎 + Dashboard 新 table Phase 1~3

5-B. 風險與緩解

風險等級緩解
銀行改版 → 放行偵測失效 偵測失敗 fallback 手動確認,不阻斷。DOM hash 變更時 console.warn。
出帳配對誤判(同金額同時間) 帳號必須 match;多候選 → 不自動配對,標記人工確認。
Extension 新舊版本並存 HQ API 向後相容。舊版不送 direction,HQ 視為入帳(現行行為)。
ApprC::update_status() race condition pre_check API 使用 SELECT ... FOR UPDATE,mark_filling 做 CAS。
餘額驗證誤報 警告不硬擋。缺口清單供人工確認。