diff --git a/.gitignore b/.gitignore index 389f88a..9223439 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -docs/AML_Backend/docker-compose-local-dev/AML-local-dev.bak -justsolutionsWebV2/ -AML_Backend/ -AML_Frontend/ -justsolutionsWeb/ +/docs/AML_Backend/docker-compose-local-dev/AML-local-dev.bak +/justsolutionsWebV2/ +/AML_Backend/ +/AML_Frontend/ +/justsolutionsWeb/ + \ No newline at end of file diff --git a/docs/justsolutionsWebV2/KYC订阅字段与租用逻辑分析.html b/docs/justsolutionsWebV2/KYC订阅字段与租用逻辑分析.html new file mode 100644 index 0000000..519f93b --- /dev/null +++ b/docs/justsolutionsWebV2/KYC订阅字段与租用逻辑分析.html @@ -0,0 +1,327 @@ + + +
+ + +釐清 plans-plus.html 續費查詢頁「上次訂閱資訊」中的 KYC 欄位到底代表什麼、KYC 功能是否受「有沒有租設備」限制、以及是否每個租戶預設開啟;並記錄本次為此做的文案修正。
| 問題 | 答案 | 說明 |
|---|---|---|
| 「上次訂閱資訊」的 KYC 應該是「是否租用 KYC 設備」嗎? | +基本是 | +該值 = TenantProperty.EnableKYC,而它由「下單方案是否含 Tag1Code = KYC 的方案行」決定;此方案行在 plans-plus 對應加購項「KYC 讀卡設備租用」。故它實質表示「上次訂閱有無租 KYC 讀卡設備」。 |
+
| 有沒有租 KYC 設備,會影響用戶在系統中使用 KYC 功能嗎? | +只影響掃描入口 | +enableKYC 在前端只控制「ID Scan/機器掃描」按鈕的顯隱。「上傳」與「手動錄入」兩條路徑始終可用;後端 KYCService/KYCController 完全不校驗 EnableKYC。 |
+
| 「能否使用 KYC 功能」是每個租戶預設開啟的嗎? | +不是 | +EnableKYC 對一般租戶預設 false,僅在下單方案含 KYC 加購時為 true(續費沿用上次值)。唯一預設 true 的是 Host 宿主租戶。但因前端僅擋掃描按鈕,所有租戶預設仍可用「上傳/錄入」做 KYC。 |
+
plans-plus 續費頁「上次訂閱資訊」卡片的「已購增值 / Add-ons」列,經由這條鏈路取得 KYC 標記:
+ +previousAddonText() → currentSubscription.addons.kyc → BFF 直通 → CurrentSubscriptionAddonsDto.Kyc ← tp.EnableKYC| 層 | 內容 |
|---|---|
| 前端顯示 plans-plus.js:570-581 / plans-plus.html:347 |
+ showTenantInfo() 把 previousAddonText(s) 寫入 #ppTiAddons;當 addons.kyc === true 時,列表加入一個 KYC 標籤。 |
+
| 續費查詢 DTO RenewableTenantByEmailDto.cs:46-54 |
+ CurrentSubscriptionAddonsDto.Kyc,欄位註解明寫「是否已購 KYC(TenantProperty.EnableKYC)」。 |
+
| 後端聚合 OrderService.cs:2460 |
+ Addons = new CurrentSubscriptionAddonsDto { Kyc = tp.EnableKYC }——直接取當前生效 TenantProperty.EnableKYC。 |
+
| 底層欄位 TenantProperty.cs:69-71 |
+ public bool EnableKYC,註解「是否啟用了 KYC 功能」。命名雖是「功能開關」,但賦值來源只綁定「有沒有買 KYC 設備加購」(見下節)。 |
+
加購項對應:plans-plus 第 4 步的 KYC 加購項在 plans-plus.html:468 標題為「KYC ID Reader Rental / KYC 身份讀取設備租用」——一台實體讀卡機,按訂閱期租用。這正是產生 Tag1Code = KYC 方案行、進而把 EnableKYC 置 true 的東西。
EnableKYC 全庫僅有 3 種寫入來源(其餘皆為 EF 遷移快照),沒有任何「按功能開關」的入口:
| 場景 | 代碼 | 取值 |
|---|---|---|
| 新租戶下單 | +OrderService.cs:763 | +EnableKYC = checkHasEnableKYCInPlan(purchasePlanDetailList) |
+
| 續費(新開訂單路徑) | +OrderService.cs:2633 | +EnableKYC = checkHasEnableKYCInPlan(purchasePlanDetailList) |
+
| 續費(事件隊列生效路徑) | +OrderService.cs:872 | +EnableKYC = lastTenantProperty.EnableKYC(沿用上一份) |
+
| Host 宿主初始化 | +TenantPropertyDataSeeder.cs:63-79 | +EnableKYC = true(僅當 currentTenant.Id == null,即 Host) |
+
判定函式只看方案的 Tag1:
+// OrderService.cs:2694-2697
+private bool checkHasEnableKYCInPlan(List<PlanDetail> purchasePlanDetailList)
+{
+ return purchasePlanDetailList
+ .Where(a => a.Plan.Tag1Code == PlanTag1Enums.KYC.ToString()).Any();
+}
+
+ 推論:EnableKYC 語義上等同「該訂閱是否含 KYC 讀卡設備租用」,而不是一個獨立的「KYC 功能總開關」。所以把「上次訂閱資訊」裡的它讀作「是否租用 KYC 設備」是準確的。
AML_Frontend 的 KYC section 讀取當前租戶的 enableKYC:
// kyc-section.component.ts:147-148
+this.amlTenant.getCurrentTenantProperty$().subscribe(res => {
+ this.enableKYC = res?.enableKYC ?? false
+})
+
+ 而它只作用在「ID Scan(機器讀卡掃描)」這一個按鈕上(switchType === 1):
| 按鈕 | switchType | 受 enableKYC 控制? | 說明 |
|---|---|---|---|
| ID Scan(機器掃描) | 1 | 是(*ngIf="enableKYC") | 插實體讀卡機掃描證件;未租設備則隱藏。kyc-section.component.html:10 |
| Upload2(上傳) | 2 | 否 · 始終顯示 | 上傳證件圖片走 OCR/校驗。kyc-section.component.html:18-24 |
| Input(手動錄入) | 3 | 否 · 始終顯示 | 手動輸入證件資訊。kyc-section.component.html:26-32 |
後端 KYCService / KYCController 從不讀取 EnableKYC。全庫對該欄位的非遷移讀取僅有兩處:續費沿用(OrderService.cs:872)與續費查詢上報(OrderService.cs:2460)。因此它純粹是前端那顆掃描按鈕的顯隱旗標,不構成服務端鑑權。
結論:沒租設備的租戶照樣能用完整 KYC(上傳 / 手動錄入 → OCR、校驗、檢索),只是看不到「插讀卡機掃證件」這個入口。
「租 KYC 設備」= 解鎖前端硬體掃描按鈕,而非解鎖 KYC 功能本身。
EnableKYC 預設 false,僅當下單方案含 KYC 設備加購才為 true;續費時沿用上一份的值(OrderService.cs:872)。true 的(TenantPropertyDataSeeder.cs:63-79,currentTenant.Id == null),並非每個業務租戶。一句話:廣義 KYC(上傳/錄入)對所有租戶預設可用;EnableKYC(=是否租了 KYC 讀卡設備)預設關閉,它只額外解鎖前端「機器掃描證件」按鈕。
原本 plans-plus「上次訂閱資訊」把 KYC 加購項顯示為含糊的「KYC/KYB」(易被誤解成一種核查功能)。已改為與加購項一致的「KYC 設備租用」表述,以準確反映「是否租用 KYC 設備」。後端無改動。
| 位置 | 修改前 | 修改後 |
|---|---|---|
| plans-plus.js:575 ( previousAddonText,「已購增值」列) |
+ parts.push('KYC/KYB') |
+ parts.push(tr('ppx_addon_kyc_label')) |
+
| plans-plus.html:579 (訂單摘要 #ppSumKyc 靜態回退文字) |
+ KYC/KYB Verification |
+ KYC ID Reader Rental |
+
改用的 i18n 鍵 ppx_addon_kyc_label 三語皆已是租用表述:繁中「KYC 身份讀取設備租用」/ EN「KYC ID Reader Rental」/ 日「KYC IDリーダー機器レンタル」,故翻譯本體無需改動。訂單摘要 ppx_sum_kyc 的翻譯值原本就已是租用表述,本次僅同步修正其過期的 HTML 靜態回退文字。
驗證:本地以 npm run start:local 啟動 justsolutionsWebV2 即可看到「上次訂閱資訊 → 已購增值」與訂單摘要處的 KYC 顯示為「KYC 設備租用」表述。
| 角色 | 檔案 : 行 |
|---|---|
| 底層欄位定義 | AML_Backend/modules/iCON.Abp.AMLPortal/…/Domain/DbEntity/TenantProperty.cs:69-71 |
| 賦值:新租戶 / 續費 / 判定函式 | …/AMLPortal.Application/OrderService.cs:763、2633、872、2694-2697 |
| 續費查詢上報 KYC | …/AMLPortal.Application/OrderService.cs:2460 |
| 續費查詢 DTO | …/AMLPortal.Application.Contracts/OrderAppLayer/RenewableTenantByEmailDto.cs:46-54 |
| Host 預設 true | …/AMLPortal.Domain/Seeders/TenantPropertyDataSeeder.cs:63-79 |
| 前端讀 enableKYC / 掃描按鈕門檻 | AML_Frontend/…/kyc-section/kyc-section.component.ts:147-148、.html:10 |
| AML DTO 映射 | …/iCON.Abp.AML.Application/AMLAppAutoMapperProfile.cs:78 · …/TenantConfigLayer/TenantPropertyDto.cs:118 |
| plans-plus 顯示 / 加購項 | justsolutionsWebV2/public/js/plans-plus.js:570-581 · public/plans-plus.html:468、579 |
本文為程式碼閱讀分析結論,隨代碼演進可能變動;引用行號以撰寫時的倉庫狀態為準。
+ +用户在 plans-plus.html 提交订单后,后端如何处理;支付完成后又如何开通 / 续期 / 执行检测。
覆盖四类:新增租户(new) · 续费(renew) · 增值服务(topup) · 即用即付 / 单次查询(single)
+ + +所有请求先打到本站 /api/*,由 justsolutionsWebV2 的 Node/Express 服务器按环境分流:test 走本地 mock,其他环境把订阅类接口转成真实后端调用(订阅路由已实现真实对接,单次查询 / 在线支付本轮仍由 mock 兜底)。
collectPayload() 汇总 type + 方案 + 加值项 + 主体信息api.post() → /api/*APP_ENV / MOCK:真实订阅路由 vs mock 兜底CreateOrder;renew/topup → TenantRenewalCreateConsumerLink → CreateConsumerOrderProcessTenantEventQueue → 建/续租户ProcessConsumerPortalOrder → 按 functionCodes 跑检测下文每类订单单独给出完整链路。订阅三类(new/renew/topup)共用同一套「订单 → 支付回调 → 租户事件队列 Job」骨架;即用即付走另一套「即时检测链接 → 订单 → RabbitMQ → 检测编排」骨架。
+| 类型 | 前端 type | BFF 路由 | 后端接口 | 后端服务方法 | 订单枚举 | 支付后动作 |
|---|---|---|---|---|---|---|
| 新增租户 | new |
+ POST /api/subscriberoutes/subscribe.js |
+ /api/amlPortal/Order/portal/CreateOrder |
+ OrderService.CreateOrderOrderService.cs:146 |
+ OrderType=NewReg |
+ 新建 ABP 租户 + 按 edition 开关 feature + 建 TenantProperty | +
| 续费 | renew |
+ POST /api/subscribe |
+ /api/amlPortal/Order/portal/TenantRenewal |
+ OrderService.TenantRenewalOrderService.cs:2199 |
+ OrderType=Renewal |
+ 作废旧 TenantProperty,建新的(期限往后叠加、额度累加) | +
| 增值服务 | topup |
+ POST /api/subscribe |
+ /api/amlPortal/Order/portal/TenantRenewal |
+ OrderService.TenantRenewal(PlanList 只含加值项) |
+ OrderType=Renewal |
+ 同续费分支;但无基础方案 → 只叠加额度 / 用户数 / KYC,不延长期限 | +
| 即用即付 | single |
+ POST /api/single-query当前 mock |
+ 目标:/api/aml/ConsumerPortal/CreateConsumerLink → CreateConsumerOrder |
+ ConsumerPortalServiceConsumerPortalService.cs:964 / 325 |
+ ConsumerPortalOrder |
+ 发 RabbitMQ → 按 functionCodes 逐模块检测 → 邮件发结果 |
+
ppSubmit() plans-plus.js:1342:type==='single' → submitSingle()(走 /single-query + /payments/create);否则 → POST /subscribe。BFF 的 subscribe.js 再按 payload.type 决定调 CreateOrder(new)还是 TenantRenewal(renew / topup)。
+ POST /subscribe,type='new'getCatalogBundle(countryCode) 取目录 → buildPlanList() 把方案 + 各加值项映射为 {PlanId, PlanDetailId, PCS};resolveAgentId() 把推荐人代码解析成后端用户 Guid(AgentorId)。KYC 按订阅月数选期限变体。routes/subscribe.jsPOST /api/amlPortal/Order/portal/CreateOrderCustomer(默认密码)→ 建 Order(PenddingPaid / UnPaid,OrderType=NewReg)+ OrderDetail(记录期限 / JCount / QCount / 用户上限)。未指定 agent → 挂到顶级 salesAdmin。PlanPrice≤0 → PendingActive,发「激活邮件」,用户点邮件里的 ActiveFreeOrder 链接后再开通。IsOfflinePayment=true → PenddingAudit,等管理员 OrderAudit 审核后进入开通。MockupPayment() :2916:若 IsMockupPayment=true,读取 mock webhook 数据、内联直接调 OnPaymentSuccess()(等价于「立即支付成功」)。真实支付则等支付方回调。out_trade_no=OrderCode 找单;写 PaymentInfo(含 InvoiceNo)→ Order.OrderStatus=Paid / PaymentStatus=Paid / 记 PaidAmount / PaymentTime。幂等:已有 PaymentInfo 则直接 return。最后 InsertTenantEventQueue(order) 插入一条 EventType=Create / Status=Pending 的租户事件队列 :1371。Success(幂等)。CreateTenant()(ABP SaaS 建租户 + admin)。AMLPortal.Enable,按 edition → feature 映射 启用 / 禁用业务功能(行业决定可用模块)。TenantProperty:期限(结合前端 EffectiveStartTime + 各方案 period 叠加)、JCountLeft/QCountLeft(累加)、UserCountLimit=max(各方案)+附加用户、EnableKYC。TenantInfo(邮箱 / 国别 / BR/CI / 地址 / 联系人)。Order=Completed、队列 Success。失败按 RetryTimes 重试,超限 Failed。EffectiveStartTime(默认今天)为起点叠加。OrderService.cs:692-751续费前会先查回当前订阅:前端在第 1 步用管理员邮箱 ppLookupTenant() 命中 queryRenewableTenantByEmail,带出租户、当前方案、到期日、额度、推荐人,并默认沿用上次方案。
type='renew',payload 带 tenantId + 管理员邮箱 + 新方案 + 加值项。POST /subscribebuildPlanList()(含基础方案 + 加值项)→ 组 TenantRenewalParam(TargetTenantID + TenantAdminEmail + PlanList)。routes/subscribe.jsPOST /api/amlPortal/Order/portal/TenantRenewalIsActive 的当前 TenantProperty;校验 admin 邮箱与租户 admin 一致。复用 lastOrder 的公司 / 邮箱 / 行业 / BR/CI 等,建 Order(Renewal / PenddingPaid / UnPaid)+ OrderDetail。基础方案(Tag1=B)的服务起点:当前 EffectiveEndTime 未过期→接续到期日之后;已过期→从今天起。未指定 agent → 沿用上次 agent。EventType=Create 队列(同一队列,Job 内按 OrderType 区分分支)。IsActive TenantProperty → 置 IsActive=false → 建新一条 TenantProperty:JCountLeft/QCountLeft = 旧余额 + 本次购买(额度累加,续费不清零)UserCountLimit = max(旧, 各方案) + 附加用户;EnableKYC 沿用Order=Completed、队列 Success。JCountLeft/QCountLeft 在旧余额上累加,期限从原到期日往后接续(未过期时)。OrderService.cs:879-880, 888-929增值服务(加购)与续费共用同一后端接口 TenantRenewal,区别只在 PlanList 不含基础方案,只含加值项(增加使用人 / KYC 设备租赁 / jQuota 加量)。
type='topup';只勾选加值项。若订阅已过期 → 前端拦截,提示「先续费」(ppTopupBlocked)。if (payload.type !== 'topup') 才 push 基础方案 → topup 时跳过基础方案,只映射 users / kyc / jquota 三类加值项的 PlanId/PlanDetailId/PCS。routes/subscribe.js buildPlanList()POST /api/amlPortal/Order/portal/TenantRenewal(同续费接口)Renewal 订单 + OrderDetail。因 PlanList 无 Tag1=B 的基础方案,CurrentOrderServiceStartTime/EndTime 不会被设置。dtEffectiveEndTime 无 period 可叠加,沿用旧到期日)JCountLeft/QCountLeft(若买了 jQuota)、UserCountLimit(若买了增加使用人 AdlU)、KYC(若租设备)Order=Completed。OrderType=Renewal),差异完全由 PlanList 里有没有基础方案决定。因此「只加额度 / 只加用户 / 只租 KYC」而不延长期限,是 topup 的天然结果,而非后端另写了一套逻辑。即用即付不创建租户、不做订阅,而是「付一次、查一次、邮件发结果」。它对应后端 AML 模块的 即时检测链接(ConsumerPortal)一套流程,与订阅三类完全不同。
+ +DEFAULT_SQ_OPS:证件核验 V / 名单筛查 E / AI 增强 A / 失信 D),functionCodes='VEAD'。整包价来自 GetPlanList 的 PAYG(P2G) 方案。POST /single-query → POST /payments/create → 跳转 mock 收银台/single-query 只回一个 mock-sq-* 订单号;/payments/create 回 mock 收银台 URL;/payments/:pid/webhook 模拟支付成功事件。不触发任何真实检测。server/mock/plans-plus.jsConsumerCustomer,生成带 AccessToken 的 ConsumerLink,把 FunctionCodes 存到链接上(该链接允许 / 预设的检测项),发链接邮件。FunctionCodes 用计费表算价 → 建 ConsumerPortalOrder(PaymentStatus=Paid、OrderStatus=Pendding)→ 每个 functionCode 建一条 DetectionTask(BatchId=订单Id)→ 更新 ConsumerLink → 发 RabbitMQ 消息(订单 Id)。:398-411, :446ProcessConsumerPortalOrder(orderId)。DetectionService.RunDetectionModulesAsync(FunctionCodes=order.FunctionCodes, WaitForCompletion=true);全部模块完成后 Order=Success 并发结果邮件。functionCodes.Contains(...) 才执行:含 E/A → ES/AI 筛查;含 V → 证件核验;含 D → 失信查询;含 O → OCR;含 S → 风评;含 R → CDD 报告。未包含的 code 对应模块直接跳过。:230, :281, :363, :435, :448functionCodes 建任务并逐模块门控,计费也基于同一批 code。因此前端展示的检测项目应与真正传入的 functionCodes 同源,否则会「展示了却不跑 / 跑了却没展示」。FunctionCodeEnums:E=ES、A=AI、O=OCR、V=证件核验、D=失信、S=风评、R=CDD 报告。Basic/Enums.cs:2365订阅三类(new/renew/topup)支付成功后的开通链路一致,关键在于「回调只置订单已付 + 入队;真正建 / 续租户由后台 Job 异步完成」。这样解耦是为了幂等与失败重试。
+lock 串行化;调 OnPaymentSuccess。mock 支付时由 MockupPayment 内联触发同一方法。InsertTenantEventQueue(Create/Pending)。OrderId + EventType=Create + Status=Pending。IntervalSecondsOrderType 走 NewReg / 续费分支,建 / 续租户、开 feature、写 TenantProperty、发邮件、订单置 Completed。失败重试 RetryTimes 次后 Failed。Basic/Jobs/TenantEventQueueJob.cs纵向泳道从左到右:支付方 / 后端入口 / 服务方法 / 数据库 / 后台 Job / SaaS 建租户 / 邮件。上半部(A)是同步、发生在 HTTP 请求内;虚线是异步边界——请求返回时租户尚未创建;下半部(B)是后台周期 Job 稍后完成的真正开通。活性条表示该泳道在此期间处于活动状态。
+POST /api/amlPortal/Order/portal/PaymentWebhook(status=1、带 syssn);入口用 lock 串行化。mock 支付时由 MockupPayment 用本地 mock 数据内联触发同一入口,等价于「立即支付成功」。OrderService.cs:1160 / 2916PaymentWebhook → OnPaymentSuccess(param)。:1174out_trade_no = OrderCode 查订单(禁多租户过滤 + 允许软删)。:1181PaymentInfo(含 InvoiceNo);幂等点:若该订单已有 PaymentInfo → 直接 return,不重复处理(webhook 可能重复投递)。:1197-1217Order.OrderStatus=Paid、PaymentStatus=Paid、记 PaidAmount/PaymentTime(曾软删则恢复)。:1225-1241InsertTenantEventQueue(order) 插入 EventType=Create / Status=Pending(已存在则不重复插)。:1249 / 1371PaymentWebhook 返回 200。此刻租户 / 续期尚未生效 —— 真正开通在后台 Job 完成。TenantEventQueueJob 每 IntervalSeconds 跑一次)ProcessTenantEventQueue 扫描 Pending/Retry 队列,载入 Order(含 OrderDetails / PlanDetail)与 edition;Retry 项按 RetryMinutes × TryTimes 退避。:528-556, 542Success(幂等);否则 CreateTenant(name, editionId, adminEmail, adminPassword)(ABP SaaS 建租户 + admin)。:570-627CreateTenant 返回 tenantId。:630AMLPortal.Enable,按 edition → feature 映射启用 / 禁用业务功能(行业决定可用模块)。:632-661TenantProperty(期限叠加、JCountLeft/QCountLeft 累加、UserCountLimit、EnableKYC)→ 写 TenantInfo → Order=Completed、队列 Success、清缓存。:677-801, 964-967Retry(TryTimes+1),超 RetryTimes 置 Failed。:803-844, 972-986IsActive 的 TenantProperty,新建一条(额度 / 期限 / 用户在旧值上叠加),启用 edition feature,发续费邮件,Order=Completed。OrderService.cs:848-989| 关注点 | 机制 | 位置 |
|---|---|---|
| 支付幂等 | OnPaymentSuccess 若已写过 PaymentInfo 直接 return;webhook 可重复投递 | OrderService.cs:1213-1217 |
| 开通幂等 | Job 内若同名租户已存在,只补发邮件并置 Success,不重复建租户 | OrderService.cs:578-615 |
| 失败重试 | 队列 Retry + TryTimes + 退避(RetryMinutes×TryTimes),超 RetryTimes 置 Failed | OrderService.cs:542, 831-843 |
| 未支付清理 | ClearExpiredOrderCustomer:超 ExpireOrderMins 未支付的订单,NewReg 删客户信息 | OrderService.cs:1271 |
| mock 支付 | IsMockupPayment=true 时 CreateOrder / TenantRenewal 内联触发 OnPaymentSuccess(无需真实网关) | OrderService.cs:2916 |
注:即用即付订单建单即 PaymentStatus=Paid(当前实现下支付被前置 / 简化),真正的耗时在检测编排;订阅订单则是「先建单未付 → 付款 → 异步开通」。
edition → feature 映射启用对应业务功能(realestate / DPMS / VASP 等),行业决定租户可用的检测模块。OrderService.cs:636-661, 935-953JCountLeft/QCountLeft = 旧余额 + Σ(本次购买 × PCS);JCountLeftInitEffected 记录本次生效初值。UserCountLimit = max(旧值, 各基础方案 UserCountLimit) + Σ 附加用户(AdlU)×PCS。EnableKYC;KYC 设备按订阅月数选期限变体(固定总价,PCS=1)或回退按月租×月数。routes/subscribe.js buildPlanList()resolveAgentId → 后端 AgentorId;开通后逐级向上给 agent 发通知邮件。未填 → 挂顶级 salesAdmin。functionCode 一条 DetectionTask,RunDetectionModulesAsync 逐模块 Contains 门控;计费同源。DetectionService.cs:175ONLINE_PAYMENT_ENABLED = false plans-plus.js:13:「Confirm & Subscribe」按钮恒置灰,ppSubmit() 一进来就 return :1342。用户实际只能点「联络我们 / 联络推荐人」。
+ | 能力 | 设计 / 目标链路 | 当前原型实际 |
|---|---|---|
| 订阅下单(new/renew/topup) | /subscribe → CreateOrder/TenantRenewal → 支付 → 开通 | 走线下:ppContactSubmit → POST /subscribe-offline → 后端 customer/CreateFeedback(只发线索,不建订单)routes/subscribe.js |
| 即用即付(single) | CreateConsumerLink → CreateConsumerOrder → RabbitMQ → 检测 | 纯 mock:/single-query + /payments/* 只回模拟数据,不触发真实检测server/mock/plans-plus.js |
| 真实订阅路由 | editions / plans / agents / tenants / subscribe 已对接 AMLPortal | 已实现,ONLINE_PAYMENT_ENABLED=true 后即可启用在线下单 |
ONLINE_PAYMENT_ENABLED=true;② 订阅链路已就绪(subscribe.js → CreateOrder/TenantRenewal),后端配 IsMockupPayment 或接真实支付;③ 即用即付需把 /single-query 从 mock 换成真正的 ConsumerPortal.CreateConsumerLink/CreateConsumerOrder,并保证前端展示的检测项与传入 functionCodes 同源。
+ 本流程单基于源码撰写,仅描述订单 / 支付 / 开通 / 检测编排主链路;邮件模板、发票生成、agent 组织树、额度预警等旁路仅在需要处点到为止。若后端代码调整,请以最新 OrderService.cs / ConsumerPortalService.cs / DetectionService.cs 为准。
+ +