SearchAdd API 契約
對應入口:
- SearchAdd.php
- Router mapping: Mapping.php maps
/quote_requests/search_add.jsontoV1/SearchAdd.php
Endpoint
POST /quote_requests/search_add.json
Request 組成
公開 request 主要分成四塊:
User[...]- requestor 身分相關欄位
- 這一塊負責讓系統判斷:
- 是既有 user
- 要登入既有帳號
- 還是要走 auto signup
- 常見內容包含:
emailphonepasswordnamedevice_idverify_code
- 這一塊的結果會直接影響:
- 最終
user_id session_id- request 掛到哪個 user 底下
- 最終
QuoteRequest[...]- request 主體資料
- 主要描述:
- 這張單是什麼需求
- 要服務哪個 category
- 客戶的基本聯絡與地區資訊
- 是否指定特殊配對模式
- 常見內容包含:
quote_category_idtitledescriptionphone_noaddresszip_codematch_typequote_service_idlanding_page_url
- 這一塊也會影響:
- 是否進一般 auto match
- 是否進 preserve / authorized / direct reserve / direct user 等特殊流程
Form[...]- 各 category form 的實際答案
- 這一塊承載的是需求細節,不同 category 會完全不同
- 例如:
- 服務地區
- 上課方式
- 時段
- 規格
- 預算或補充條件
- 這些答案後續會影響:
- form submission 寫表
- request summary
- language summary
- auto quote match 條件
FormTime[...]/FormSummary- form 的顯示順序與摘要資訊
FormTime[...]- 比較偏前端題目順序、顯示上下文、保留原始表單脈絡
FormSummary- 比較偏 legacy 相容摘要,讓 request 建立後可回填可讀 summary
- 這一塊不是主要業務判斷入口
- 但它會影響:
- request 建立後的摘要欄位
- 後台與 legacy 流程使用的 summary payload
Request 範例
一般建單
這是一個最小可讀的 request 範例,重點是讓人看懂 payload 是如何攤平成 User[...]、QuoteRequest[...]、Form[...]。
{
"User[email]": "demo@example.com",
"User[phone]": "0912345678",
"User[name]": "測試使用者",
"User[device_id]": "demo-device-001",
"QuoteRequest[quote_category_id]": "280",
"QuoteRequest[title]": "英文家教",
"QuoteRequest[description]": "需要成人英文口說課程",
"QuoteRequest[phone_no]": "0912345678",
"QuoteRequest[address]": "台北市大安區",
"QuoteRequest[zip_code]": "106",
"QuoteRequest[match_type]": "1",
"QuoteRequest[landing_page_url]": "/course-landing",
"Form[6766]": "成人英文",
"Form[6767]": "每週一次",
"Form[6768]": "一對一",
"FormSummary": "{\"需求類型\":\"成人英文\",\"頻率\":\"每週一次\",\"上課方式\":\"一對一\"}"
}這種 request 通常代表:
- 用
User[...]先確定 requestor - 用
QuoteRequest[...]建 request 主體 - 用
Form[...]補 category-specific 細節 match_type = 1代表走一般主線,不是特殊指定流程
指定 provider 的 Authorized request
這個範例的重點是:
match_type = Authorize- 帶
quote_service_id
表示這張 request 不是走一般 auto match,而是指定某位 provider / service。
{
"User[email]": "demo@example.com",
"User[phone]": "0912345678",
"User[name]": "測試使用者",
"QuoteRequest[quote_category_id]": "280",
"QuoteRequest[title]": "英文家教",
"QuoteRequest[description]": "希望直接由指定老師接案",
"QuoteRequest[phone_no]": "0912345678",
"QuoteRequest[address]": "台北市信義區",
"QuoteRequest[zip_code]": "110",
"QuoteRequest[match_type]": "2",
"QuoteRequest[quote_service_id]": "3301",
"Form[6766]": "成人英文",
"Form[6767]": "每週一次",
"FormSummary": "{\"需求類型\":\"成人英文\",\"頻率\":\"每週一次\"}"
}這種 request 通常代表:
- request 主體仍然照一般方式建立
- 但後續不走一般 provider pool match
- 而是先對指定的
quote_service_id建 bid,再進 authorized 後處理
authorized 同步後處理目前對齊的重點是:
- 入口改走
QuoteBid::processWantToContactProvider() - 同步完成 bid contact state
- 同步完成付款判斷
- wallet
- debt only
- immediate charge
- auto refill
- 同步補:
quote_user_subscription_log_idAutoQuoteContactContact_ProAuto_Charge_Fail- receipt event
- fail-switch to new leads
Success Response
成功時主體為:
{
"error": 0,
"message": "Success",
"user_id": 123,
"username": "user@example.com",
"session_id": "token",
"QuoteRequest": { "...": "..." },
"QuoteBid": [{ "...": "..." }]
}username 欄位不應視為 search_add 的穩定契約欄位。
- 舊版 controller 實作實際回的是 request 流程中的
$user_email - 因此會依登入路徑與 request payload 而變動
- 既有 session 路徑若未補到 email,可能回
null - credential / signup 路徑則可能回 email
做 legacy 對照時,不要單獨以 username 是否為 null 當成主流程 parity 判斷依據;優先看:
user_idsession_idQuoteRequestQuoteBid
Error Response
對外仍維持 legacy 風格:
errormessage
內部錯誤鍵值不作為 public contract 對外保證。
同步與非同步邊界
這是 search_add 最容易被誤解的地方。
同步 response 只保證:
- request 已建立
- request-side writers 已完成
- 本輪 auto / narrow / finalize 已完成
- 部分特殊流程已補寫
同步 response 不保證:
- legacy manual / related fallback 已經跑完
newQuoteRequesttask 已經完成- 所有後續 downstream task 都已反映在
QuoteBid
如果要確認 manual / related flow,請看 async_fallbacks.md。
對串接端的重要提醒
- 不要用「當次 response 沒有 manual bid」直接判定整體配對失敗
QuoteBid代表的是同步主線當下可見結果- 真正的 legacy parity 需要連同後續 task 一起看