QuoteBids Change Status Rating
legacy rating 實際是「provider 送出報價 / 更新報價」,不是 consumer 評分。
快速流程
這支 API 是 provider 在案件聊天室送出報價。讀這份文件時可以先用下面流程理解,再往下看各 guard、付款與 queue 細節。
1. Route
/quote_bids/change_status/rating/{quote_bid_id}.json
-> QuoteBids::change_status_rating()
-> QuoteBid::processChangeStatusRating()
2. 前置檢查
確認操作者是 bid provider、電話已驗證、帳號未被 blocked / fraud / category limitation 擋下。
再確認 request 未關閉、未過期、未 hired,且 bid 尚未送出。
3. 判斷報價型態
is_contact_charge = true
-> 保證回覆 / contact charge 報價
is_contact_charge = false
-> 一般手動報價
4. 一般手動報價
檢查同 provider 是否已用其他 bid 報價、request 報價數是否達上限。
計算 site fee。
wallet 足額就立即扣 wallet。
wallet 不足但有 auto quote card / prime,就先寫 unpaid auto-quote debt,必要時立即刷卡。
更新 quote_bids 為 InProgress,並增加 quote_requests.quote_bid_count。
5. 保證回覆 / contact charge 報價
檢查 prime 或 provider auto quote card。
計算 contact charge fee,寫 quote_bid_site_fees。
更新 quote_bids 為 InProgress、is_auto_quote=1、is_contact_charge=1。
建立 quote_bid_calls。
6. 共用成功 side effect
更新報價內容、price_note、pricing fields、request unread state。
建立 SubmitQuote activity。
寫附件 message。
更新 quote template / last used template。
coupon fulfill。
更新 FETNET 綁定 consumer 的未讀 counter。
插入 Receive_Quote notification event。
插入 quote_bid_calc_counter task event。
插入 new_quote_bid_rating task event。
provider 第一筆報價時檢查 referral。對應入口
legacy 主要入口:
/quote_bids/change_status/rating/{quote_bid_id}.json
get-lancer-php56/app/Plugin/Quotes/Controller/QuoteBidsController.php::change_status($filter, $quote_bid_id)
filter = rating注意:
QuoteBidsController::rating()是另一個獨立 action,但 legacy 實際主線在change_status(..., rating, ...)。API 路由與遷移/API Migration/pro360_api_82/api_migration/quote_bids.md內的/quote_bids/rating.json不等於本文件的change_status/rating。
New API 現況
目前已搬主要 legacy 流程:
Lib/Common/RouterRule/Mapping.php已新增/quote_bids/change_status/rating/{id}.jsonEndpoint/V1/QuoteBids.php::change_status_rating()已新增QuoteBid::processChangeStatusRating()已新增tests/QuoteBidsChangeStatusRatingTest.php已覆蓋一般手動報價成功、wallet 足額扣款、wallet 不足寫 unpaid auto-quote debt、無卡 guard、default card foreign / retry / cooldown、prime bind、prime pay debt、prime foreign card、contact charge 成功、contact charge 無卡 guard、Receive_Quote 通知 queue、quote template / last used template、非 provider、已報價、request closedTapPayUtil已加CHANGE_STATUS_RATING_TEST_FAKE_TAPPAY_RESULT測試 hook,避免 PHPUnit 打 TapPay 外部服務。QuoteBid::sendReceiveQuoteNotification()已補 legacyReceive_Quoteevent,包含 MoEngage、email、push、web push、SMS;Life55688 / FETNET 分支也保留。- coupon fulfill、quote template 更新、FETNET unread、第一筆報價 referral check 已補。
新專案可重用的部分:
PreviewQuoteFeeUtil::getSiteFee():site fee 計算。QuoteBid::hasQuoteUserSubscriptionLog():避免同 bid 重複付款。QuoteBid::processWantToContactProvider()內有 wallet / card / prime / debt 相關邏輯,但它是 contact provider / narrow match 流程,不等價於 manual quote rating。TaskEventFactory已有new_quote_bid_rating與request_bid_count_limithandler。
待驗:
- contact charge core path 已有 PHPUnit;仍需要用 staging case 驗實際 provider/card/prime 資料下的 legacy response。
- 一般手動報價已覆蓋 fee=0 core path、wallet 足額扣款、wallet 不足但有 auto quote card 時先寫 unpaid debt。
- default card foreign / retry / cool-down 已有 fake charge PHPUnit;prime 綁卡、prime 付清 debt、prime foreign card 已有 fake TapPay PHPUnit。
Receive_Quotequeue payload 已有 PHPUnit;實際 worker 發信 / push / SMS 建議用 staging 真實 token 做端到端驗證。
主要輸入
| 來源 | 欄位 | 用途 |
|---|---|---|
| path | quote_bid_id | 目標 bid |
| form-data | price_note | 報價說明,也會被 abnormal keyword 檢查 |
| form-data | rating_type | flat / hourly / quote / required |
| form-data | flat_rate_at | 單次報價金額 |
| form-data | hourly_rate_at / hourly_rate_for | 時薪與工時 |
| form-data | prime | TapPay prime;用於綁卡或立即處理欠款 |
| form-data | is_contact_charge | 是否走保證回覆 / contact charge 報價 |
| form-data | last_used_template_id | 更新 provider 最近使用模板 |
| form-data | attachments | 報價附件,會寫入 message |
is_contact_charge 判斷需保留 legacy 相容規則:
- 如果 request 沒帶
is_contact_charge,沿用quote_bids.is_contact_charge == 1。 - 如果 request 帶
is_contact_charge=1,還要確認 category 支援 contact charge。 - 其他狀況視為一般手動報價。
前置 Guard
這些 guard 在分支前先做:
| 條件 | legacy error |
|---|---|
| bid 不存在、不是 provider、基本資料異常 | error=1, err_change_status_rating_1 |
quote_bids.is_can_not_quote = 1 | error=11, err_change_status_rating_3 |
| provider phone 未驗證 | error=6, err_change_status_rating_6 |
| blocked user | error=7, err_change_status_rating_7 |
| blocked user config / category limitation 擋下 | error=690, err_change_status_rating_690 |
| fraud tag | error=8, err_change_status_rating_7 |
| auto quote card 已失效 | error=11, err_change_status_rating_11 |
另有 abnormal 檢查:
AbnormalConfig::checkNewUser(..., change_status_rating)AbnormalConfig::checkListKeyword(..., change_status_rating)- 例外只寫
change_status_rating__checkNewUserlog,不會直接中止報價。
如果 consumer 端已移除此 bid,legacy 會在未 mute 的情況下嘗試 restore QuoteBidRemove。
分支一:保證回覆 / Contact Charge 報價
進入條件:is_contact_charge = true。
transaction 內主要規則:
SELECT quote_bids ... FOR UPDATE鎖 bid。- 若
is_auto_quote != 0、narrow_status_id != 0或已quote_sent_on,拒絕。 - 有
prime時,先處理 provider auto quote 欠款或綁卡。 - 沒有
prime且 provider 未開 auto quote card,拒絕。 - 檢查 request:
- archived / closed / expired 拒絕。
hired_count > 0拒絕。- bid 已送出或已有 subscription log 拒絕。
- 計算 site fee,寫
quote_bid_site_fees。 - 更新 bid 為保證回覆報價。
- 建立
quote_bid_calls,若已超出可通話 quota 則標成 out-of-quota。
成功時主要欄位:
| table | 欄位 |
|---|---|
quote_bids | quote_sent_on = now |
quote_bids | quote_status_id = InProgress |
quote_bids | provider_status_id = InProgress |
quote_bids | is_interested = 1 |
quote_bids | is_auto_quote = 1 |
quote_bids | is_contact_charge = 1 |
quote_bids | is_want_to_contact_provider = 0 |
quote_bids | total_site_fee, auto_quote_fee_reason |
quote_bid_site_fees | type = contact_charge |
quote_bid_calls | 建立 provider call row |
分支二:一般手動報價
進入條件:is_contact_charge = false。
transaction 內主要規則:
- 若 Redis
user_credit_card_just_use_{user_id}TTL 在 1 到 30 秒內,legacy 會 sleep 等待綁卡狀態穩定。 - 最多 retry 10 次。
SELECT quote_bids ... FOR UPDATE鎖 bid。- 若
is_auto_quote != 0、narrow_status_id != 0或已quote_sent_on,拒絕。 SELECT quote_requests ... FOR UPDATE鎖 request 名額欄位。- 同 provider 對同 request 已有其他
InProgress/Hiredbid,拒絕。 - 檢查 request:
- archived / closed / expired 拒絕。
hired_count > 0拒絕。
- 檢查 bid count limit:
- 一般用
quote_requests.quote_bid_count。 - 若
QuoteRequestLimitId::isLimit(request_id),要加上auto_quote_contact_count與contact_charge_contact_count。
- 一般用
- 若尚未有 subscription log,計算 site fee,並依 wallet / card 狀態建立付款紀錄。
- 先寫入
quote_sent_on與 bid 狀態,降低重複送出風險。 - 更新
quote_requests.quote_bid_count。 - 若達 bid limit,插入
task_event_queue.request_bid_count_limit。 - wallet 足額時立即扣 wallet,寫
transactions.QuotePaymentForSubmission與quote_user_subscription_logs.real_pay_date。 - wallet 不足但有 auto quote card /
prime時,先寫transactions.QuotePaymentForSubmissionAutoQuoteunpaid debt;只有達 debt 門檻或需立即扣款時才刷卡。
成功時主要欄位:
| table | 欄位 |
|---|---|
quote_bids | quote_sent_on = now |
quote_bids | quote_status_id = InProgress |
quote_bids | provider_status_id = InProgress |
quote_bids | is_interested = 1 |
quote_bids | is_paid_for_subscription = 1 |
quote_bids | is_contact_charge = 0 |
quote_bids | total_site_fee, auto_quote_fee_reason |
quote_requests | quote_bid_count 更新 |
transactions / quote_user_subscription_logs | wallet 足額為 paid submission;wallet 不足為 unpaid auto-quote debt |
報價內容更新
兩個分支成功後,還會共用後段報價內容更新。
固定會更新:
quote_bids.quote_last_update_onquote_bids.price_notequote_bids.provider_last_read_on = now + 3 secondsquote_bids.is_requestor_readed = 0quote_bids.is_quote_viewed = 0quote_requests.is_archived = 0quote_requests.is_requestor_readed = 0
rating_type 規則:
rating_type | 效果 |
|---|---|
flat | 寫 flat_rate_at、quote_amount、pricing_unit=單次 |
hourly | 寫 hourly_rate_at、quote_amount、pricing_unit=小時;有 hourly_rate_for 時用 rate * hours |
quote | is_on_site_visit_required_for_quote=1,關閉 book-it-now |
required | is_more_information_required=1,關閉 book-it-now |
每次更新前會重置多個 pricing 欄位,避免殘留舊報價模式:
flat_rate_athourly_rate_athourly_rate_forper_unit_atper_unit_forunits_ofquote_amountis_on_site_visit_required_for_quoteis_more_information_required
其他 side effect:
- 寫
quote_activities.SubmitQuote或UpdateQuote。 - 附件逐筆寫入 quote conversation message。
- 更新
users.last_used_template_id,必要時儲存quote_templates。 - 更新
quote_categories_quote_services.last_quote_sent_on。 - 更新 FETNET 綁定 consumer 的未讀 counter:若 request category 對應 lesson,增加
fetnet_users.unread_lesson;否則增加fetnet_users.unread_home。沒有fetnet_users綁定資料時不影響主流程。 - 新報價會送
sendReceiveQuoteNotification()。 - 新報價會插入
task_event_queue.new_quote_bid_rating。 - provider 第一筆報價可能觸發 referral check。
付款 / Prime 規則
prime 是 TapPay frontend 取得的一次性付款 token。legacy 在 rating 內使用它做兩種事:
- wallet 不足時,先寫 unpaid auto-quote debt。
- 如果累積 debt 達門檻或設定要求立即扣款,有
prime時用pay_by_prime付清 auto quote debt。 - 如果未達扣款門檻,有
prime時只做bind_card。 - 沒有
prime但有有效 auto quote card 時,先保留 unpaid debt,等後續 debt charge 流程處理。
注意:quote_user_subscription_logs.quote_bid_id 是 int,但新版 quote_bids.id 已可能超過 signed int 上限。測試機已觀察到 quote_bid_id=2147684586 寫入 subscription log 時,quote_user_subscription_logs.quote_bid_id 會變成 2147483647。因此排查 subscription log 時要優先用 quote_bids.quote_user_subscription_log_id -> quote_user_subscription_logs.id,不要只用 quote_user_subscription_logs.quote_bid_id 反查。
重要錯誤:
| 情境 | legacy error |
|---|---|
| 未設定信用卡 / 無法完成 card setup | error=10, err_change_status_rating_10 |
| 一般扣款失敗或餘額不足 | 常見 error=2, err_change_status_rating_2 |
| TapPay foreign card | error=211, err_change_status_rating_211 |
| credit card cool down | 回固定訊息「綁定信用卡準備中,請等30秒後再試」 |
| 未預期例外 | error=999, err_change_status_rating_999 |
回應可能包含:
bank_result_codebank_result_msg
Log / Queue
相關 application log channel:
change_status_rating
change_status_rating__checkNewUser
change_status_rating_is_allow_contact_charge_error
change_status__rating
QuoteBids_Rating
quote_bid_rating
rating_request_sent_count
dealUpdateCol_error1
dealUpdateCol_error2
quote_bid_rating_handler相關 queue / handler:
| event | handler / 用途 |
|---|---|
task_event_queue.quote_bid_calc_counter | QuoteBidCounterCacheHandler,重算 provider / request / service counter |
task_event_queue.new_quote_bid_rating | QuoteBidRatingHandler,更新平均回覆時間 |
task_event_queue.request_bid_count_limit | RequestBidCountLimitHandler,正式環境會處理 SEO request ES |
event_queue.Receive_Quote | 新報價通知:MoEngage Receive_Quote、email 110 New Quote Received Notification、push/web push push.content.project.quoted、SMS sms.receive_quote |
task_event_queue.request_sent_count | contact charge 轉保證回覆報價時更新 sent count |
測試機驗證紀錄
2026-05-11 測試機呼叫:
POST /quote_bids/change_status/rating/2147684586.json
is_contact_charge=0
rating_type=hourly
hourly_rate_at=109
price_note=哈哈囉囉囉ㄌresponse:
{"status":"Success","error":0,"hash_url":"","is_new":1,"minimum_transaction_amount":3}DB 對照結果:
| 類型 | 結果 |
|---|---|
quote_bids | quote_status_id=2, provider_status_id=2, quote_sent_on 已寫入,is_interested=1, is_paid_for_subscription=1, is_contact_charge=0, is_auto_quote=0 |
| 報價內容 | rating_type=hourly 對應 hourly_rate_at=109, quote_amount=109, pricing_unit=小時, price_note=哈哈囉囉囉ㄌ |
quote_requests | quote_bid_count=1, is_requestor_readed=0 |
| payment | transactions.transaction_type_id=40, amount=102, real_pay_date 已被 default card debt charge 補上,stripe_charge_id=D20260511XD6H38 |
| subscription log | quote_bids.quote_user_subscription_log_id=20534,但 quote_user_subscription_logs.quote_bid_id=2147483647,原因是欄位型別 int overflow |
| activity | quote_activities.SubmitQuote,provider_user_id=8617, requestor_user_id=8514, receiver_user_id=8617 |
| task queue | quote_bid_calc_counter 與 new_quote_bid_rating 已在 task_event_queue_log 成功 |
| notification | Receive_Quote 已在 event_queue_log_2 成功,包含 email / SMS / web_push / push / MoEngage |
| contact charge | quote_bid_site_fees、quote_bid_calls 沒有 row,符合 is_contact_charge=0 |
相關資料表
| 類型 | 資料表 |
|---|---|
| bid 狀態 / 報價內容 | quote_bids |
| request 名額 / 狀態 | quote_requests, quote_request_limit_ids |
| contact call | quote_bid_calls |
| 扣款 / 訂閱 log | transactions, quote_user_subscription_logs, stripe_customers, tap_pay_charge_logs |
| fee / coupon | quote_bid_site_fees, quote_coupons |
| activity / message | quote_activities, messages |
| queue | task_event_queue, task_event_queue_log, event_queue, event_queue_log_1..4 |
| 報價模板 / 統計 | quote_templates, quote_categories_quote_services, quote_categories |
建議切批
不要和 invoice / completed / self_hire 同一批。
建議拆成:
- 只補 route + endpoint skeleton,確認 routing 與錯誤格式。
- 搬前置 guard,先用 unit test 固定 provider / phone / blocked / request closed 行為。
- 搬一般手動報價成功 path,不含 contact charge。
- 搬付款與 retry path。
- 搬 contact charge path。
- 補通知、activity、message、template、last quote sent side effect。
建議測試矩陣
最小可先補:
- 一般手動報價成功。
- wallet 足額扣款成功。
- wallet 不足但有 auto quote card 時寫 unpaid auto-quote debt。
- 已有
quote_sent_on擋下。 - provider phone 未驗證。
- request closed / archived / expired。
- bid count limit reached。
- 同 provider 已用其他 bid 對同 request 報價。
- 一般手動報價無 wallet / 無有效 auto quote card。
- default card foreign card
error=211。 - default card retry / cooldown。
- contact charge 成功,含
quote_bid_calls。 - contact charge 無
prime且 provider 未開 auto quote card,回error=10。 prime綁卡 / 付欠款成功。- queue-or-log:
new_quote_bid_rating、新報價通知事件。