users 狀態變動

文件狀態:草稿 / 已補 provider payer 排查視角
最後驗證:2026-05-07
來源:quote bid contact charge trace、provider_accept_narrow_match migration / staging 驗證、Common Process Documents

目的

users 是身份主表,但不應寫成所有 user 相關資訊的大雜燴。這裡只整理跨流程會影響 request / bid / payment / notification 的身份狀態。

完整 search_add trace 請先看 業務流程/search_add;本資料夾只保留 actor / requestor / provider / payer 的身份視角。

這份文件目前優先回答 quote bid contact charge 與 narrow match accept 裡最容易混淆的問題:

誰是登入者?
誰是 consumer / requestor?
誰是 provider?
誰被扣款?
扣款失敗時要回頭看 users 的哪些狀態?

相關業務流程

  • users_delete:使用者刪除帳號時,users 停用、email / phone 加 DEL{timestamp}_ prefix、服務/案件歸檔、session 清除與 downstream queue。

不回答什麼

  • 不整理 users 全欄位定義。
  • 不記錄單次 staging 測試 user id、session token 或付款 token。
  • 不把 provider 資格、封鎖、錢包、卡片全部塞進 users;這些通常還要看關聯表與 application log。
  • 不用 users 單表判斷付款成功;付款成功要回到 transactions 與 quote bid contact 狀態。

使用者在不同流程可能扮演:

角色常見關聯
requestor / consumerquote_requests.user_id, message sender, notification receiver
provider / proquote_bids.provider_user_id, quote_services.user_id, transaction payer
session actorAPI session token 對應 user
payerquote bid contact charge 時通常是 provider,也就是 transactions.user_id
notification receiver依事件不同可能是 consumer 或 provider

它連到哪些資料

  • user_profiles:姓名、電話等 profile 資料。
  • api_sessions:API session token 與 device。
  • quote_requests:requestor。
  • quote_services:provider service owner。
  • quote_bids:provider / requestor bid context。
  • transactions:付款主體通常是 provider user。
  • stripe_customers:provider card / auto quote card。
  • blocked_user_configs / blocked_chat_users:接案資格、封鎖與互動限制。

Provider payer 視角

quote bid contact charge 的付款主體是 provider,不是 consumer。

例如 provider_accept_narrow_match 成功 accept 後,流程會轉進 quote bid contact charge。這時 consumer 是邀請者 / requestor,但實際付費接觸新客源的是 provider:

API session token
-> session actor user id
-> 必須等於 quote_bids.provider_user_id
-> provider 正式 contact quote request
-> users.available_wallet_amount / provider card / TapPay prime
-> transactions.user_id = provider user id
-> transactions.class = QuoteBid
-> transactions.foreign_id = quote_bid_id

對照 requestor:

quote_requests.user_id = consumer / requestor
quote_bids.provider_user_id = provider / payer
transactions.user_id = provider / payer

所以排查付款時,不要從 quote_requests.user_id 去查 wallet 或 card。應該先從 quote_bids.provider_user_id 找 provider,再查 provider 的 wallet / card / qualification。

常見流程中的身份語意

流程session actorrequestor / consumerproviderpayer
search_add建立需求者或匿名/session userquote_requests.user_id可能由 match 產生 bid通常無同步扣款
consumer_want_provider_quoteconsumer / request ownerquote_requests.user_idquote_bids.provider_user_id無,這是邀請 provider 報價
provider_accept_narrow_matchproviderquote_requests.user_idquote_bids.provider_user_idprovider
want_to_contact_provider依入口而定quote_requests.user_idquote_bids.provider_user_idprovider contact charge 主線中是 provider

consumer_want_provider_quoteprovider_accept_narrow_match 容易被混在一起:

consumer_want_provider_quote
-> consumer 邀請 provider 報價
-> 不扣 provider 款
 
provider_accept_narrow_match
-> provider 接受 invitation
-> 成功後進 contact charge
-> provider 是 payer

Provider payer 要看的 users 狀態

排查 provider 付款 / contact 失敗時,users 主要提供幾種線索:

線索常見欄位 / 關聯用途
actor 是否正確api_sessions.user_id, users.idsession user 是否為 bid provider
provider 基本資格users.is_phone_confirmed, user_profiles.phonephone / profile guard 是否可能擋 contact
provider walletusers.available_wallet_amountwallet 是否足以支付 quote bid fee
provider cardstripe_customerswallet 不足時是否有可用 card / auto quote payment method
provider 封鎖 / 限制blocked_user_configs, blocked_chat_users, qualification tables是否因資格或封鎖無法聯繫 consumer

這些狀態通常不能單獨解釋 API response。付款與 contact 失敗要先看 application log:

channel = processWantToContactProvider
channel = logQuoteBidTxn
channel = getCheckConnect

