QuoteServices External Review Submit

API:

POST /quote_services/external_review/submit.json

功能說明

consumer 透過 /review/service/{quote_service_id}/{hash} 外部評價頁提交評價時呼叫。前端呼叫鏈是 ReviewContainer.reduceData()serviceActions.postExternalReview()APIManager.rateServiceExtenal()

目前狀態

已補 action、routing 與 PHPUnit。

正式機流量

2026-06-04 查正式機 access log:

125 POST    /quote_services/external_review/submit.json 200
 82 OPTIONS /quote_services/external_review/submit.json 200

同一流程的讀取端 GET /quote_services/external_review/link/{quote_service_id}.json 已另行搬移。

Legacy Rule

legacy controller:

/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteServicesController.php

已確認規則:

  1. 預設 response 是 {"error":1,"message":"Invalid session"}
  2. 若有有效 session,使用 session user;此時忽略 User[...] 註冊 / 登入欄位。
  3. 若無有效 session,會走 _getUserIdFromInput(),用 User[email]User[password]User[device_id] 等欄位登入或自動建立 consumer user。
  4. 必須有 QuoteFeedback input,至少包含:
    • QuoteFeedback[hash]
    • QuoteFeedback[is_satisfied]
    • QuoteFeedback[feedback]
  5. SecurityHash::fulfill('QuoteService', hash) 找不到時回 {"error":4,"message":"Invalid hash"}
  6. security_hashes.user_id 等於提交者 user id,回 {"error":5,"message":"Cannot review the same user"}
  7. 成功時會:
    • 新增 quote_feedbacks
    • 更新被評價 provider 的 user feedback stats
    • 建立 AddReview activity,param1 為原始 QuoteFeedback JSON
    • {"error":0,"message":"Success"},若是無 session 自動登入 / 註冊路徑,會附 session_id

新舊程式對照

detail doc 必須保留實際行號,避免只看 controller 入口但漏掉 user resolve、activity 或統計 helper。

Legacy PHP 5.6

legacy controller:

/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteServicesController.php
legacy 行號職責
3571-3575external_review('submit') dispatch 到 _submit_external_review()
3620-3628_submit_external_review() 入口;預設 Invalid session,有有效 session 時使用 session user。
3629-3638無有效 session 時呼叫 _getUserIdFromInput();成功時把 session_id 放入 response。
3639-3645確認有 QuoteFeedback input,呼叫 SecurityHash::fulfill('QuoteService', hash)
3646-3649hash owner 等於提交者時回 error=5
3650-3659建立 quote_feedbacks,欄位來源為 hash record 與 QuoteFeedback input。
3660-3664成功 response;更新 user feedback stats;建立 QuoteService / AddReview activity。
3667-3669render JSON response。
3741-3755_getUserIdFromInput() 初始狀態與 QuoteUtil 載入。
3756-3772User[email],支援 Google / Facebook token 驗證與 social id lookup。
3774-3780social id 已綁定不同 email 時,視為 invalid session。
3782-3804Redis 重複呼叫 guard、讀 password / device_id、依 email 或 social id 找 user。
3806-3837新 email 自動建立 consumer user;password 空值時用 device id;同步 social register flags。
3839-3853建立 user_profilesuser_logins
3854-3864referral hash 成功時建立 referral,並標記新 user signup 成功。
3865-3897既有 social user 直接信任;既有 email 走 device session 或 password login。
3901-3910新 user 寫 UserSignup activity,upsert api_sessions,回 user_id/session_id
3911-3927invalid session、password required、invalid password response。

legacy helpers:

/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/AppModel.php
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/Component/QuotesUtilComponent.php
legacy 行號職責
668-673createQuoteServiceActivityById() 建立 QuoteService activity。
QuotesUtilComponent.php:59-86updateUserFeedbackStats() 只統計 quote_feedbacks.model = 'QuoteBids' 的評論後更新 user stats。

New PHP 8.2

new 檔案行號對應 legacy職責
Lib/Common/RouterRule/Mapping.php:61-64access log pathroute /quote_services/external_review/submit.jsonQuoteServices::external_review_submit()
Endpoint/V1/QuoteServices.php:248-317QuoteServicesController.php:3620-3669submit 主流程、session / external user resolve、hash guard、feedback write、stats、activity、response。
Endpoint/V1/QuoteServices.php:319-3283627-3638有 session 優先用 session user;無 session 才走 external input。
Endpoint/V1/QuoteServices.php:364-4783741-3927搬移 _getUserIdFromInput() 的 email、password、device session、新 user、api session response。
Endpoint/V1/QuoteServices.php:480-5213759-3780Google / Facebook token 驗證、social id user lookup、email mismatch guard。
Endpoint/V1/QuoteServices.php:523-6173806-3837新 consumer user 建立;使用 explicit allowlist,避免複製既有 row。
Endpoint/V1/QuoteServices.php:619-6533839-3849建立 user_profiles
Endpoint/V1/QuoteServices.php:655-6683854-3861referral hash 成功時建立 referral。
Lib/Model/QuoteFeedback.php:979-1003QuotesUtilComponent.php:59-86更新 user feedback stats;同 legacy,只統計 model = QuoteBids
Lib/Model/QuoteActivity.php:108-129AppModel.php:668-673建立 QuoteService activity;不寫 quote_activity_consumers
tests/QuoteServicesExternalReviewSubmitTest.php:99-1533650-3664成功建立 feedback、hash fulfilled、AddReview activity。
tests/QuoteServicesExternalReviewSubmitTest.php:155-1813646-3649same-user guard,並確認不建立 feedback。
tests/QuoteServicesExternalReviewSubmitTest.php:183-2263629-3638, 3806-3910無 session 時以 User[...] 自動建立 user / session 並提交評價。
tests/QuoteServicesExternalReviewSubmitTest.php:228-2423639-3645invalid hash response。

