transactions 狀態變動

文件狀態:草稿 / 已補 quote bid contact charge 排查主線
最後驗證:2026-05-07
來源:Common Process Documents、Lib/Model/Transaction.php::logQuoteBidTxn、Lib/Model/QuoteBid.php contact charge trace

目的

transactions 是交易與扣款紀錄主表。它用 class + foreign_id 連回不同 domain object,其中 quote bid contact charge 會使用:

transactions.class = QuoteBid
transactions.foreign_id = quote_bid_id

這裡專注 table 狀態判讀與跨表關聯;wallet/card/TapPay/Stripe 的完整分支規則放在 付款與扣款流程/quote_bid_contact_charge

search_add 主線通常不是扣款主線。若從 search_add trace 到 transaction,需先確認是否已進入後續 contact provider / narrow match accept / payment flow。

不回答什麼

  • 不重寫 wallet / card / TapPay / Stripe 的完整分支。
  • 不記單次付款 response 或 staging 測試 ID。
  • 不把 Redis lock 當成 transaction 狀態;Redis 只保護併發或短期狀態。

它連到哪些資料

  • users:交易 user,quote bid contact charge 通常是 provider payer。
  • quote_bidsclass = QuoteBidforeign_id 指向 bid。
  • quote_requests:可透過 quote bid 回到 request。
  • stripe_customers:card / auto quote customer 狀態。
  • TapPay / Stripe logs:金流實際 response 與 rec_trade_id / charge id。
  • application log:processWantToContactProviderlogQuoteBidTxn 是付款排查第一入口。

建議閱讀順序

  1. quote_bid_contact_charge
  2. Log channel 與 error code 索引
  3. quote_user_subscription_log_id
  4. 欄位/class.md
  5. 欄位/foreign_id.md
  6. 欄位/transaction_type_id.md
  7. 欄位/real_pay_date.md
  8. 欄位/stripe_charge_id.md
  9. 流程/quote_bid_contact_charge.md
  10. 關聯/quote_bids.md
  11. 關聯/users.md

核心觀念

transactions 不能只看是否有 row。要區分:

有 transaction row
已付款 transaction
未付款 / debt transaction
wallet transaction
card / TapPay / Stripe transaction

常用判讀:

real_pay_date 有值 -> 通常代表已付款
stripe_charge_id 有值 -> 可能是 Stripe charge id,也可能是 TapPay rec_trade_id
quote_bids.quote_user_subscription_log_id -> 指向相關 transaction id,但仍要看 real_pay_date 判斷付款狀態

Quote bid contact charge 主線

provider 正式 contact 一筆 quote / new lead 時,常見 trace 是:

QuoteBid::processWantToContactProvider()
-> Redis payoff lock
-> QuoteBid::processWantToContactCharge()
-> wallet / card / TapPay branch
-> Transaction::logQuoteBidTxn()
-> transactions
-> quote_bids.quote_user_subscription_log_id

重點:

  • Redis payoff lock 只保護 provider 同一時間扣款,不代表扣款成功。
  • 拿不到 lock、Redis 斷線、付款失敗、transaction guard 是不同層級。
  • processWantToContactProvider log 通常比 transactions 更接近根因。
  • logQuoteBidTxn log 代表流程已走到 transaction 寫入前後。
  • 沒有 transaction row 時,不要先判斷 transaction 壞掉;可能是 guard、付款方式或 Redis lock 先擋住。

什麼情況不會建立 transaction

常見不建立成功 transaction 的情況:

情況常見線索優先查
provider / request / bid guard 擋住API error、沒有 contact 狀態endpoint / business flow
provider 資格或封鎖限制error 690 / 251 / 256processWantToContactProvider log
wallet 不足且無 card / primeerror 10,要求設定信用卡processWantToContactProvider log
TapPay / card 失敗error 211 或 charge errorpayment provider log / application log
transaction amount 不合法logQuoteBidTxntransactions 寫入前 context
Redis lock 擋住重複扣款lock / retry logRedis lock key、application log

quote_user_subscription_log_id

quote_bids.quote_user_subscription_log_id 是 quote bid 對應交易紀錄的重要連結:

quote_bids.quote_user_subscription_log_id = transactions.id

但這個欄位不能單獨代表「付款成功」。

判斷時至少一起看:

transactions.real_pay_date
transactions.real_pay_amount
transactions.amount
transactions.transaction_type_id
transactions.stripe_charge_id
quote_bids.is_paid_for_subscription

stripe_charge_id

這個欄位名稱沿用舊 schema,不一定只存 Stripe charge id。

在 TapPay pay-by-prime 成功時,可能存:

TapPay rec_trade_id

所以看到 stripe_charge_id 有值時,要搭配 payment branch / provider log 判斷它是哪個 provider 的交易識別。

排查順序

付款或 contact 異常時,建議順序:

  1. 查 application log。
channel = processWantToContactProvider
channel = logQuoteBidTxn
channel = getCheckConnect
  1. quote_bids contact / transaction link。
SELECT
  id,
  provider_user_id,
  quote_request_id,
  is_want_to_contact_provider,
  contact_provider_on,
  is_paid_for_subscription,
  quote_user_subscription_log_id,
  total_site_fee,
  narrow_status_id,
  quote_status_id,
  provider_status_id
FROM quote_bids
WHERE id = <quote_bid_id>;
  1. transactions

常用 SQL

SELECT
  id,
  created,
  user_id,
  foreign_id,
  class,
  transaction_type_id,
  amount,
  real_pay_amount,
  real_pay_date,
  stripe_charge_id,
  wallet_balance
FROM transactions
WHERE class = 'QuoteBid'
  AND foreign_id = <quote_bid_id>
ORDER BY id DESC;
  1. 如果有 quote_user_subscription_log_id,也用 transaction id 反查一次:
SELECT
  id,
  created,
  user_id,
  foreign_id,
  class,
  transaction_type_id,
  amount,
  real_pay_amount,
  real_pay_date,
  stripe_charge_id,
  wallet_balance
FROM transactions
WHERE id = <quote_user_subscription_log_id>;
  1. 若牽涉 Redis lock,再回到 Redis 設定與 key pattern。

Redis 設定與用法來源見 Redis。Redis key 通常有 TTL,key 不存在不等於流程沒跑過。

適合優先補的文件

  • 欄位/real_pay_date.md:paid / unpaid 的判斷。
  • 欄位/stripe_charge_id.md:Stripe charge id 與 TapPay rec_trade_id 共用欄位的注意事項。
  • 流程/quote_bid_contact_charge.md:從 quote bid contact 到 transaction 的落點。
  • 關聯/quote_bids.md:bid 如何透過 quote_user_subscription_log_id 對回 transaction。