再回查 usersquote_bidstransactions

常見錯誤與 users 關聯

Response / internal code常見意義users/provider payer 視角
3呼叫者不是 providersession actor 不等於 quote_bids.provider_user_id
10無可用付款方式 / 要求設定信用卡provider wallet 不足,且沒有可用 card / prime
211外國卡或不支援卡片provider card / TapPay branch 失敗
251provider 尚未完成接案資格provider qualification / certification 不符合
256provider 封鎖 consumerprovider 與 requestor 的 block/chat restriction
690provider 目前無法聯繫provider 資格、phone、狀態或其他 guard

API response 的 error 是對外包裝。相同 error=10 背後可能是 wallet 不足、沒有 card、沒有 prime、金流 provider 拒絕或 transaction 前置 guard。應以 processWantToContactProvider log 的 context 為準。

建議閱讀順序

  1. quote_bid_contact_charge
  2. Log channel 與 error code 索引
  3. transactions
  4. provider_accept_narrow_match
  5. narrow_match
  6. 角色.md
  7. 欄位/is_phone_confirmed.md
  8. 欄位/available_wallet_amount.md
  9. 關聯/api_sessions.md
  10. 關聯/user_profiles.md
  11. 關聯/stripe_customers.md
  12. 關聯/blocked_user_configs.md
  13. 關聯/blocked_chat_users.md

核心觀念

同一個 users.id 在不同流程的語意不同。排查前先確認它是:

session actor
requestor
provider
transaction payer
notification receiver

例如 provider_accept_narrow_match 中:

session actor = provider
quote_bids.provider_user_id = provider
quote_requests.user_id = requestor
transactions.user_id = provider payer

排查順序

provider payer 相關問題建議照這個順序,不要先從 users 單表猜原因。

  1. 先看 application log。
channel = processWantToContactProvider
channel = logQuoteBidTxn
channel = getCheckConnect
  1. 確認 bid 的 requestor / provider。
SELECT
  qb.id,
  qb.provider_user_id,
  qb.quote_request_id,
  qb.quote_service_id,
  qb.is_want_to_contact_provider,
  qb.contact_provider_on,
  qb.total_site_fee,
  qb.quote_user_subscription_log_id,
  qr.user_id AS request_user_id,
  qr.is_closed,
  qr.is_expired,
  qr.hired_count
FROM quote_bids qb
INNER JOIN quote_requests qr ON qr.id = qb.quote_request_id
WHERE qb.id = <quote_bid_id>;
  1. provider_user_id 查 provider user。

查資料前先依 repo 規則 SHOW COLUMNS,不要猜欄位。常見會看的欄位:

SELECT
  id,
  is_phone_confirmed,
  available_wallet_amount
FROM users
WHERE id = <provider_user_id>;
  1. 查 provider profile / card / block / qualification。

這些表的欄位差異比較容易踩錯,先查 schema 再查資料:

user_profiles
stripe_customers
blocked_user_configs
blocked_chat_users
provider qualification / certification related tables
  1. 查交易是否成立。
SELECT
  id,
  created,
  user_id,
  class,
  foreign_id,
  amount,
  real_pay_amount,
  real_pay_date,
  stripe_charge_id
FROM transactions
WHERE class = 'QuoteBid'
  AND foreign_id = <quote_bid_id>
ORDER BY id DESC;

判讀重點:

transactions.user_id 應該是 provider_user_id
transactions.foreign_id 應該是 quote_bid_id
real_pay_date 有值通常才代表已付款
stripe_charge_id 可能是 Stripe charge id,也可能是 TapPay rec_trade_id

手動驗證提醒

如果用 staging curl 驗 provider_accept_narrow_match

沒有 prime
-> 代表這次 request 沒帶 TapPay 一次性付款 token
-> 若 provider wallet 不足且沒有可用 card,可能回 error=10 要求設定信用卡
 
有 prime
-> 代表本次 request 帶 TapPay token
-> 成功時可進 TapPay pay-by-prime,交易識別可能寫入 transactions.stripe_charge_id

第一次 no-prime 失敗和第二次帶 prime 成功,可以發生在同一筆 bid 的前後兩次 request。排查時要用時間序確認前一次失敗沒有留下 accepted/contact/transaction 成功狀態,後一次成功才更新 bid 與 transaction。

適合優先補的文件

  • 角色.md:requestor / provider / payer / receiver 的判讀。
  • 欄位/is_phone_confirmed.md:報價與接案流程的電話驗證 guard。
  • 欄位/available_wallet_amount.md:wallet 扣款前後如何判斷。
  • 關聯/api_sessions.md:session token 如何決定 actor。
  • 關聯/stripe_customers.md:provider card 與 TapPay / Stripe 扣款能力。