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 | 意義 | 是否進扣款 |
|---|---|---|
2 | provider 接受 invitation | 是,會進 contact / charge 主線 |
3 | provider 拒絕 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 branchprocessProviderAcceptNarrowMatch() accept path 的關鍵呼叫是:
$this->processWantToContactProvider(
$requestorUserId,
$quoteBidId,
(string)($reason ?? ''),
true,
false,
null,
$prime
);第四個參數代表 narrow match contact path。這也是為什麼 accept 成功後會看到 contact / charge side effect。
主要資料表
| 階段 | 資料表 | 重點欄位 |
|---|---|---|
| 判斷 bid | quote_bids | id, provider_user_id, quote_request_id, is_narrow_match, narrow_status_id, quote_status_id |
| 判斷 request | quote_requests | id, user_id, is_archived, is_closed, is_expired, hired_count, match_type, preserve_quote_service_id |
| 判斷 provider | users / blocked_user_configs / blocked_chat_users | provider phone、接案資格、封鎖 consumer |
| 扣款 | users / stripe_customers / transactions | wallet、auto quote card、class = QuoteBid, foreign_id = quote_bid_id |
| 活動紀錄 | quote_activities | NarrowProviderAccept, NarrowProviderReject, AutoQuoteContact, SendChat |
| 訊息 | messages / message_contents | requestor note conversation |
| 非同步任務 | event_queue / event_queue_log_1..event_queue_log_4 / task_event_queue / task_event_queue_log | consumer 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_Quotedirect reserve 會改送:
event_queue.event_key = Accept_Direct_Reserve_New_Leadsdirect reserve 判斷條件:
quote_requests.match_type = DIRECT_RESERVE
quote_requests.preserve_quote_service_id = quote_bids.quote_service_idReject 成功時的 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 |
| 呼叫者必須是 provider | quote_bids.provider_user_id |
| request 不可封存 / 關閉 / 過期 / 已 hired | quote_requests.is_archived, is_closed, is_expired, hired_count |
| bid 必須是 narrow match | quote_bids.is_narrow_match = 1 |
| bid 必須還沒正式聯絡 | quote_bids.quote_status_id = Send |
| invitation 必須 waiting | quote_bids.narrow_status_id = WAITING |
accept path 才會再檢查:
| 規則 | 說明 |
|---|---|
| provider 接案資格 | BlockedUserConfig::is_blocked_user() |
| category limitation | QuoteCategoryLimitation::isPass() |
| certificate block | CertQualification::checkQualification() |
| provider 封鎖 consumer | BlockedChatUser::isBlocked(provider, requestor) |
| provider 電話驗證 | provider is_phone_confirmed |
排查順序
不要只看 API response。付款或 contact 異常時,先看 log,再看 DB state。完整扣款排查順序見 付款與扣款流程/quote_bid_contact_charge。
- application log
channel = processWantToContactProvider
channel = logQuoteBidTxn
channel = error_in_consume_user_wallet
channel = TapPayUtil_pay_by_primequote_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>;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;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;- 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 211 | 211 | 請使用國內發行的信用卡 |
charge error 16 | 10 | 請先設定信用卡以進行報價 |
qualification blocked 690 | 690 | 您的接案資格受到限制,請重新整理後再試 |
certification denied 251 | 251 | 您尚未完成接案資格 |
blocked chat 256 | 256 | 目前操作對象已遭您封鎖,解封後才能操作 |