From a0baa7a1819414481edc231fca63d8887bc0dc78 Mon Sep 17 00:00:00 2001 From: fengruixiang <474182370@qq.com> Date: Mon, 6 Jul 2026 16:46:03 +0800 Subject: [PATCH] Implement code changes to enhance functionality and improve performance --- .../justsolutionsWebV2/plan-plus-api分析.html | 217 ++++++++++++++---- 1 file changed, 166 insertions(+), 51 deletions(-) diff --git a/docs/justsolutionsWebV2/plan-plus-api分析.html b/docs/justsolutionsWebV2/plan-plus-api分析.html index 851edda..52285b4 100644 --- a/docs/justsolutionsWebV2/plan-plus-api分析.html +++ b/docs/justsolutionsWebV2/plan-plus-api分析.html @@ -90,6 +90,7 @@ .callout.note { background: var(--note-bg); border-color: var(--note-border); } .callout.gap { background: var(--gap-bg); border-color: var(--gap-border); } .callout.ok { background: var(--ok-bg); border-color: var(--ok-border); } + .callout.newreq { background:#fbefff; border-color:#d8b9ff; } .callout .lbl { font-weight: 700; } .pill { display:inline-block; font-size:11px; font-weight:700; padding:1px 8px; border-radius:20px; } .pill.done { background: var(--ok-bg); color:#1a7f37; border:1px solid var(--ok-border);} @@ -119,6 +120,10 @@

參照:旧站 justsolutionsWebPlanService(同一套後端端點) · mock 契約權威來源 server/mock/plans-plus.js

本次更新(2026-07):修正 BR/CI 校驗現狀、補入 單次查詢/線下對接/agent tiers/KYC 租賃/主體類型 等前端新契約,並新增「本地開發環境對接」與「本地種子數據缺口」章節。

補充更新(2026-07):釐清「聯絡我們/聯絡推薦人」語義——不創建任何訂單/租戶/線索,而是把當前訂單摘要既有 contact us API(POST /api/contact → 後端 customer/CreateFeedback發送給平台管理員;若已填有效推薦人代碼,則改送該推薦人(其郵箱)。原「線下對接下單」定位(5.8)已據此改寫。

+

更正(2026-07):BR/CI 非必填(業務決策)——去除 CreateOrderOrderService.cs:154/:190 兩處必填校驗(保留唯一性校驗)。文檔原「仍必填 / 阻塞 / 需業務決策」表述作廢;第 1/5.7/6.3/7-9 章已統一。核對:當前 working copy 這兩處仍為 active,需確認放開已在對接/部署環境應用。

+

再次更新(2026-07):單次查詢(隨付即用 · Pay-As-You-Go)後端已支持——由 AML 主模塊 ConsumerPortal「即時檢測」承接。調用方已確認為 AML 後端內部編排訂單支付完成後,由後端在 2C 租戶上下文內依次 CreateConsumerLink(建鏈接、拿 accessToken)→ CreateConsumerOrder(建訂單、即時檢測、結果郵件);justsolutionsWebV2 只負責下單 + 收款,不調這兩個接口(既非 V2 BFF、也非瀏覽器發起)。原 5.6「後端零支持」結論作廢,第 1/2/4/5.6/7-9 章已據此改寫。

+

租戶查詢落定(2026-07):GET /tenants/lookup?email= 需後端新增「按管理員郵箱查可續費租戶」端點。現 queryRenewableTenant 在 DB 層只能按租戶名 ContainsTenantAdminEmail 是查完後遍歷全部租戶用戶逐條回填(OrderService.cs:2372-2379),無法按郵箱在 DB 過濾。BFF「以空 keyword 拉全部可續費租戶、再本地按郵箱篩」的變通不採用(現端點按租戶名搜、返回整張租戶列表,查詢維度與聚合歸屬都更該在後端);後端按郵箱全庫掃描本身可接受(租戶數少、低頻交互,可復用現 GetAllUsers() 全庫掃描、改按郵箱過濾)。第 1 / 5.5 / 7 / 8 / 9 章已據此改寫,並補入新端點的具體設計(入參、反查邏輯、聚合返回)。

+

單次查詢定價修正(2026-07):隨付即用(PAYG)的收款金額改由 AML GetPlanListcountryCode 返回的 PAYG(P2G) 方案價決定(不再是 V2 /single-query/options 分項單價之和);檢測內容為固定 4 項、純展示、用戶不可勾選/取消——證件核驗(V)名單篩查(ES=E)AI 增強型篩查(A)信貸記錄篩查(失信人=D)(叫法後續可能再調整)。頁面 /plans-jp(日本別名,server/index.js 直接以 plans-plus.html 承接、URL 不變;日本 IP 訪 /plans 自動 302 至此)對應 countryCode:"JPN"目前 countryCode 僅日本一國;PAYG 為單一整包價,非分項之和)。原 5.6「op→功能碼非 1:1 / 公司查冊歸屬 / V2 分項單價與後端 jQ 計費對齊」等缺口據此收斂;第 1 / 3.1 / 5.6 / 7 / 8 / 9 章已改寫。

@@ -134,7 +139,7 @@
  • 5.3 GET /countries(註冊地)
  • 5.4 GET /agents/:code(推薦人 + tiers)
  • 5.5 GET /tenants/lookup(按郵箱查租戶)
  • -
  • 5.6 單次查詢 single-query(新)
  • +
  • 5.6 單次查詢 single-query(ConsumerPortal 即時檢測)
  • 5.7 POST /subscribe(下單)
  • 5.8 聯絡我們/推薦人(發訂單摘要·不下單)
  • 5.9 POST /payments/create(支付·暫緩)
  • @@ -156,9 +161,9 @@ GET /plans/catalogplan/portal/GetPlanList改造復刻 filterPlan;KYC 改月租、jQuota 改配套;bestValue/note/nameJP 後端無;本地庫 0 條 Plan GET /countriesOrder/getCategoryByTypes已實現—(routes/countries.js 已可用) GET /agents/:codeSearchUserByCodeAndType + GetPlanList(agentUserId)改造新增 tiers(可售方案級別)、email/phone、隱藏 agentorId - GET /tenants/lookupOrder/portal/queryRenewableTenant缺口大後端按租戶名搜;前端按郵箱、唯一命中(不再多租戶消歧);需重建 currentSubscription + referrer - GET /single-query/options
    POST /single-query無對應後端缺口大全新按次查詢流程,後端完全無端點(見 5.6) - POST /subscribeCreateOrder / TenantRenewal改造planDetailIdBR/CI 仍必填(更正)topup/個人主體待定 + GET /tenants/lookupqueryRenewableTenant新增 queryRenewableTenantByEmail需後端新增已定:後端新增按郵箱端點。現端點 DB 層只按租戶名 Contains 搜、郵箱查後回填→不能按郵箱過濾;前端按郵箱、唯一命中(不再多租戶消歧);新端點直接聚合 currentSubscription + referrer(見 5.5) + GET /single-query/options
    POST /single-queryV2 只到「下單+收款」;
    支付後 AML 後端內部 CreateConsumerLink+CreateConsumerOrder改造更正:後端已支持(AML 即時檢測);兩步為後端內部編排,V2/瀏覽器均不調;PAYG 定價改由 GetPlanList(countryCode) 的 PAYG 方案價、檢測內容固定 4 項(V/E/A/D)純展示不可選(見 5.6) + POST /subscribeCreateOrder / TenantRenewal改造planDetailIdBR/CI 非必填(已定)topup/個人主體待定 POST /contact
    (聯絡我們/推薦人)customer/CreateFeedback復用現有不創建訂單:訂單摘要經 contact us API 發管理員;填了推薦人則發推薦人(見 5.8) POST /payments/create暫緩本輪支付除外:前端提交後直接顯示「已提交成功」(見 5.9) @@ -166,8 +171,8 @@

    本次三個最重要的更正 / 硬缺口:

    -

    BR/CI 必填(舊版結論已過時,本輪已放開)CreateOrder 兩處「BR 或 CI 至少一個」校驗(OrderService.cs:154:190)原本仍生效(舊版文檔稱 2026-06 已放開與現狀不符)。已改 本輪按決策已注釋這兩處必填校驗(保留唯一性校驗),並 rebuild 生效——個人主體/空 BR 已可下單。注意:此為全局改動,旧站共用 CreateOrder,其「空 BR 不再攔截」影響待業務確認(見第 9 章 Q1)。

    -

    單次查詢(single-query)後端零支持:全庫 grep 無任何按次查詢 / pay-per-use 概念。/single-query/options/single-query 需新增後端,或本地繼續走 mock。

    +

    BR/CI 非必填(業務決策,已定):plans-plus 新購(含個人主體 / 空 BR/CI)需能下單 ⇒ BR/CI 不作必填。實現=注释 CreateOrder 兩處必填校驗(OrderService.cs:154:190);唯一性校驗 ExistsByOrganizationBRCI 對「BR、CI 皆空」返回 false:1124),空值安全通過,無需其它改動核對 當前 working copy 這兩處仍為 active——若對接/部署環境尚未放開,空 BR/CI 仍會被攔,需確認該改動已應用。注意此為全局改動(旧站共用 CreateOrder),「空 BR 不再攔截」影響請業務知悉(見第 9 章 Q1)。

    +

    單次查詢(single-query)後端已支持(本輪更正,原「零支持」結論作廢):走 AML 主模塊的 ConsumerPortal 即時檢測,且調用方為 AML 後端內部編排——訂單支付完成後,後端在 2C 租戶上下文內 CreateConsumerLink(建鏈接、拿 accessToken)→ CreateConsumerOrder(建訂單、即時檢測、結果郵件)。V2 只下單 + 收款,不調這兩個接口本輪再定:PAYG 檢測內容固定為 證件核驗 / 名單篩查(ES) / AI 增強型篩查 / 信貸記錄篩查(失信人) 4 項純展示(用戶不可勾選/取消,確定性映射 V/E/A/D),收款金額取自 GetPlanListcountryCode/plans-jp→JPN)返回的 PAYG(P2G) 方案價——原「op→功能碼非 1:1 / 公司查冊無碼 / V2 分項單價與後端 jQ 對齊」缺口收斂。CreateConsumerLink 非匿名(RealtimeScreeningManagement + AML.ConsumerPortal.Enable)在後端 2C 租戶上下文中天然滿足,故 V2 BFF 無需持該租戶憑證改造 詳見 5.6。

    本地種子數據缺口:本地 docker 庫 AMLPortal_Plans / AMLPortal_PlanDetails 均為 0 條SaasEditions 僅 1 條 StandardAMLPortal_AgentUserPlans 為 0。⇒ 直接對接會拿到空目錄,必須先補種子數據才能跑通(見第 6 章)。

    @@ -193,7 +198,10 @@

    關鍵差異(與旧站):旧站 Angular 直接從瀏覽器調 /api/amlPortal/*,token 存 localStorage。V2 改為瀏覽器不直接接觸後端,由服務端 BFF 持有憑證、收口後端調用並裁剪響應。因此本文每個端點都拆成「前端契約」與「BFF→後端映射」兩層。

    -

    支付本輪除外:當前 plans-plus.jssubmit() 已臨時改為——POST /subscribe 成功後直接顯示「已提交成功」showSubmitted()),不再調 /payments/create、不跳收銀台(源碼注釋標明「臨時改動…還原方法」)。submitSingle() 仍保留支付鏈,但單次查詢後端未就緒。⇒ 本輪落地只需打通到 /subscribe 為止;支付見 5.9 暫緩。

    +

    支付本輪除外:當前 plans-plus.jssubmit() 已臨時改為——POST /subscribe 成功後直接顯示「已提交成功」showSubmitted()),不再調 /payments/create、不跳收銀台(源碼注釋標明「臨時改動…還原方法」)。⇒ 本輪落地只需打通到 /subscribe 為止;支付見 5.9 暫緩。

    +
    +
    +

    single-query 是 BFF 範式的例外:其餘端點都是「瀏覽器 → V2 /api/* → BFF 代理後端」;但單次查詢的即時檢測觸發(CreateConsumerLink/CreateConsumerOrder)不走 BFF 代理——由 AML 後端在支付完成後內部編排(2C 租戶上下文),V2 只到「下單 + 收款」為止。詳見 5.6。

    @@ -212,7 +220,12 @@ GET /plans/catalog→ { success, data:{ standard:[Plan], cpa:[Plan], addons:{ jquota:{ packages:[{id, jq, price, name*}] } } } } GET /countries → { success, data:[{code, name, nameTC, nameSC, nameJP, phoneCode}] } GET /single-query/options → { success, data:{ operations:[ - {id, price, nameCN/EN/JP, descCN/EN/JP}] } } // ← 新 + {id, nameCN/EN/JP, descCN/EN/JP}] } } // ← 固定 4 項純展示,不可勾選/取消;price 不再逐項計 +// 固定 4 項:證件核驗(V) / 名單篩查ES(E) / AI 增強型篩查(A) / 信貸記錄篩查失信人(D) +// PAYG 收款價 → 取 GetPlanList(countryCode) 的 PAYG(P2G) 方案 price(單一整包價,非分項之和) +POST /amlPortal/plan/portal/GetPlanList body = { // ← PAYG 定價來源(沿用旧站請求體 + countryCode) + pageIndex:0, pageSize:100, filter:"", getAllItems:false, + tag1List:[], tag2List:[], tag3List:[], countryCode:"JPN" } // 目前 countryCode 僅日本一國 Plan = { planId, tag2Code, nameCN, nameEN, nameJP, periodMonths, price, originalPrice, qCount(-1=無限), userCountLimit, @@ -220,6 +233,9 @@ Plan = { planId, tag2Code, nameCN, nameEN, nameJP, periodMonths,

    KYC 改為「設備月租」:catalog 的 addons.kyc 不再是 unitPrice,而是 monthlyPrice。前端 kycUnit() = monthlyPrice × 所選方案 periodMonths(隨方案期數自動變動,一次付清、無套餐優惠價,見 plans-plus.js kycPriceForMonths())。jQuota 亦由「按量」改為「選配套」(jquotaPackageId)。

    +
    +

    單次查詢(PAYG)目錄改為「固定展示 + 外部定價」:/single-query/options 的 4 項不再是可勾選的計費項,而是固定信息展示證件核驗 / 名單篩查(ES) / AI 增強型篩查 / 信貸記錄篩查(失信人),用戶不可選/取消;名稱後續可能調整)——可由前端硬編碼、或後端返回固定 4 項,不含 price。PAYG 的收款金額單獨取自 GetPlanListcountryCode 返回的 PAYG(P2G) 方案價:/plans-jp(日本別名頁)傳 countryCode:"JPN"——目前 countryCode 僅日本一國(後端 GetPlanListParam.CountryCode 支持「該國專屬 + 全球通用(CountryCode=null)」過濾,將來擴國時沿用同一機制)。該價為單一整包價。⇒ 前端需依 URL/地域推導 countryCode 並在載入期取價(見 5.6)。

    +

    3.2 交互期(按需)

    GET /tenants/lookup?email=          // ← 只按郵箱;郵箱全局唯一 → 至多命中 1 個租戶
    @@ -272,7 +288,7 @@ POST /single-query body = {
     
     

    4. 後端真實端點清單(AMLPortal)

    -

    以下端點均在 AML_Backend/modules/iCON.Abp.AMLPortal,並已被旧站 justsolutionsWeb/PlanService 使用驗證過。前綴 /api/amlPortal/*(agent 校驗端點在 Identity 模塊 /api/identity/*)。

    +

    1–11 均在 AML_Backend/modules/iCON.Abp.AMLPortal,並已被旧站 justsolutionsWeb/PlanService 使用驗證過。前綴 /api/amlPortal/*(agent 校驗端點在 Identity 模塊 /api/identity/*)。12–13 為單次查詢用到的方法,位於 AML 主模塊 iCON.Abp.AML/api/aml/ConsumerPortal/*);但由 AML 後端在支付完成後內部編排調用,V2 BFF 不代理、瀏覽器不直調(見 5.6)。

    @@ -287,10 +303,11 @@ POST /single-query body = { - + +
    #方法 / 路由用途位置
    9POST /api/amlPortal/Order/portal/TenantRenewal租戶續費下單OrderController:321 · OrderService:2197
    10POST /api/amlPortal/Order/portal/PaymentWebhook支付回調(支付方服務端調,非前端)OrderController:163
    11POST /api/amlPortal/customer/CreateFeedback聯絡我們/推薦人:接收訂單摘要(不下單);AssignedAgentUserId 收件人路由(見 5.8)CustomerController · routes/contact.js 已封裝
    單次查詢(options + 下單)後端無對應端點(見 5.6)
    12POST /api/aml/ConsumerPortal/CreateConsumerLink
    (後端內部編排調用)
    單次查詢第 1 步:建即時檢測鏈接(回 accessToken·非匿名(RealtimeScreeningManagement + AML.ConsumerPortal.Enable)——在後端 2C 租戶上下文內滿足ConsumerPortalController:340 · ConsumerPortalService:964
    13POST /api/aml/ConsumerPortal/CreateConsumerOrder
    (後端內部編排調用)
    單次查詢第 2 步:憑 accessToken 建訂單並即時檢測(異步跑模塊 + 結果郵件)·匿名ConsumerPortalController:78 · ConsumerPortalService:325
    -

    DTO 位置:Application.Contracts/PlanAppLayer/*(GetPlanListParam、PlanDto、PlanDetailDto)、Application.Contracts/OrderAppLayer/*(CreateOrderParam、SelectPlanItem、TenantRenewalParam、QueryRenewableTenantParam、TenantPropertyDto)。agent 返回 DTO:iCON.Abp.FX.Users/AppUserDto

    +

    DTO 位置:Application.Contracts/PlanAppLayer/*(GetPlanListParam、PlanDto、PlanDetailDto)、Application.Contracts/OrderAppLayer/*(CreateOrderParam、SelectPlanItem、TenantRenewalParam、QueryRenewableTenantParam、TenantPropertyDto)。agent 返回 DTO:iCON.Abp.FX.Users/AppUserDto。單次查詢 DTO:iCON.Abp.AML/Application.Contracts/ConsumerPortalAppLayer/*(CreateConsumerLinkParam、CreateConsumerCustomerDto、CreateConsumerLinkDto、CreateConsumerOrderParam、ConsumerLinkDto)+ IndividualAppLayer/CreateIndividualDtoOrgLayer/CreateOrganizationDto

    @@ -398,16 +415,23 @@ POST /single-query body = {
    -

    5.5 GET /tenants/lookup(按郵箱查租戶) 缺口大

    -
    GET /api/tenants/lookup?email=  →  POST /api/amlPortal/Order/portal/queryRenewableTenant
    +

    5.5 GET /tenants/lookup(按郵箱查租戶)需後端新增端點

    +
    GET /api/tenants/lookup?email=  →  NEW /api/amlPortal/Order/portal/queryRenewableTenantByEmail

    前端按郵箱查租戶,返回 match:'none'|'unique' 兩態(不再多租戶消歧——註釋明確「租戶名與郵箱均全局唯一 → 一郵箱至多 1 個租戶」)。命中後帶出 currentSubscription + referrer 預填續費/加購表單。

    -

    但後端 queryRenewableTenant 只接受 {keyword} 且按 TenantName.Contains(keyword) 搜索(OrderService.cs:2362),返回 List<TenantPropertyDto>——TenantAdminEmail 是查完後再逐條用 admin 用戶郵箱回填(:2375-2379),不能在 DB 層按郵箱過濾

    -

    三個不匹配點

    -
      -
    1. 查詢維度不符:前端給郵箱、後端按名字搜。短期 BFF 變通:以空/寬 keyword 拉全部可續費租戶,在服務端按 TenantAdminEmail===email 過濾(⚠️ 全量拉取、性能與越權風險,僅臨時)。正解:後端新增「按管理員郵箱查可續費租戶」端點
    2. -
    3. currentSubscription 需重建:前端的 currentSubscription(planId、name、periodMonths、price、qCount、userCountLimit、startDate、expiryDate、usedQuota、addons)並非 TenantPropertyDto 直接字段,需由 TenantProperty + 其 Order/OrderDetail 推導。可參考 GetCurrServiceInfoOrderService.cs:398CurrServiceInfoDto)。
    4. -
    5. referrer 新增:前端續費頁展示「註冊時填寫的推薦人」。TenantPropertyDto 已有 AgentorCode/AgentorName ⇒ 映射為 referrer:{code:AgentorCode, name:AgentorName}
    6. -
    +

    但現有後端 queryRenewableTenant 只接受 {keyword} 且按 TenantName.Contains(keyword) 在 DB 層搜索(OrderService.cs:2362-2364),返回 List<TenantPropertyDto>——TenantAdminEmail 是查完命中租戶後、再 GetAllUsers() 遍歷全庫用戶UserName=='admin' && TenantId 逐條回填(:2369-2379),邏輯上「先有租戶、後有郵箱」,無法在 DB 層按郵箱過濾。⇒ 前端「給郵箱、要唯一命中」的查詢維度與現端點根本錯位

    +
    +

    已定決策:後端新增「按管理員郵箱查可續費租戶」端點。不採用 BFF 變通(以空 keyword 拉全部可續費租戶、再服務端按 TenantAdminEmail===email 過濾)——現端點按租戶名搜、返回整張可續費租戶列表,讓 BFF 承接全表列表並自拼 currentSubscription/referrer 的訂單反查,既非其職責、也易錯;查詢維度與聚合歸屬都更該落在後端(注:問題不在掃描成本——後端側按郵箱全庫掃描本身可接受,租戶數少、低頻交互;而在查詢維度錯位與聚合歸屬。)新端點把查詢維度反過來(先按郵箱定位 admin 用戶 → 再取其租戶),順帶一次性聚合 plans-plus 續費/加購頁所需的 currentSubscription + referrer,避免 BFF 拼裝易錯的訂單反查。

    +
    +

    新增端點設計(供後端實作參照)

    +
    NEW POST /api/amlPortal/Order/portal/queryRenewableTenantByEmail  [AbpAutoAuth("Portal")]
    +

    TenantPropertyDto → 前端 Tenant 映射(可得部分)

    @@ -425,20 +449,112 @@ POST /single-query body = {
    前端TenantPropertyDto備註
    currentSubscription.addons.users/jquotaPackageId經訂單明細反查推導
    -
    -

    建議後端改動:新增 queryRenewableTenantByEmail(email),直接返回 plans-plus 所需的「租戶 + currentSubscription(含 planId/上期價/已購加值項)+ referrer」聚合結構,避免在 BFF 拼裝易錯的訂單反查。

    +
    +

    聚合由誰拼裝:上表「可得部分」是 TenantPropertyDto 直出字段;而 currentSubscriptionplanId / 名稱 / 上期 price / periodMonthsaddons.users / jquotaPackageId 均需經 Order → OrderDetails(按 Tag1Code:B=基礎、AdlU=增購用戶、jQ/j=jQuota、KYC)反查、usedQuota = QCountPurchase − QCountLeft。按本次決策,這段反查應落在新端點 queryRenewableTenantByEmail 內(服務端一次算好),而非 BFF——後端已有 GetCurrServiceInfoOrderService.cs:398)反查 ActiveBPlans 的現成範式可借用,只需擴出加值項明細與上期價。BFF routes/tenants.js 因此退化為字段直通 + 命中態(none/unique)包裝

    -

    5.6 單次查詢 single-query(按次付費) 新增需求 · 後端零支持

    -
    GET /api/single-query/options  ·  POST /api/single-query  →  無對應後端
    -

    plans-plus 新增第 4 種流程 type='single':無需訂閱、填「結果接收郵箱 + 查詢主體 + 勾選查詢項目」→ 付款 → 結果郵件發送。前端契約見 3.1/3.3。全庫 grep 無任何按次查詢 / pay-per-use 端點或實體

    +

    5.6 單次查詢 single-query(隨付即用 · Pay-As-You-Go) 後端已支持 · 後端內部編排

    +
    GET /api/single-query/options(固定展示)  ·  POST /amlPortal/plan/portal/GetPlanList {countryCode}(PAYG 定價)  ·  POST /api/single-query  (收單 + 支付) ⟶ 支付完成 ⟶  AML 後端內部 CreateConsumerLink → CreateConsumerOrder
    + +
    +

    本輪定案(PAYG 定價 + 固定檢測項):隨付即用的檢測內容固定為 4 項、純信息展示,用戶不可勾選/取消——證件核驗名單篩查(即 ES 檢測)AI 增強型篩查信貸記錄篩查(即失信人檢測)(叫法後續可能再調整),確定性映射後端功能碼 V / E / A / D(無 S 風險評估問卷、R CDD 報告、O OCR)。收款金額不再由 V2 分項單價相加,而是取自 AML GetPlanListcountryCode 返回的 PAYG(P2G) 方案價/plans-jp 頁傳 countryCode:"JPN"目前 countryCode 僅日本一國,前端依 URL/地域推導),該價為單一整包價(非分項之和)。⇒ 原「op→功能碼非 1:1、公司查冊無獨立碼、V2 分項單價與後端 jQ 對齊」三處缺口收斂:檢測項固定 4 = E/A/V/D,定價口徑統一到 GetPlanList

    +
    + +
    +

    本輪更正(原「後端零支持」結論作廢;並釐清調用方):單次查詢由 AML 主模塊的 ConsumerPortal「即時檢測」承接(iCON.Abp.AML)。調用方已確認:這兩步不是 V2 BFF、也不是瀏覽器發起,而是 AML 後端在「訂單支付完成後」、於 2C 租戶上下文內部編排執行——CreateConsumerLink(建鏈接拿 accessToken)→ CreateConsumerOrder(建訂單、即時檢測、結果郵件)。justsolutionsWebV2 的職責止於「下單 + 收款」,完全不觸碰這兩個接口。

    +
    + +
    +

    為何不能放在 V2 BFF:CreateConsumerLink 非匿名[Authorize(RealtimeScreeningManagement)] + [RequiresFeature(AML.ConsumerPortal.Enable)]ConsumerPortalController.cs:337-340),且在 CurrentTenant 下建鏈接。V2 BFF 只持「門戶訪客」憑證(__tenant=Portal),既無實時篩查權限、也非 2C 租戶上下文 ⇒ 調不動。放到後端內部後,2C 租戶上下文與功能開關天然滿足,鑑權不再是 BFF 的問題CreateConsumerOrder 雖是 [AllowAnonymous]:76-78,憑 accessToken),但既然第 1 步已在後端,第 2 步同處後端內部串接最自然。

    +
    + +

    職責邊界與調用鏈

    +
    [V2 · 瀏覽器 + BFF] +  GET /api/single-query/options ……… 查詢項目目錄(V2 自定義,見 3.1) +  POST /api/single-query ………………… 建「待支付」單次查詢單(存 郵箱/主體/所選項目/金額)→ orderId +  POST /api/payments/create …………… 收款(見 5.9;本輪支付除外) +──────────────── 支付完成(webhook / 回調)──────────────── +[AML 後端 · 2C 租戶上下文內部編排](非 V2、非瀏覽器發起) +  固定 4 項檢測 → functionCodes = E/A/V/D(證件核驗V·名單篩查ES=E·AI增強A·失信D;無 S/R/O) +  ① CreateConsumerLink { 郵箱, functionCodes:"EAVD", validHours } → accessToken +  ② CreateConsumerOrder { accessToken, objectType, individual|organization, functionCodes:["E","A","V","D"] } +  → 建訂單(直接 Paid) + 每碼一條 DetectionTask → MQ 異步跑檢測 → 完成發結果郵件
    + +
    +

    待後端明確的銜接點(由後端定義,非 V2 落地):「支付完成」如何把單次查詢單的資料(郵箱/主體/所選項目)+ 支付結果交給後端這段內部編排,常見兩種接法:
    + ① 後端持單:V2 在 POST /single-query 時即把單推給後端一個「2C 收單/意向」端點落庫;支付 webhook 直接打到後端,後端據單觸發 ①②。
    + ② V2 持單、支付後通知後端:V2 收到支付成功後,調後端一個專門的 2C 觸發端點(其鑑權/形態由後端定義),該端點內部再跑 ①②。
    + 無論哪種,①② 本身都是後端內部行為,V2 始終不直接調 CreateConsumerLink/CreateConsumerOrder

    +
    + +

    後端內部兩步(payload 供參考)

    +

    第 1 步 · 建即時檢測鏈接 CreateConsumerLinkConsumerPortalService.cs:964):以「結果接收郵箱」upsert ConsumerCustomer,在當前 2C 租戶下建鏈接 → 回 ConsumerLinkDto(含 accessToken)。

    +
    { "createConsumerCustomerDto": { "email":"result@company.com", "name":null, "phone":null, "remark":null },
    +  "createConsumerLinkDto":     { "functionCodes":"EAVD", "validHours":72,
    +                                 "validEndTime":"2026-07-09T06:00:47.000Z" } }
    +// 回:OperationDto.Success(ConsumerLinkDto{ accessToken, functionCodes, expireTime, tenantId, … })
    + +

    第 2 步 · 建訂單並即時檢測 CreateConsumerOrder:325):據 token 定位鏈接與租戶 → 計價(jQ) → 建 Individual/Organization 實體 → 建 ConsumerPortalOrder直接置 PaymentStatus=Paid、OrderStatus=Pendding:388)→ 每個 functionCode 建一條 DetectionTask → 發 RabbitMQ 消息,由 ProcessConsumerPortalOrder 異步跑檢測模塊,完成後 SendConsumerLinkResultMail 發結果郵件。

    +
    { "accessToken":"xxx", "objectType":"Individual",
    +  "individual": { "fullNameEN":"张三", "fullNameZH":"", "dateOfBirth":{"year":0,"month":0,"day":0},
    +                  "address":"", "addressOfLiving":"", "nationalityCode":null,
    +                  "documentRequiredFileIds":[], "identityDocumentTypeCode":"", "identityDocumentNumber":"",
    +                  "gender":"", "phone":"", "email":"", "occupation":"", "entityIdentityDocuments":[] },
    +  "organization": null,
    +  "functionCodes": ["E","A","V","D"] }   // ← 固定 4 項(PAYG 檢測內容不可選)
    +// 回:OperationDto.Success({ entity, order })   // order.Id / order.OrderCode 可作結果查詢鍵
    + +

    V2 收集字段 ↔ 後端入參映射

    +

    V2 通過 POST /single-query(或銜接端點)把下列字段交給後端;後端在內部編排時據此組裝上面兩個 payload。

    + + + + + + + + + +
    V2 收集字段後端入參說明
    email(結果接收郵箱)createConsumerCustomerDto.email必填;後端按 email upsert ConsumerCustomer,並作結果郵件收件人
    subjectType 'individual'|'company'objectType 'Individual'|'Organization'枚舉 ObjectTypeEnum(Individual=0 / Organization=1);company→Organization
    subject(單一名稱字段)individual.fullNameENorganization.fullNameEN⚠️ V2 只收一個名稱;後端 CreateIndividualDto/CreateOrganizationDto 字段眾多但均可空 → 最小可用只填全名,其餘留空/默認
    固定 4 項檢測(V2 不收選擇)兩處 functionCodes(鏈接=字符串 "EAVD"、訂單=數組 ["E","A","V","D"]固定映射:證件核驗→V、名單篩查ES→E、AI 增強→A、失信→D;兩步一致
    (鏈接有效期)createConsumerLinkDto.validHours後端據此算 ExpireTime樣例中的 validEndTime 後端 DTO 無此字段、被忽略
    + +
    +

    功能碼映射(本輪固定,非 1:1 問題消除):PAYG 檢測內容固定 4 項、用戶不可選,直接確定性對應後端功能碼(後端只認 E/A/V/D/S/R,O=OCR 排除;PAYG 僅用前 4 個):

    +
      +
    • 證件核驗V(證件核驗)
    • +
    • 名單篩查(即 ES 檢測)E(ES 實體篩查)
    • +
    • AI 增強型篩查A(AI 檢測) (E、A 在後端合併為一次 EntitiesInvestigation 計費,ConsumerPortalService.cs:277
    • +
    • 信貸記錄篩查(即失信人檢測)D(失信查詢)
    • +
    +

    ⇒ 原「多個 op 坍縮為同一 E/A 計費」「公司查冊無獨立碼」的映射難點不復存在(新方案無「公司查冊」項,S/R 不在 PAYG)。定價口徑亦統一:V2 收款金額 = GetPlanListcountryCode 的 PAYG(P2G) 方案價,不再與後端逐碼 jQ 扣費逐項對賬——後端 CreateConsumerOrder 仍按 2C 租戶 jQ 計費/扣減(僅 CHN/HKG 計 KYC jQ,:297),但那是後端內部帳,與 V2 對外報價解耦。

    +
    + +

    鑑權(均在後端內部滿足)

    + + + + + + +
    方法鑑權後端內部編排下的落地
    CreateConsumerLink非匿名[Authorize(RealtimeScreeningManagement)] + [RequiresFeature(AML.ConsumerPortal.Enable)]ConsumerPortalController.cs:337-340在後端 2C 租戶上下文內執行 ⇒ 租戶上下文與功能開關天然滿足,無需 V2 憑證。待確認的只是「用哪個 2C 租戶」及其 ConsumerPortal 功能/權限已開
    CreateConsumerOrder匿名 [AllowAnonymous]:76-78accessToken 定位租戶;後端內部緊接第 1 步串行調用
    + +

    結果回取(可選)

    +
    -

    落地選項:短期保持 mockPLANS_PLUS_MOCK=true 或單獨掛 mock/plans-plus.js 的這兩條),其餘端點走真實後端;② 後端新增「按次查詢」下單 + 計費 + 結果投遞(涉及檢測引擎 iCS,工作量大)。本輪建議選 ①:single-query 端點繼續 mock,優先打通訂閱三態(new/renew/topup)的真實對接。

    +

    仍存在的缺口 / 落地要點:

    +

    銜接點:V2「支付完成」與後端內部編排的對接(上方 ①/② 兩種接法)由後端定義端點/webhook 承接。

    +

    op→功能碼映射已收斂:檢測內容固定 4 項、確定性映射 E/A/V/D(無「公司查冊」、無 S/R);V2 只需固定傳 functionCodes="EAVD"(或後端直接寫死該 4 項)。

    +

    2C 租戶:確認/建立隨付即用專用租戶、開 AML.ConsumerPortal.Enable 功能、配實時篩查權限——供後端內部編排落上下文(不再需要在 V2 BFF 配該租戶憑證)。

    +

    對外定價已確認:V2 收款金額 = GetPlanListcountryCode/plans-jp→JPN)返回的 PAYG(P2G) 方案價,為單一整包價(非分項之和),前端載入期取價、純展示 4 項檢測。後端 CreateConsumerOrder 仍把訂單直接置 Paid 並按 2C 租戶 jQ 扣費(GetPriceTwoC/線上支付分支已注釋 :196)——屬後端內部帳,與 V2 對外報價解耦。現階段 countryCode 僅日本(JPN):即只有 /plans-jp 有 PAYG 定價,其餘國家待種 P2G 方案 + PlanDetail 後再開(機制沿用,不需改前端契約)。

    +

    本地環境:需該租戶 + 檢測引擎(iCS)/RabbitMQ 就緒,ProcessConsumerPortalOrder 才能真正跑出結果;否則本地 single-query 可繼續走 mock,僅在對接環境驗證真調用。

    +
    + +
    +

    V2 側建議形態:/single-query/options 收斂為固定 4 項純展示(可前端硬編碼或後端返回固定項,不含 price);PAYG 收款價由前端載入期依 URL/地域推導 countryCode 調 GetPlanList取 PAYG(P2G) 方案價(或經 BFF /plans/catalog 帶 countryCode 一併取回)。/single-query(收單,返回 orderId)契約不變,不新增對 ConsumerPortal 的代理路由。V2 只需把「支付完成」與後端銜接(見上 ①/②),兩步真調用全部在後端內部——前端與 BFF 無需感知 CreateConsumerLink/CreateConsumerOrder,也不傳檢測項選擇(固定 4 項在後端寫死或由 V2 固定傳 EAVD)。

    @@ -455,7 +571,7 @@ POST /single-query body = { TenantAdminEmailemail必填;後端 CheckEmailExists 校驗未註冊(:159Jurisdictionjurisdiction必填(:153OrganizationReferenceedition(Guid)必填;校驗 edition 存在(:200) - OrganizationBR / OrganizationCIbr / —(前端不收 CI)阻塞 後端仍要求 BR 或 CI 至少一個(見下) + OrganizationBR / OrganizationCIbr / —(前端不收 CI)非必填 BR/CI 已定非必填,空值可下單(見下) EffectiveStartTimestartDate ContactPersoncontact個人主體 = company CompanyAddressaddress可選(個人主體為空) @@ -465,11 +581,9 @@ POST /single-query body = { IsOfflinePayment/IsAgentBehalf—在線下單固定 false -
    -

    BR/CI 阻塞(本次更正):CreateOrder 兩處校驗 當前源碼仍生效OrderService.cs:154:190——if (BR 空 && CI 空) return "OrganizationBR or OrganizationCI is required"。緊隨的唯一性校驗 ExistsByOrganizationBRCI:191)兩者皆空時返回 false,但走不到那步就先被必填校驗擋下。⇒ plans-plus:
    - · corp 主體:BR 標「可選」,但空 BR 會下單失敗;
    - · individual 主體br 恒為空、無 CI ⇒ 必然失敗
    - 三種對策(擇一):(a) 後端真正去除該必填校驗(若業務允許空 BR/CI);(b) 前端把 corp 的 BR 改為必填、且暫不放行 individual 新購;(c) BFF 為空 BR/CI 注入占位值(不推薦,污染合規欄)。需業務決策。

    +
    +

    BR/CI 非必填(已定決策):plans-plus 新購允許空 BR/CI——含 corp 主體(BR 維持「可選」、可空)與 individual 主體br 恒空、無 CI)。實現=注释 CreateOrderOrderService.cs:154:190 兩處必填校驗(if (BR 空 && CI 空) return "…required");緊隨的唯一性校驗 ExistsByOrganizationBRCI 對「BR、CI 皆空」返回 false:1124),空值安全通過,無需其它改動

    +

    ⚠️ 核對現狀:當前 working copy 的 :154/:190 仍為 active;若對接/部署環境尚未放開,空 BR/CI 會被攔,需先應用該改動。

    ② type='renew' → TenantRenewal

    @@ -593,14 +707,15 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P 項狀態說明 Edition + Plan/PlanDetail 種子已補docker-compose-local-dev/seed-plans-plus.sql(幂等,鏡像 mock 目錄;CPA edition GUID 對齊 jQSeparatedEditions,down -v 後重跑) - 後端 BR/CI 必填放開已改OrderService.cs:154,190 兩處必填校驗已注釋;唯一性校驗保留(rebuild 已生效) + 後端 BR/CI 必填放開待應用/核對決策=非必填:注释 OrderService.cs:154,190 兩處必填校驗(保留 :191 唯一性,空值安全)。當前 working copy 這兩處仍 active——需確認已在對接/部署環境放開 BFF 真實路由已寫新增 routes/editions.js · plans.js · agents.js · tenants.js · subscribe.js + services/catalog.jscountries.js 改為免 token;index.js 掛載於 mock/代理之前 訂閱三態new 已驗 renew/topup 待數據new(corp/個人/CPA/加值項)實測返回 orderId、PlanPrice 正確(含 KYC 月租 PCS=月數);renew/topup 路由已寫,但本地無可續費租戶數據,待後端「按郵箱查」或先建租戶 - single-query / paymentsmock按決策暫留 mock(PLANS_PLUS_MOCK=true 兜底);支付本輪除外 + single-query(PAYG 隨付即用)前端+mock 已實現 後端對接待接本輪落地新模型檢測內容固定 4 項純展示、用戶不可選/single-query/options 去單價、加 code=V/E/A/D);收款價取 GetPlanList(countryCode) 的 PAYG(P2G) 單一整包價——新增原始端點 POST /amlPortal/plan/portal/GetPlanList mock,返回結構與真後端逐字段一致{code,msg,data:{totalCount,items:[…planDetails[].price]},version}),切真後端免改前端。/plans-jp→JPN(目前唯一有價國家,mock ¥3,000 佔位);提交帶 functionCodes="EAVD"、幣種隨方案(JPY)。改動:public/js/plans-plus.js · plans-plus.html · translations.js + server/mock/plans-plus.js。支付本輪除外 聯絡我們(不下單)改接 contact復用 routes/contact.jsCreateFeedback;前端調用點由 /subscribe-offline/contact,推薦人路由由 BFF 補

    端到端驗證(npm run start:local + 本地 docker 後端):/editions(10 行業 + CPA 切換)· /plans/catalog(standard/cpa/addons 全對)· /countries(249)· /agents/:code(無數據 found:false)· /subscribe(new 各變體均返回 orderId、訂單入庫)。頁面 GET /plans-plus HTTP 200。

    +

    PAYG(single-query)本輪驗證(APP_ENV=test mock 模式):/single-query/options 返固定 4 項、無單價;POST /amlPortal/plan/portal/GetPlanList{countryCode:"JPN"}→P2G ¥3,000(JPY),{HKG}→空目錄(價格不可用);抽取 plans-plus.js 實際 extractPaygPlan/paygPlanPrice/fmtMoneyCur 跑真實響應:P2G / 3000 / JPY / ¥3,000 通過、HKG→null/0(禁用提交)。GET /plans-jp HTTP 200、含 ppSqPriceValue 橫幅。(真後端 GetPlanList 對接與 ConsumerPortal 支付後編排見 5.6,仍待後端。)

    @@ -609,15 +724,15 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P - + - - + + - + @@ -639,7 +754,7 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P - + @@ -650,11 +765,11 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P

    8.2 可能的後端改動(與後端團隊確認)

      -
    • BR/CI 必填:是否按 plans-plus 需求去除 CreateOrder 的 BR/CI 必填校驗(或僅對個人主體放開)。
    • -
    • 按郵箱查可續費租戶端點,返回聚合 currentSubscription(含 planId / 上期 price / 已購加值項)+ referrer。
    • +
    • BR/CI 非必填:已定去除 CreateOrder 的 BR/CI 必填校驗(注释 :154/:190,保留唯一性);請確認已在對接/部署環境應用(當前 working copy 仍 active)。
    • +
    • 按郵箱查可續費租戶已定 · 待實現 新增 POST Order/portal/queryRenewableTenantByEmail(入參 {Email}[AbpAutoAuth("Portal")])——先按 UserName=='admin' && Email== 定位租戶(可復用現 GetAllUsers() 全庫掃描、改按郵箱過濾,掃描成本可接受),返回聚合 currentSubscription(planId / 上期 price / 已購加值項,反查思路借用 GetCurrServiceInfo)+ referrer(AgentorCode/Name)。詳見 5.5。
    • topup 語義:確認 TenantRenewal(PlanList 僅加值項)是否「只疊配額不延期」,或新增 TenantTopup
    • KYC 計費口徑:確認 KYC plan 是否可按 PCS=月數 計月租。
    • -
    • single-query:是否新增後端按次查詢端點,還是短期 mock。
    • +
    • single-query:已用 ConsumerPortal 即時檢測,兩步由後端內部編排(見 5.6)。檢測內容固定 4 項(V/E/A/D)、定價取 GetPlanList(countryCode) 的 PAYG 方案價。待後端確認:① V2「支付完成」與後端編排的銜接端點/webhook;② 隨付即用專用「2C 租戶」+ AML.ConsumerPortal.Enable 功能 + 實時篩查權限(供後端落上下文,V2 無需持該租戶憑證);③ op→功能碼映射已定固定 EAVD——僅需確認後端是「V2 固定傳 EAVD」還是「後端寫死」;④ 收款金額對齊已確認:PAYG = 單一整包價目前 countryCode 僅日本(JPN),僅 /plans-jp 有 P2G 方案,餘國待種 PlanDetail 後再開。
    • 聯絡我們送推薦人待實現 已定方案——後端 CreateFeedbackAssignedAgentUserId 收件人字段(DTO + 實體 + 服務解析郵箱 + EF 遷移):有值發推薦人、否則發平台銷售。
    • (可選)Plan 增 bestValue/多語言 note/nameJP;edition 增多語言名稱。
    @@ -665,9 +780,9 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P - - - + + +
    #新增/變更對接影響狀態
    1單次查詢 single-query(按次付費,第 4 種流程)後端零支持,需新增或保留 mock需後端/mock
    1單次查詢 single-query(隨付即用,第 4 種流程)後端已支持(ConsumerPortal 即時檢測);兩步由 AML 後端在支付完成後內部編排(CreateConsumerLink→CreateConsumerOrder),V2 只下單+收款。檢測內容固定 4 項純展示不可選(V/E/A/D);定價取 GetPlanList(countryCode) 的 PAYG 方案價/plans-jp→JPN),映射與 2C 租戶均在後端(見 5.6)改造
    2聯絡我們/推薦人(發訂單摘要 · 不創建訂單復用既有 contact/CreateFeedback;「送推薦人」需後端加 AssignedAgentUserId 收件人字段(小改)+ BFF 拼裝 message復用+後端小改
    3agent tiers(推薦人可售級別過濾方案)SearchUser 不含 tiers,需經 GetPlanList(agentUserId) 推導BFF
    4agent email/phone(聯絡推薦人)AppUserDto 已含 Email/PhoneNumber,BFF 帶出BFF
    5主體類型 corp/individual(新租戶)個人主體無 BR/CI ⇒ 撞後端 BR/CI 必填阻塞
    6BR/CI 仍必填(更正舊版「已放開」結論)OrderService.cs:154,190 校驗仍生效需業務決策
    5主體類型 corp/individual(新租戶)個人主體無 BR/CI;BR/CI 已定非必填 ⇒ 不再阻塞(待 :154/:190 放開已應用)非阻塞
    6BR/CI 非必填(已定)注释 OrderService.cs:154,190(保留唯一性);當前 working copy 仍 active,待確認已放開待應用/核對
    7KYC 改設備月租(monthlyPrice × 月數)後端 KYC 單價 ⇒ 下單 PCS=月數(見 5.2)BFF/後端確認
    8jQuota 改選配套(package,非按量)後端 jQ plan 天然離散,BFF 映 packagesBFF
    9按郵箱查租戶 + 郵箱唯一單命中後端按名字搜,需按郵箱端點;不再多租戶消歧需後端
    9按郵箱查租戶 + 郵箱唯一單命中已定:後端新增 queryRenewableTenantByEmail(現端點 DB 層只按租戶名搜、郵箱查後回填→不能按郵箱過濾);不再多租戶消歧;新端點聚合 currentSubscription+referrer(見 5.5)需後端新增
    10續費頁展示 referrerTenantPropertyDto.AgentorCode/Name 映射BFF
    11續費續價 / 顯示舊方案(Req 9)續價後端不認前端 price;舊方案卡屬前端渲染後端確認
    12過期加購阻斷(須先續費)純前端門檻,無對接影響無影響
    routes/plans.js(新)GET /plans/catalog + filterPlan 拆分 + KYC 月租/jQ 配套 + 緩存 planDetailIdGetPlanList
    routes/countries.js已存在,直接掛載getCategoryByTypes
    routes/agents.js(新)GET /agents/:code → {code,name,tiers,email,phone,agentorId}SearchUserByCodeAndType (+GetPlanList)
    routes/tenants.js(新)GET /tenants/lookup?email= + currentSubscription 重建 + referrerqueryRenewableTenant(ByEmail)
    routes/tenants.js(新)GET /tenants/lookup?email= → 字段直通 + 命中態(none/unique) 包裝(currentSubscription/referrer 聚合由後端算好)新增 queryRenewableTenantByEmail
    routes/subscribe.js(新)POST /subscribe 分流 + PlanList 組裝(含 KYC PCS=月數)CreateOrder / TenantRenewal / (Topup)
    routes/contact.js已存在;「聯絡我們」復用之——把結構化 payload 序列化為 message 摘要 + 透傳 agentUserId,轉 CreateFeedback(收件人路由由後端 AssignedAgentUserId 承接)CreateFeedback
    mock/plans-plus.jssingle-query / payments 暫留 mocksubscribe-offline 改由 contact 承接(可保留為兼容別名)
    P0 · 本地種子補 Edition + Plan/PlanDetail 種子,對齊配置(第 6 章)後端返回非空目錄,可對接
    P1 · 只讀目錄editions + plans/catalog + countries(已就緒)+ agents頁面渲染方案/行業/國家,校驗推薦人
    P2 · 新購下單subscribe(new)(先解 BR/CI 阻塞)新租戶下單至「已提交成功」(支付除外)
    P3 · 續費/加購tenants/lookup(後端新端點)+ subscribe(renew/topup)續費/加購閉環(支付除外)
    P4 · 單次查詢/聯絡/支付single-query + 聯絡我們(接 contact API + 推薦人路由)+ payments(後端就緒後)完整流程 + 在線支付
    P2 · 新購下單subscribe(new)(BR/CI 已定非必填:注释 :154/:190)新租戶下單至「已提交成功」(支付除外)
    P3 · 續費/加購tenants/lookup(依賴後端新端點 queryRenewableTenantByEmail)+ subscribe(renew/topup)續費/加購閉環(支付除外);阻塞於後端新端點
    P4 · 單次查詢/聯絡/支付single-query(支付後接 ConsumerPortal 兩步:CreateConsumerLink→CreateConsumerOrder)+ 聯絡我們(接 contact API + 推薦人路由)+ payments(後端就緒後)完整流程 + 在線支付
    @@ -676,10 +791,10 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P

    9. 待確認問題清單

      -
    1. BR/CI 必填:關鍵 後端 CreateOrder 仍要求 BR 或 CI 至少一個(OrderService.cs:154,190)。plans-plus 新購(含個人主體)需不填也能下單——是否去除該校驗?還是前端把 corp BR 改必填、個人主體暫不開放?
    2. -
    3. 單次查詢:後端是否新增按次查詢(options + 下單 + 計費 + 結果投遞)?本輪是否先保留 mock?
    4. +
    5. BR/CI:已定非必填 plans-plus 新購(含個人主體 / 空 BR/CI)可下單。實現=注释 CreateOrderOrderService.cs:154,190(保留 :191 唯一性,空值安全)。待辦:確認該放開已在對接/部署環境生效(當前 working copy 兩處仍 active);並知悉全局影響(旧站共用 CreateOrder,空 BR 不再攔截)。
    6. +
    7. 單次查詢:已定方向 後端已支持——ConsumerPortal 即時檢測;調用方=AML 後端內部編排(支付完成後 CreateConsumerLinkCreateConsumerOrder,V2 只下單+收款,見 5.6)。本輪再定:檢測內容固定 4 項純展示不可選(證件核驗V / 名單篩查ES=E / AI 增強A / 失信D);收款金額取 GetPlanListcountryCode 的 PAYG(P2G) 方案價/plans-jp→JPN)。待後端確認:① V2「支付完成」與後端編排的銜接方式(後端持單 + 收 webhook,或 V2 支付後調後端專門 2C 觸發端點);② 隨付即用專用「2C 租戶」+ AML.ConsumerPortal.Enable + 實時篩查權限(供後端落上下文,V2 無需該租戶憑證);③ functionCodes="EAVD" 由 V2 固定傳、還是後端寫死;④ 已確認 PAYG = 單一整包價(非分項之和);目前 countryCode 僅日本(JPN),僅 /plans-jp 有 P2G 方案 + PlanDetail,餘國後續再種。
    8. 聯絡我們(不下單):已定方案 復用 contact/CreateFeedback 發送訂單摘要(不創建訂單/線索);「送推薦人」擬由後端 CreateFeedback.AssignedAgentUserId 承接(待實現,見 5.8)。待辦:後端加字段 + 服務路由 + EF 遷移;前端調用點由 /subscribe-offlinePOST /api/contact 並透傳 agentUserId;BFF 映為 AssignedAgentUserId
    9. -
    10. 租戶查詢:能否新增「按管理員郵箱查可續費租戶」,直接帶 currentSubscription(planId/上期價/已購加值項)+ referrer?
    11. +
    12. 租戶查詢:已定新增後端端點 queryRenewableTenantByEmail(email) 直接返回「租戶 + currentSubscription(planId/上期價/已購加值項)+ referrer」聚合(見 5.5)。待後端確認:① 「admin 郵箱」是否恆等於 UserName=='admin' 用戶的 Email(現 queryRenewableTenant 即此語義)——若存在非 admin 用戶名的租戶管理員,需改按角色定位;② 過期租戶是否照樣返回(供前端做臨期/過期展示 + topup 阻斷);③ 聚合 DTO 形態(新 RenewableTenantDto vs 擴 TenantPropertyDto)。
    13. topup:純加購走 TenantRenewal(PlanList 僅加值項)後端是否接受、語義是否「只疊配額不延期」?
    14. KYC 月租:KYC plan 是否可按 PCS=租賃月數 計費(planDetail.price 當月租單價)?
    15. 續費續價:後端 TenantRenewal 是否支持沿用上期價/自定義價?否則前端續費折扣僅展示、實際按目錄價。
    16. @@ -692,7 +807,7 @@ cross-env APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true P

    - 本分析基於源碼靜態閱讀 + 本地 docker 庫實測(2026-07):plans-plus.js / server/*(含 mock 契約)/ AMLPortal Controllers 與 Service / CustomIdentityUserController / 旧站 PlanService / appsettings.local.json 與本地數據庫。涉及後端行為(BR/CI 校驗、續費續價、topup / KYC 計費語義)以實際接口 + 後端確認為準。 + 本分析基於源碼靜態閱讀 + 本地 docker 庫實測(2026-07):plans-plus.js / server/*(含 mock 契約)/ AMLPortal Controllers 與 Service / iCON.Abp.AMLConsumerPortalControllerConsumerPortalService(單次查詢即時檢測)/ CustomIdentityUserController / 旧站 PlanService / appsettings.local.json 與本地數據庫。涉及後端行為(BR/CI 校驗、續費續價、topup / KYC 計費語義、單次查詢 2C 計費與鑑權)以實際接口 + 後端確認為準。