QuoteBids Switch To Manual Quote
對應 legacy / web-app 實際 API:
POST /quote_bids/switch_to_manual_quote/{quote_bid_id}.jsonquote_bid_id 是 quote_bids.id。body 使用 FormData;只有需要 TapPay 時才帶 prime,不會用 JSON body 傳 quote_bid_id。
說明:legacy controller method 是 QuoteBidsController::switch_to_manual_quote($quote_bid_id),web-app 也以 path 傳入 RequestObject.QuoteBid.id。新版 routing 目前保留不帶 path id、改從 body 讀 quote_bid_id 的 fallback,但這不是 web-app 的正式呼叫格式。
功能說明
provider 在尚未聯絡 consumer 的 auto quote bid 上,選擇改成自己手動送出報價時會呼叫此 API。
成功時,這支 API 會把同一筆 quote_bids.id 從 auto quote 未聯絡狀態改成「一般手動報價已送出且已付款」狀態:
quote_bids.is_auto_quote改為0。quote_bids.is_want_to_contact_provider、is_paid_for_subscription改為1。quote_bids.is_contact_charge改為0。- 依手動報價規則重新計算
total_site_fee與auto_quote_fee_reason。 - 若原 auto quote fee 曾佔用 quote service category balance,且仍在可還原期間,會先還原 balance。
- 依付款結果扣 provider wallet,或建立 auto quote debt / card charge / TapPay
prime綁卡與扣款紀錄。 - 寫入
transactions與quote_user_subscription_logs,並把 subscription log id 回寫到 bid。 - 建立
SwitchToManualQuoteactivity,送出 consumer 收到報價 notification,並寫入 request counter 相關 queue。
若 provider 資格、bid 狀態、request 狀態、電話驗證、類別限制或付款檢查失敗,應回 legacy 對應 error,且不應把 bid 轉成已送出手動報價。
快速結論
- actor:provider session。
- new API:已補
Endpoint/V1/QuoteBids.php::switch_to_manual_quote()。 - routing:已補
Mapping.php,支援 legacy / web-app 實際呼叫的/quote_bids/switch_to_manual_quote/{quote_bid_id}.json;若 path 沒帶 id,action 也保留 bodyquote_bid_idfallback。 - request body:
FormData,只在有 TapPay prime 時帶prime。 - legacy controller:
QuoteBidsController::switch_to_manual_quote() - legacy domain:
QuoteBid::processSwitchToManualQuote() - 正式機流量:2026-05-27 查詢
ip-10-5-2-242access log,總計 934 筆。 - 優先度:高。這支有正式流量,且牽涉付款、報價、activity、notification 與 queue。
正式機 Access Log
2026-05-27 查詢機器:ip-10-5-2-242
查詢條件:
LOG_DIR=/var/log/apache2
ACCESS_LOG=get-lancer_access.log
PATTERN='quote_bids/switch_to_manual_quote'結果:
| 範圍 | 筆數 |
|---|---|
get-lancer_access.log + get-lancer_access.log.1 | 88 |
get-lancer_access.log.*.gz | 846 |
| total | 934 |
待補確認:
- path 分布。legacy method 有
$quote_bid_id參數,web-app 已確認使用/quote_bids/switch_to_manual_quote/{quote_bid_id}.json;可再用 access log 確認正式機是否還有非 web-app caller 使用其他 path。
查 path 分布建議:
{
sudo grep -Eho 'quote_bids/switch_to_manual_quote[^ ?"]*' "$LOG_DIR/$ACCESS_LOG" "$LOG_DIR/$ACCESS_LOG.1"
sudo find "$LOG_DIR" -maxdepth 1 -type f -name "$ACCESS_LOG.*.gz" -print0 \
| xargs -0 sudo zgrep -Eho 'quote_bids/switch_to_manual_quote[^ ?"]*'
} | sort | uniq -c | sort -nrLegacy Controller Rule
legacy 檔案:
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteBidsController.php:1777-1828QuoteBidsController::switch_to_manual_quote($quote_bid_id):
- 透過
.jsonroute / RequestHandler 進入 API response 流程,且需要 valid API session;request body 實際可為FormData,web-app 不送 JSON body。 - 從 session 取得 provider
user_id。 - 從 request data 取得
prime。 - 若 Redis key
user_credit_card_just_use_{user_id}存在,且 TTL 在 30 秒內,會 sleep 等待綁卡完成。 - 透過
QuoteBid::handleLockAndRetry()包住QuoteBid::processSwitchToManualQuote($user_id, $quote_bid_id, $prime)。 - 成功 response:
{"status":"success","error":0}ProExceptionresponse 會帶:- translated
status error- exception
extends bank_result_codebank_result_msg
- translated
Legacy Domain Rule
legacy 檔案:
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php:2668-3033主要 method:
QuoteBid::basicAutoQuoteChecking():2668-2774QuoteBid::processSwitchToManualQuote($user_id, $quote_bid_id, $prime = null):2781-3033
Guard
- 取得 provider payoff lock:
RedisUtil::acquirePayoffLock($user_id)。 basicAutoQuoteChecking($user_id, $quote_bid_id, true, true):- bid 必須存在,且不是 removed。
- actor 必須是 provider。
- provider / requestor / service 必須 active。
- bid 必須是 auto quote。
- bid 尚未聯絡 provider。
- provider 必須已電話驗證,否則 error 16。
- provider 不能被
BlockedUserConfig限制或 disable match,否則 error 690。 QuoteCategoryLimitation::isPass()必須通過,否則 error 690。- lock
quote_requests,檢查quote_bid_count < bid_count_limit,否則 error 15。 - request 不能 archived / closed / hired / expired,否則 error 3。
Payment / Bid Update
- 把本次計費視為手動報價:
quote_bid['QuoteBid']['is_auto_quote'] = 0後重新跑getSiteFee()。 - 若仍在 auto quote restore balance period,呼叫
restoreBalance()還原 quote service category balance。 - 呼叫
processCharge($quote_bid, $site_fee, 0, !empty($prime))。 - 若有 coupon discount,呼叫
QuoteCoupon::fulfillCoupon()。 - 更新
quote_bids:is_want_to_contact_provider = 1is_auto_quote = 0is_contact_charge = 0is_paid_for_subscription = 1quote_sent_on = nowquote_last_update_on = nowtotal_site_fee = site_feeauto_quote_fee_reason = json(site_fee_reason)
- 建立
SwitchToManualQuoteactivity。 - 呼叫
chargeByCreditCard($provider_user_id, $wait_for_charge_set, $prime)。 - commit 後呼叫
completeChargeByCreditCard()。
Retry / Failure
legacy 會 retry 最多 10 次。若付款相關錯誤為:
AutoRechargeFailedCreditNotEnoughRetryCreditCardCreditCardCoolDown
其中 RetryCreditCard 會 invalid 目前 auto quote card,若還有下一張 valid card 則 retry;若沒有可用卡,送 charge fail provider,最後回:
error = 4
status = err_switch_to_manual_quote_4
message = 請先設定信用卡以進行報價Post Commit Side Effect
成功付款主線後:
sendReceiveQuoteNotification($quote_bid),失敗不阻斷主流程。- 若是 Life55688 request,呼叫
Life55688Util::addEventExpertQuoting()。 QuoteCategoriesQuoteService::updateLastQuoteSentOn()。task_event_queue.request_sent_counttask_event_queue.request_requestor_read_count
New Project 搬移注意
- 不能只更新
quote_bids.is_auto_quote。這支的核心是付款 + 手動報價 side effect。 prime必須一路傳到付款 helper,代表 TapPay 一次性付款 token,不是 boolean。- response 要保留
bank_result_code/bank_result_msg。 - 可優先沿用已為
want_to_contact_provider/change_status_rating補齊的付款 helper,避免另寫一套 payment 行為。 - queue 驗證要接受 queue 或 log 任一存在。
New PHP 8.2
| new 檔案行號 | 對應 legacy | 職責 |
|---|---|---|
Lib/Common/RouterRule/Mapping.php:149-151 | access log path | 將 /quote_bids/switch_to_manual_quote/{quote_bid_id}.json route 到 QuoteBids::switch_to_manual_quote();也支援 path 不帶 id 時由 body 補。 |
Endpoint/V1/QuoteBids.php:3329-3416 | QuoteBidsController.php:1777-1828, AppModel.php:1081-1105 | session guard、quote_bid_id / prime 解析、Redis card bind wait、domain call、lock wait / deadlock retry、success/error response mapping。 |
Lib/Model/QuoteBid.php:2045-2170 | QuoteBid.php:2781-3033 | payoff lock、transaction、fee、payment、bid update、activity、post-commit side effect。 |
Lib/Model/QuoteBid.php:2217-2366 | QuoteBid.php:2668-2774, 2810-2885 | row lock 與 legacy guard 對齊;包含 quote_status_id != Removed。 |
Lib/Model/QuoteBid.php:2396-2430 | QuoteBid.php:2892-2900 | auto quote restore balance。 |
Lib/Model/QuoteBid.php:2432-2449 | QuoteBid.php:2931-2958 | payment exception 轉成 err_switch_to_manual_quote_4。 |
Lib/Model/QuoteBid.php:2816-2985, 1616-1659 | QuoteBid.php:2902, 2925-2971 | switch 專用付款 helper;處理 wallet、auto refill package、transaction、subscription log、prime 純綁卡 / pay-by-prime、default card debt charge、invalid card retry。 |
Lib/Model/QuoteCategoriesQuoteService.php:18-85 | QuoteCategoriesQuoteService.php:271-318 | 成功後更新 service-category last quote time;total_site_fee >= 500 時同步更新 data mart high value quote 欄位。 |
tests/QuoteBidsSwitchToManualQuoteTest.php:129-304 | success / already contacted / removed guard / prime bind only | 覆蓋 wallet 成功、已聯絡 guard、removed bid guard、prime 未達 debt threshold 時只綁卡並保留 unpaid debt。 |
逐段行數對照
controller / routing:
| Legacy 行號 | New 行號 | 對照內容 | 狀態 |
|---|---|---|---|
| access log path | Mapping.php:149-151 | route /quote_bids/switch_to_manual_quote/{quote_bid_id}.json 到 new action。 | 已對齊 |
QuoteBidsController.php:1777-1784 | Endpoint/V1/QuoteBids.php:3329-3341 | action 入口、JSON/API session guard、取得 provider user。 | 已對齊 |
QuoteBidsController.php:1793 | Endpoint/V1/QuoteBids.php:3343-3354, 3364-3368 | 取得 quote_bid_id 與 prime。new 同時支援 path id 與 body id。 | 已對齊 |
QuoteBidsController.php:1795-1803 | Endpoint/V1/QuoteBids.php:3355-3361 | Redis user_credit_card_just_use_{user_id} TTL 30 秒內等待綁卡完成。 | 已對齊 |
QuoteBidsController.php:1805-1814, AppModel.php:1081-1105 | Endpoint/V1/QuoteBids.php:3363-3380, 3411-3416 | 呼叫 domain method;遇到 lock wait / deadlock 最多 retry 3 次;成功回 {"status":"success","error":0}。 | 已對齊 |
QuoteBidsController.php:1815-1827 | Endpoint/V1/QuoteBids.php:3374-3399 | ProException response 保留 error/status/context,並帶 bank_result_code / bank_result_msg。 | 已對齊 |
domain 主流程:
| Legacy 行號 | New 行號 | 對照內容 | 狀態 |
|---|---|---|---|
QuoteBid.php:2791-2798 | Lib/Model/QuoteBid.php:2047-2053, 2167-2169 | provider payoff lock acquire / release。 | 已對齊 |
QuoteBid.php:2803-2808 | Lib/Model/QuoteBid.php:2060-2065, 2216-2255 | 開 transaction,鎖定 bid row,讀 bid/request/provider/requestor/service/category data。 | 已對齊 |
QuoteBid.php:2668-2774 | Lib/Model/QuoteBid.php:2262-2315 | basicAutoQuoteChecking:bid 存在、quote_status_id != Removed、provider owner、active user/service、is_interested、auto quote、尚未聯絡。 | 已對齊;2026-05-27 補 removed guard regression |
QuoteBid.php:2810-2842 | Lib/Model/QuoteBid.php:2317-2347 | provider phone confirmed、blocked user、disable match、category limitation。 | 已對齊 |
QuoteBid.php:2844-2854 | Lib/Model/QuoteBid.php:2349-2354 | request 報價人數上限。 | 已對齊 |
QuoteBid.php:2866-2885 | Lib/Model/QuoteBid.php:2356-2365, 2372-2391 | request archived / closed / hired / expired guard,並建立 fee helper 需要的 request shape。 | 已對齊 |
QuoteBid.php:2888-2890 | Lib/Model/QuoteBid.php:2067-2078 | 將本次計費視為 manual quote,重新計算 site fee / fee reason。 | 已對齊 |
QuoteBid.php:2892-2900 | Lib/Model/QuoteBid.php:2080, 2396-2430 | auto quote restore balance period 內還原 balance,並寫 restore activity。 | 已對齊 |
QuoteBid.php:2902, 2925-2927, 2971 | Lib/Model/QuoteBid.php:2082-2094, 2816-2985, 1616-1659 | 付款主線:wallet / auto refill package / transaction / subscription log / card / prime 純綁卡或 pay-by-prime / default card debt charge。 | 已對齊 legacy threshold;外部金流成功與失敗仍需 staging 實測 |
QuoteBid.php:2904-2910 | Lib/Model/QuoteBid.php:2096 | coupon fulfill。 | 已對齊 |
QuoteBid.php:2912-2920 | Lib/Model/QuoteBid.php:2098-2108 | 更新 bid flags、quote time、total site fee、fee reason。 | 已對齊 |
QuoteBid.php:2922-2923 | Lib/Model/QuoteBid.php:2110-2118 | 建立 SwitchToManualQuote activity。 | 已對齊 |
QuoteBid.php:2931-2958 | Lib/Model/QuoteBid.php:2089-2090, 2432-2449, 1616-1659 | 付款錯誤轉 error=4, status=err_switch_to_manual_quote_4;default card debt charge 會 retry invalid card。 | 已搬移;多卡實際刷卡 fallback 仍需 staging 實測 |
QuoteBid.php:2973-2975 | Lib/Model/QuoteBid.php:2133-2140 | sendReceiveQuoteNotification 失敗不阻斷主流程。 | 已對齊 |
QuoteBid.php:2977-2997 | Lib/Model/QuoteBid.php:2142 | Life55688 expert quoting event。 | 已對齊,實作共用既有 helper |
QuoteBid.php:2999-3007, QuoteCategoriesQuoteService.php:271-318 | Lib/Model/QuoteCategoriesQuoteService.php:18-85, Lib/Model/QuoteBid.php:2144-2150 | 更新 quote_categories_quote_services.last_quote_sent_on;高價 quote 同步更新 data mart。 | 已對齊 |
QuoteBid.php:3009-3027 | Lib/Model/QuoteBid.php:2152-2166 | 寫入 request_sent_count、request_requestor_read_count task event,失敗只記 log。 | 已對齊 |
目前文件判斷:
- 已有逐段行數對照,之後 review 應直接用本節檢查,不只看上面的流程摘要。
- 2026-05-27 已修正:removed bid guard、lock retry、switch payment 改走完整 charge path、high value data mart update。
prime未達 debt threshold 的 bind-only path、達 threshold 的 pay-by-prime path、foreign card rollback、wallet 不足且無卡 error 4 已補 PHPUnit;多卡 invalid fallback 仍需 staging 或可控 fake fixture 實測。
Staging 實測
2026-05-28:web-app 舊呼叫格式
呼叫:
POST https://api-staging.pro360.com.tw/quote_bids/switch_to_manual_quote/2147689774.json
Content-Type: multipart/form-data
body: empty FormDataresponse:
{"status":"success","error":0}DB 連線:
{"db_name":"getlancer","db_host":"ip-172-23-2-24","db_port":"3306"}主狀態:
quote_bids.id = 2147689774
quote_request_id = 22206
provider_user_id = 8617
quote_status_id = 2
provider_status_id = 2
is_auto_quote = 0
is_want_to_contact_provider = 1
is_paid_for_subscription = 1
is_contact_charge = 0
is_restore_balance = 1
total_site_fee = 160
quote_user_subscription_log_id = 20799
quote_sent_on = 2026-05-28 03:40:55
quote_last_update_on = 2026-05-28 03:40:55
auto_quote_fee_reason = {"8":{"reason":"Normal","discount":1}}付款 / subscription log:
transactions.id = 36840
transaction_type_id = 40
amount = 160
real_pay_date = 2026-05-28 03:40:56
stripe_charge_id = D20260528W1HImO
quote_bids.quote_user_subscription_log_id = 20799
quote_user_subscription_logs.id = 20799
amount = 160
is_paid = 1同 provider 原本已有一筆 unpaid auto quote debt:
transactions.id = 36839
amount = 176
real_pay_date = 2026-05-28 03:40:56
stripe_charge_id = D20260528W1HImO這表示 empty FormData / 無 prime 的舊呼叫走了「已有有效自動報價卡片,刷 auto quote debt」路徑;本次 160 與既有 176 一起完成付款,符合 legacy processCharge() + chargeByCreditCard() 的行為。
side effect:
quote_activities.quote_activity_type_id = 52 Auto Quote Restore Balance
quote_activities.quote_activity_type_id = 54 Switch to Manual Quote
event_queue_log_4.event_key = Receive_Quote
task_event_queue_log.event_key = request_sent_count
task_event_queue_log.event_key = request_requestor_read_count注意:quote_user_subscription_logs.quote_bid_id 是 signed int,遇到 2147689774 這種大 id 會寫成 2147483647 上限值;驗證 subscription log 必須用 quote_bids.quote_user_subscription_log_id -> quote_user_subscription_logs.id,不要用 quote_user_subscription_logs.quote_bid_id 當唯一反查依據。
Verification
測試指令:
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteBidsSwitchToManualQuoteTest.php2026-05-28 結果:
OK (7 tests, 75 assertions)覆蓋:
- provider 可透過
/quote_bids/switch_to_manual_quote/{quote_bid_id}.json將 auto quote 轉 manual quote。 - wallet 足額時扣 wallet、寫
transactions.class = QuoteBid/transaction_type_id = 33、建立 subscription log。 - 更新
quote_bids.is_auto_quote = 0、is_want_to_contact_provider = 1、is_paid_for_subscription = 1、quote_sent_on、quote_last_update_on、total_site_fee。 - 建立
SwitchToManualQuoteactivity。 - 寫入
request_sent_count/request_requestor_read_counttask event。 - 已聯絡 bid 保留 legacy guard:
error = 0,status = err_switch_to_manual_quote_6。 - removed bid 保留 legacy not-found guard:
error = 1,status = err_switch_to_manual_quote_1,不寫 transaction / subscription log。 prime且 debt 未達 threshold 時只 bind card,transaction 保持 unpaid,對齊 legacychargeByCreditCard()amount = 0 行為。prime且 debt 達 threshold 時走 TapPay pay-by-prime,transaction 會補real_pay_date/stripe_charge_id。primeforeign card failure 回error = 211,status = err_change_status_rating_211,transaction / subscription log / bid update rollback。- wallet 不足、無有效 auto quote card、無
prime時回error = 4,status = err_switch_to_manual_quote_4,不寫 transaction。
額外驗證:
tests/QuoteBidsChangeStatusRatingTest.php已補 fixture debt 隔離與測試期間有效 auto quote card 準備:tearDown 後還原,避免本機 baseline debt / card 狀態影響 payment guard。tests/QuoteBidsChangeStatusRatingTest.php重跑結果:OK (15 tests, 144 assertions)。
建議測試矩陣
- provider wallet 足夠,無
prime,成功。 - wallet 不足,無
prime,回 error 4。 - wallet 不足,有
prime,TapPay pay-by-prime 成功。 - 有
prime但 TapPay foreign card / charge fail,確認 response mapping 與 rollback。 - provider 未電話驗證,回 error 16。
- request closed / expired / hired,回 error 3。
- bid 已聯絡,回 existing contact guard。