provider_accept_narrow_match

文件狀態:已對 legacy / 已對 new API / 主要路徑已實測
最後驗證:2026-05-07
來源:legacy QuoteBid::processProviderAcceptNarrowMatch、new QuoteBid::processProviderAcceptNarrowMatch、PHPUnit、staging curl、專案 migration detail

目的

provider_accept_narrow_match 是 provider 回應 consumer 送出的 narrow match invitation。

API:

POST /quote_bids/provider_accept_narrow_match/{quote_bid_id}/narrow_status_id:{status}.json

這支 API 有兩條路:

narrow status意義是否進扣款
2provider 接受 invitation是,會進 contact / charge 主線
3provider 拒絕 invitation否,只更新 narrow 狀態與 activity

重點:accept 不是只把 narrow_status_id 改成 accepted。成功 accept 會轉進 processWantToContactProvider(),因此會連動 quote_bids contact 欄位、transaction、activity、message、queue。

完整 narrow match 業務 trace 請先看 業務流程/narrow_match。這篇只回答 provider 回應 invitation 時,quote_bids 與相鄰資料表如何變化。

主流程簡圖

provider 呼叫 provider_accept_narrow_match
-> 驗 API key 與 provider session
-> 讀取 quote_bids / quote_requests / provider
-> 確認 bid 是 waiting narrow match
-> 若 REJECTED,只更新 narrow_status_id 與 NarrowProviderReject activity
-> 若 ACCEPTED,檢查 provider 資格 / 封鎖 / 電話驗證
-> 進入 processWantToContactProvider
-> 更新 quote_bids accepted/contact 狀態
-> provider wallet / card / TapPay prime 扣款
-> 寫 transaction / activity / message / queue
-> 通知 consumer
-> 回 success

核心 trace

新專案:

Endpoint/V1/QuoteBids.php::provider_accept_narrow_match()
-> Lib/Model/QuoteBid.php::processProviderAcceptNarrowMatch()
-> Lib/Model/QuoteBid.php::processWantToContactProvider()
-> Lib/Model/QuoteBid.php::processWantToContactCharge()
-> wallet / credit card / TapPay prime / transactions
-> quote_activities / messages / event_queue / task_event_queue

舊專案:

QuoteBidsController.php::provider_accept_narrow_match()
-> QuoteBid.php::processProviderAcceptNarrowMatch()
-> QuoteBid.php::processWantToContactProvider()
-> charge / wallet / transaction branch

processProviderAcceptNarrowMatch() accept path 的關鍵呼叫是:

$this->processWantToContactProvider(
    $requestorUserId,
    $quoteBidId,
    (string)($reason ?? ''),
    true,
    false,
    null,
    $prime
);

第四個參數代表 narrow match contact path。這也是為什麼 accept 成功後會看到 contact / charge side effect。

主要資料表

階段資料表重點欄位
判斷 bidquote_bidsid, provider_user_id, quote_request_id, is_narrow_match, narrow_status_id, quote_status_id
判斷 requestquote_requestsid, user_id, is_archived, is_closed, is_expired, hired_count, match_type, preserve_quote_service_id
判斷 providerusers / blocked_user_configs / blocked_chat_usersprovider phone、接案資格、封鎖 consumer
扣款users / stripe_customers / transactionswallet、auto quote card、class = QuoteBid, foreign_id = quote_bid_id
活動紀錄quote_activitiesNarrowProviderAccept, NarrowProviderReject, AutoQuoteContact, SendChat
訊息messages / message_contentsrequestor note conversation
非同步任務event_queue / event_queue_log_1..event_queue_log_4 / task_event_queue / task_event_queue_logconsumer notification、contact count、sent count

Accept 成功時的 quote_bids 變化

成功接受並完成 contact / charge 後,重點欄位會變成:

quote_bids.narrow_status_id = ACCEPTED
quote_bids.quote_status_id = InProgress
quote_bids.provider_status_id = InProgress
quote_bids.quote_sent_on = NOW()
quote_bids.is_want_to_contact_provider = 1
quote_bids.contact_provider_on = NOW()
quote_bids.is_paid_for_subscription = 1
quote_bids.quote_user_subscription_log_id = transactions.id

也會建立:

quote_activities.NarrowProviderAccept
quote_activities.AutoQuoteContact
transactions.class = QuoteBid
transactions.foreign_id = quote_bid_id
task_event_queue.bid_contact_count
task_event_queue.request_sent_count

