POST /users/refer.json

狀態

已補 action / routing / PHPUnit;staging legacy / new 實測待補。

這支只負責從推薦好友頁寄出推薦邀請 email。它不查推薦列表、不新增 user_referrals、不發 referral bonus、不修改 wallet;推薦關係是在被推薦人後續註冊流程依 referral_hash 寫入。

Legacy 流量

2026-07-08 查 legacy 正式機 ip-10-5-2-242/var/log/apache2/get-lancer_access.log*

Method / pathstatuscount
POST /users/refer.json2002
OPTIONS /users/refer.json2002
GET /users/refer.json2001

GET /users/refer.json 是同名 web route,會走 cookie / redirect 流程;JSON API 搬移先以 POST /users/refer.json 為範圍。

操作路徑

Web 專家推薦好友頁:

https://staging.pro360.com.tw/dashboard/free-credits/referral

前端在此頁按「發送Email」後,popup 送出 email,呼叫 POST /users/refer.json

檔案行為
/Users/mattsu/Documents/Site/web-app/modules/components/dashboard/ProReferral.js:46-51onSendEmail(email) 呼叫 APIManager.sendReferrailEmail(email),成功後關閉 popup。
/Users/mattsu/Documents/Site/web-app/modules/utils/api-manager.js:3451-3461sendReferrailEmail(email) 讀 session token,FormDataemail,POST /users/refer.json

Legacy 對照

項目legacy
web/json dispatcher/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php:4409-4412
web cookie flow/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php:4414-4447
JSON API action/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php:5668-5695
email/event helper/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/User.php:852-890
provider name helper/Users/mattsu/Documents/Site/get-lancer-php56/app/Lib/QuoteUtil.php:48-60
avatar helper/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/User.php:1526-1533
event queue insert/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/EventQueue.php:15-28
legacy email DO/Users/mattsu/Documents/Site/get-lancer-php56/app/DataObject/Notification/EmailParameterDO.php:3-24
legacy event DO/Users/mattsu/Documents/Site/get-lancer-php56/app/DataObject/Notification/EventParameterDO.php:3-9

Legacy JSON 流程:

  1. UsersController::refer() 如果 RequestHandler->prefers('json'),轉進 refer_from_api()
  2. 預設 response 是 {"error":1,"message":"Invalid request"}
  3. 必須是 JSON request 且 RestApiHelper::hasValidApiSession($this) 通過。
  4. blocked user 會在 session helper 直接回 HTTP 403 + {"error":5,"message":"Blocked User"}
  5. 從 session 取得目前 user_id
  6. 只有 POST 會呼叫 User::sendReferralEmail($user_id, $this->request->data['email'])
  7. 呼叫後直接回 {"error":0,"message":"Success"}

注意:legacy 有一段檢查 email 是否已註冊的程式,但整段被註解掉;搬移時不可加回「Email Existed」檢查。

sendReferralEmail 行為

User::sendReferralEmail($user_id, $email)

  1. users + contain UserProfile.first_nameUserProfile.last_name
  2. 只有找到 user 且 users.referral_hash 非空,才寫 event_queue
  3. 建立一個 email task:
    • to = request email
    • templateKey = 204 Vendor Signup Invitation by Referral
    • langId = User::getMultiLangId($user_id)
    • replaceContent 如下:
keylegacy 來源
##ACTION_1_TEXT##空字串
##ACTION_1_URL##空字串
##ACTION_2_TEXT##空字串
##ACTION_2_URL##空字串
##PROVIDER_NAME##QuoteUtil::getUserFullName($user)
##REFERRAL_URL##Configure::read('site.url') . '/pro?referral=' . users.referral_hash
##EMAIL_SIGNATURE##Configure::read('email.signature')
##PROVIDER_PHOTO##User::getUserAvatar($user_id)
##MORE_URL##EmailSrc::getMoreUrl()
  1. 寫入 event_queue
    • event_key = Invite_Referral
    • priority = 5
    • params.user_id = current user_id,legacy 實測 JSON 型別為 string。
    • params.tasks[0] = email task

如果 user 不存在或 referral_hash 為空,legacy 不寫 queue;但 refer_from_api() 仍然回 {"error":0,"message":"Success"},因為 helper 沒回傳值也沒丟 exception。

New 對照

項目new
endpointEndpoint/V1/Users.php:1116-1158 已補 refer()
email/event formatterEndpoint/V1/Users.php:1338-1377 已補 insertLegacyReferralInviteEvent()
provider name formatterEndpoint/V1/Users.php:1379-1395 已補 legacy fallback。
routingLib/Common/RouterRule/Mapping.php:112-115 已補 /users/refer.json explicit route。
session helper可沿用 users migrated API 現有 legacy session resolver;要保留 blocked user 與 access side effect。
provider name helperLib/Model/User.php:483-500 可重用基礎姓名組合;本 API 另補 legacy fallback:姓名空時用 username,仍空時用 User
user avatar helperLib/Model/User.php:505-512 已有 getUserAvatar(),SQL 與 legacy 對齊。
language helperLib/Model/User.php:624-631 已有 getMultiLangId()
email task DOLib/DataObject/Task/EmailTaskDO.php:4-27 可對齊 legacy EmailParameterDO public 欄位。
event task DOLib/DataObject/Task/EventTaskDO.php:4-15 可對齊 legacy EventParameterDO
event queue modelLib/Model/EventQueue.php:19-31 與 legacy insertEvent() shape 一致。
EmailSrc::getMoreUrl()Lib/Util/EmailSrc.php:384-388 與 legacy /pro/learn-more/ fixed URL 對齊。

