用户在 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 为准。