一般 new leads 會通知 consumer:

event_queue.event_key = Receive_New_Leads_Quote

direct reserve 會改送:

event_queue.event_key = Accept_Direct_Reserve_New_Leads

direct reserve 判斷條件:

quote_requests.match_type = DIRECT_RESERVE
quote_requests.preserve_quote_service_id = quote_bids.quote_service_id

Reject 成功時的 quote_bids 變化

reject 不進付款、不建 transaction、不送 contact notification。

quote_bids.narrow_status_id = REJECTED
quote_activities.NarrowProviderReject

不應發生:

quote_bids.is_want_to_contact_provider = 1
transactions.class = QuoteBid
quote_activities.AutoQuoteContact
event_queue.Contact_Pro

扣款規則

付款主體是 provider,不是 consumer。provider 接受 invitation 後,系統把它視為「聯絡 / 接觸新客源」,因此進入 quote bid contact charge。

完整 wallet / card / TapPay prime / transaction 規則見 付款與扣款流程/quote_bid_contact_charge。這裡只記 provider accept narrow match 相關的 API mapping。

沒有帶 prime 時,如果 provider 既有付款能力不足,對外 response 會是:

{
  "error": 10,
  "status": "請先設定信用卡以進行報價",
  "bank_result_code": null,
  "bank_result_msg": null
}

prime 且 TapPay pay-by-prime 成功時,transaction 會被標記 paid;transactions.stripe_charge_id 會存 TapPay rec_trade_id

Guard 規則

accept / reject 共用前置 guard:

規則對應資料表 / 欄位
bid 必須存在quote_bids.id
呼叫者必須是 providerquote_bids.provider_user_id
request 不可封存 / 關閉 / 過期 / 已 hiredquote_requests.is_archived, is_closed, is_expired, hired_count
bid 必須是 narrow matchquote_bids.is_narrow_match = 1
bid 必須還沒正式聯絡quote_bids.quote_status_id = Send
invitation 必須 waitingquote_bids.narrow_status_id = WAITING

accept path 才會再檢查:

規則說明
provider 接案資格BlockedUserConfig::is_blocked_user()
category limitationQuoteCategoryLimitation::isPass()
certificate blockCertQualification::checkQualification()
provider 封鎖 consumerBlockedChatUser::isBlocked(provider, requestor)
provider 電話驗證provider is_phone_confirmed

排查順序

不要只看 API response。付款或 contact 異常時,先看 log,再看 DB state。完整扣款排查順序見 付款與扣款流程/quote_bid_contact_charge

  1. application log
channel = processWantToContactProvider
channel = logQuoteBidTxn
channel = error_in_consume_user_wallet
channel = TapPayUtil_pay_by_prime
  1. quote_bids
SELECT
  qb.id,
  qb.quote_request_id,
  qr.user_id AS requestor_user_id,
  qb.provider_user_id,
  qb.quote_service_id,
  qb.is_auto_quote,
  qb.is_narrow_match,
  qb.narrow_status_id,
  qb.quote_status_id,
  qb.provider_status_id,
  qb.is_want_to_contact_provider,
  qb.contact_provider_on,
  qb.quote_sent_on,
  qb.total_site_fee,
  qb.quote_user_subscription_log_id,
  qb.is_paid_for_subscription,
  qb.reason,
  qr.match_type,
  qr.preserve_quote_service_id
FROM quote_bids qb
INNER JOIN quote_requests qr ON qr.id = qb.quote_request_id
WHERE qb.id = <quote_bid_id>;
  1. transactions
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_activities
SELECT
  id,
  created,
  quote_activity_type_id,
  provider_user_id,
  requestor_user_id,
  receiver_user_id,
  secondary_foreign_id,
  param1,
  param2
FROM quote_activities
WHERE secondary_foreign_id = <quote_bid_id>
ORDER BY id DESC;
  1. queue / log

event_queue / task_event_queue 可能已被 worker 搬到 log table。驗 notification 或 task 時,要接受 queue 或 log 任一存在:

event_queue OR event_queue_log_1..event_queue_log_4
task_event_queue OR task_event_queue_log

常見錯誤 mapping

內部錯誤API error對外 status
foreign card 211211請使用國內發行的信用卡
charge error 1610請先設定信用卡以進行報價
qualification blocked 690690您的接案資格受到限制,請重新整理後再試
certification denied 251251您尚未完成接案資格
blocked chat 256256目前操作對象已遭您封鎖,解封後才能操作