Migration decision:

  • 不直接呼叫 User::getReferralHash() 來自動補 users.referral_hash。legacy sendReferralEmail() 只檢查現有 referral_hash,沒有在這支 API 補 hash;自動補會新增 legacy 沒有的 DB write。
  • request log 保留 RouterV3 generic log,不新增 endpoint duplicate full request log。body 只有 email,仍要維持既有 request log 過濾規則。

Request

必要 headers:

X-PRO360-Rest-Api-Key: <api key>
X-PRO360-User-Session-Token: <session id>
Content-Type: multipart/form-data

body:

欄位必填說明
emaillegacy 未明確 validate收件人 email;legacy 直接放進 email task to。缺欄位在 PHP 5.6 只會 notice,API 仍走 success path;new 實作不可變成 TypeError。

最小 curl:

curl -sS 'https://api-staging.pro360.com.tw/users/refer.json' \
  -X POST \
  -H 'X-PRO360-Rest-Api-Key: <api key>' \
  -H 'X-PRO360-User-Session-Token: <session id>' \
  -F 'email=test@example.com'

Response

Valid session + POST:

{"error":0,"message":"Success"}

Invalid request / invalid session / non-POST:

{"error":1,"message":"Invalid request"}

Blocked user:

{"error":5,"message":"Blocked User"}

Blocked user 由 session helper 回 HTTP 403。

DB / Queue Side Effect

會讀:

table用途
api_sessions驗證 session token。
users取得目前 user、referral_hashmulti_lang_id
user_profiles組 provider name。
attachmentsclass = UserAvatarforeign_id = user_id,取 amazon_s3_thumb_url。legacy 沒有 review_status / order 條件。
event_actionsworker 後續依 Invite_Referral 找 template。
email_templatesworker 後續使用 204 Vendor Signup Invitation by Referral

會寫:

table條件內容
event_queuevalid session + POST + user exists + users.referral_hash 非空event_key = Invite_Referral,payload 含一個 email task。
usersvalid session helperlegacy session access side effect 會更新 iphone_last_accesslast_access_client
task_queuevalid session helper 條件成立時provider 超過 180 天未 access 時可能寫 returnedQuoteService,與其他 migrated users API 一致。

不會寫:

  • user_referrals
  • user_over_referrals
  • wallet / transaction
  • quote activity
  • login/session 建立
  • users.referral_hash

Config 對照

keylegacy 來源new 對應用途
site.url/Users/mattsu/Documents/Site/get-lancer-php56/app/Config/settings.yml:271,範例值 https://www.pro360.com.twLib/Common/Configuration.php:272-273 / deployment config##REFERRAL_URL##。staging / production 要確認部署值。
email.signature/Users/mattsu/Documents/Site/get-lancer-php56/app/Config/settings.yml:22,範例值 PRO36O客服部 敬上Lib/Common/Configuration.php:271 / deployment config##EMAIL_SIGNATURE##

2026-07-09 local DB 已確認:

event_actions: id=21, event_key=Invite_Referral, template_type=email, template_key=204 Vendor Signup Invitation by Referral
email_templates: id=111, name=204 Vendor Signup Invitation by Referral, is_active=1

測試計畫

已補 tests/UsersReferTest.php

  • valid session + POST + user 有 referral_hash:回 success,寫一筆 event_queue,payload shape / replaceContent / templateKey / langId 對齊。
  • valid session + POST + user 沒 referral_hash:回 success,但不寫 event_queue,也不補 users.referral_hash
  • valid session + POST + email 已是既有 user email:仍回 success,不做 Email Existed 檢查。
  • valid session + POST + missing email:維持 legacy success path,不因 PHP 8.2 undefined index / null 失敗。
  • invalid session:回 legacy invalid request。
  • blocked user:HTTP 403 + blocked response。
  • non-POST:回 invalid request。
  • negative test:不寫 user_referrals / user_over_referrals / wallet / transaction。

執行結果:

docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/UsersReferTest.php

結果:OK (8 tests, 46 assertions)

待確認

  • staging legacy 實測 event_queue_log_3event_key=Invite_Referralparams.user_id 為 string,tasks[0].langId 為 number,worker 已從 event_queue 搬到 log。
  • staging 部署 site.url 是否要用 https://staging.pro360.com.tw 還是 legacy 環境實際值;這會影響 email 內的 referral link。
  • legacy RestApiHelper::getUserId() full request log 與 new RouterV3 generic log 的實際 context 差異;目前建議不新增 duplicate endpoint log。