逐段對照

new 行號對應 legacy職責
Mapping.php:61-643571-3575將 legacy external_review('submit') path 拆成新版 explicit route。
QuoteServices.php:250-263, 319-3283623-3638預設 Invalid session;有 session 用 session user,無 session 解析 external input;無 session 成功時回 session_id
QuoteServices.php:265-2773639-3645QuoteFeedback,呼叫 SecurityHash::fulfill('QuoteService', hash);找不到回 error=4
QuoteServices.php:279-2843646-3649hash owner 與提交者相同時回 error=5
QuoteServices.php:286-3043650-3659建立 quote_feedbacks,保留 model=QuoteServiceforeign_id=quote_service_idquote_service_id
QuoteServices.php:306, QuoteFeedback.php:979-10033663, QuotesUtilComponent.php:59-86成功後更新 user feedback stats;同 legacy,只統計 model = QuoteBids
QuoteServices.php:307-312, QuoteActivity.php:108-1293664, AppModel.php:668-673建立 QuoteService / AddReview activity,param1 是原始 QuoteFeedback JSON。
QuoteServices.php:314-3173660-3669成功 response:{"error":0,"message":"Success"}
QuoteServices.php:364-4243741-3804, 3865-3885email、device id、既有 user、既有 session、空密碼 fallback 到 device id。
QuoteServices.php:426-432, 474-4773887-3897, 3922-3925legacy crypt password 比對;失敗回 invalid password。
QuoteServices.php:433-459, 523-6683806-3864, 3901-3910新 user 建立、profile、login、referral、UserSignup activity、api session upsert。
QuoteServices.php:467-4723916-3920既有 user 空密碼且無 device session 時回 password required。
QuoteActivity.php:108-129AppModel.php:668-673使用 QuoteService activity 寫入,不走 bid consumer index。

已確認實測

2026-06-04 legacy staging 實測 same-user guard:

request body:

QuoteFeedback[hash]=7d7de5abf902bc90549609ed35345d5e
QuoteFeedback[is_satisfied]=5
QuoteFeedback[feedback]=good
User[email]=luckynaming@gmail.com
User[name]=luckynaming
User[password]=
User[device_id]=e79ca347-5d20-4a96-98de-3c96dd01e90c

response:

{"error":5,"message":"Cannot review the same user"}

判讀:此 request 帶有效 user session,legacy 使用 session user 判斷,不使用 User[...] 欄位另建或登入 user;因此 hash owner 和 session user 相同時會被擋。

User Resolve

  • 有有效 session 時:使用 session user,忽略 User[...] 欄位。
  • 無 session 時:依 legacy _getUserIdFromInput() 使用 User[email]User[password]User[device_id]
  • 無 session 且新 email:建立 consumer user、user profile、api session,並寫 UserSignup activity,param2 = external review
  • 無 session 且既有 email:
    • 空密碼且同 device 已有 api_sessions:使用該 session。
    • 有密碼:用 legacy crypt() 比對。
    • 空密碼且沒有 device session:回 {"error":2,"message":"Failed to login, password is required"}
    • 密碼錯誤:回 {"error":3,"message":"Failed to login, invalid password"}
  • User[id_token] / Facebook token 仍依 legacy 優先驗 social id;若 social id 已綁定不同 email,回 Invalid session。

Side Effect

  • SecurityHash::fulfill('QuoteService', hash) 會把 security_hashes.is_fulfilled 設為 1;legacy 未使用 one-time guard,重複 hash 仍可能找到同一筆。
  • success 會新增 quote_feedbacks
    • model = QuoteService
    • foreign_id = quote_service_id
    • reviewed_user_id = security_hashes.user_id
    • quote_service_id = quote_service_id
  • success 會呼叫 QuoteFeedback::updateUserFeedbackStats(reviewed_user_id);legacy 這支統計只計入 model = QuoteBids,所以外部 service review 本身不會增加 service stats。
  • success 會寫 QuoteService / AddReview activity,param1 是原始 QuoteFeedback JSON。

測試

2026-06-04:

php -l Endpoint/V1/QuoteServices.php
php -l Lib/Common/RouterRule/Mapping.php
php -l Lib/Model/QuoteActivity.php
php -l tests/QuoteServicesExternalReviewSubmitTest.php
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteServicesExternalReviewSubmitTest.php

結果:

OK (4 tests, 28 assertions)