隨付即用(PAYG)· Agent 展示 —— 接口需求单

提出方:justsolutionsWebV2(Node BFF + 静态站,官网 v2)。承接方:AML_Backend — iCON.Abp.AMLPortal / iCON.Abp.AML。

范围只有两块:官网的隨付即用页(public/{region}/pages/pay-as-you-go.html)与三处共用的代理商卡片展示(_shared/js/agent-cards.js,contact / plans-plus / pay-as-you-go)。订阅主流程(新购 / 续费 / 加购)不在本单内。

复核日期 2026-08-19 · 对照后端提交 ef733325 · 对照前端 justsolutionsWebV2@main。下文所有「现状」都是照着这两份代码读出来的,不是推测。

目录

  1. 怎么读这份单子
  2. 需求汇总表
  3. 一 · 隨付即用(A1–A10)
  4. 二 · Agent 展示(B1–B7)
  5. 附录 · 现有端点与枚举速查

怎么读这份单子

每条需求都按同一格式写:现状(后端现在是什么样,附代码位置)→ 问题(这个现状让前端付出了什么代价)→ 建议(希望后端提供什么)→ 不做的后果。前端已经落地的临时方案一律写明,方便后端就绪后对照删除。

优先级含义
P0阻断:真实环境跑不通、或已经在往数据库里写脏数据、或存在数据泄露。上线前必须解决。
P1完善:现在能跑,但形态不对 —— 前端靠约定/猜测撑着,后端一改就静默出错,或用户体验有明显缺口。
P2口径确认:多半只需要后端回一句「是/不是」,或补一个配置项,不一定要写代码。
一个共同的背景 官网 v2 不直接打后端:前端 → Node BFF(justsolutionsWebV2/server/)→ AMLPortal 门户端点([AbpAutoAuth("Portal")],免 token)。所以下面凡是说「BFF 现在自己兜着」,都指 server/routes/*.js 里写了一段本不该由前端承担的业务逻辑 —— 那正是希望搬回后端的部分。

需求汇总表

编号需求涉及端点 / DTO优先级
A1优惠码校验端点 + 建单入参新端点 · CreatePayAsYouGoOrderParamP0
A2零价(免付款)单的真实路径,别再借线下审核单CreatePayAsYouGoOrder · Order 实体P0
A3SubjectName 允许为空(主体改由用户在检测链接页自填)CreatePayAsYouGoOrder · UpdateOfflinePaygOrder · ActivatePaygOrderP0
A4建单时校验该 agent 是否真的承接 P2G 方案CreatePayAsYouGoOrder(AgentUserPlan)P0
A5权威的隨付即用报价端点,替掉三标签约定新端点 GetPayAsYouGoOfferP1
A6按订单号查 PAYG 单状态 / 重发检测链接新端点 QueryPaygOrder(扩 QueryOrderPayable 亦可)P1
A7本单包含哪些检测项(4 项固定)要能查新端点 / 并入 A5P1
A8香港以外市场的在线收款网关PaymentGatewayEnums · PaymentWebhookP1
A9站点配置表(IsPayAsYouGoEnabled 等)后端无此表,现写死在前端注册表P2
A10TwoC.ExpireOrderMins 口径确认(含配 0 = 永不过期)ClearExpiredPaygOrderP2
B1AgentByCountryDto 补 AgentRemark,消掉 BFF 的 N+1GetAgentsByCountryP0
B2匿名的用户查询端点返回了完整用户对象(含邮箱电话)SearchUserByCodeAndTypeP0
B3代理列表要过滤停用用户、并按用户去重GetAgentsByCountryP0
B4展示字段扩展(公司 / 城市 / 语言 / 网站 / 头像)与备注换行口径AgentUserSetting · AgentRemarkP1
B5上架开关 IsPublished + 排序 DisplayOrderAgentUserSettingP1
B6线下收款方式要能给出收款指引,而不只是一个枚举名GetAgentPaymentMethodsByCodeP1
B7代理列表支持按方案品类过滤(A4 的另一种实现口)GetAgentsByCountryParamP1

一 · 隨付即用(Pay-As-You-Go)

页面 public/{region}/pages/pay-as-you-go.html + _shared/js/pay-as-you-go.js;BFF server/routes/single-query.js、promos.js、payments.js。三条取件路径:优惠码免付款 / 在线支付(目前只有香港 · QFPay)/ 联络代理商(线下)。

A1 优惠码校验端点 + 建单入参 P0

页面的默认取件方式就是优惠码,而后端至今没有任何优惠码概念。

现状 CreatePayAsYouGoOrderParam 只有 PlanId / PlanDetailId / SubjectType / SubjectName / ContactEmail / IsOfflinePayment / AgentCode / AgentorId,价格一律取 PlanDetail.Price,既无优惠码入参也无折扣位;Order 实体同样没有 PromoCode / DiscountAmount。
AML_Backend/modules/iCON.Abp.AMLPortal/…/OrderAppLayer/CreatePayAsYouGoOrderParam.cs · Domain/DbEntity/Order.cs
前端现状 BFF 里放了一个空壳模块 server/routes/promos.js:mock 环境(APP_ENV=test)用内置码表让流程能演示,真实环境一律判无效并在日志点名。也就是说现在真实站点上没有任何一个优惠码是能用的。校验必须在服务端做(建单时 single-query.js 会回头再验一次),因为前端那句「已套用」只是显示。
justsolutionsWebV2/server/routes/promos.js:62 · server/routes/single-query.js:130
建议

① 一个校验端点(门户匿名可调,与其余 portal 端点同层):

POST /api/amlPortal/Promo/portal/VerifyPromoCode
{ "Code": "FREEQ2026", "CountryCode": "HKG", "Scene": "PayAsYouGo" }

→ { "code": 0, "data": {
      "code": "FREEQ2026",
      "kind": "Free",            // Free | Percent | Amount —— 前端按 kind 分流
      "value": 0,                // Percent: 折扣率;Amount: 直减额;Free: 0
      "currencyCode": "HKD",     // kind=Amount 时必需
      "labelEN": "...", "labelCN": "...", "labelJP": "...",
      "validTo": "2026-12-31T23:59:59",
      "remainingCount": 998      // 可为 null(不限量)
   } }

② CreatePayAsYouGoOrderParam 增 PromoCode,后端建单时再验一次并自行算价(前端传来的金额一概不采信),把 PromoCode / DiscountAmount / 实际应付额落到 Order 上。

前端目前只用得上 kind: "Free" 一种 —— 页面流程是「有码就不用付款」。折扣型(打完折还要收余额)会多出一条分支,等后端把规则定下来再一起做,接口先把 kind 留出来即可。

不做的后果优惠码功能在真实环境等于不存在(页面上永远显示「优惠码无效」)。市场投放的码没有一个能核销。
就绪后BFF 只改 promos.js 的 verifyPromo() 一个函数,路由与建单流程都不用动。

A2 零价(免付款)单的真实路径 P0

现在的免单是拿「线下待审核单」顶替的,而且金额还是原价。

现状 后端没有 PAYG 的零价路径。BFF 只能把优惠码免单按 IsOfflinePayment=true 建(PenddingAudit + 邮件通知归属 agent),但 Order.PlanPrice 仍是全价。
justsolutionsWebV2/server/routes/single-query.js:144 · AML_Backend/…/OrderService.cs:1894
问题 代理 / 财务在订单列表里看到的是一张全价待审核单,跟「真的要向客户收钱的线下单」长得一模一样,无从区分,也无从对账。免单量一大,这批脏单就得靠人肉记忆去认。
对照:订阅侧是有零价路径的(PendingActive + 激活邮件 + ActiveFreeOrder),PAYG 侧没有对应物。
AML_Backend/…/OrderController.cs:212 ActiveFreeOrder
建议 应付额为 0 时(无论是优惠码还是将来别的原因)走独立路径:PaymentGateway = null(实体注释里已经写明「免费单不经任何收款渠道,为 null」),直接置已付并调 ActivatePaygOrder 发检测链接;或与订阅侧对齐,先 PendingActive + 激活邮件,用户点击后再发链接。两种都行,请后端定一种。
订单上要留得下痕迹:PromoCode、DiscountAmount、PayableAmount(或明确 PlanPrice 为原价、PaidAmount=0 的口径)。
接口影响CreatePayAsYouGoOrder 的响应需要能让前端判断「这单不用付款、链接已发/待激活」 —— 目前 BFF 是自己拼 requiresPayment 字段给前端的,后端给出权威值最好。
不做的后果免单继续污染线下单池;且免单用户拿不到自动发出的检测链接,全靠人工跟进。

A3 SubjectName 允许为空 P0

购买页已经不收集检测主体了(付款后由用户在检测链接页自填),但后端这条链只拆了一半。

现状
  • OrderService.cs:1835 —— 必填校验已被注释掉;
  • OrderService.cs:1899 —— 仍是 SubjectName = input.SubjectName.Trim(),传 null 直接 NullReferenceException;
  • ActivatePaygOrder 用 order.SubjectName 作 ConsumerCustomer.Name 建检测链接;
  • UpdateOfflinePaygOrder 仍强制 SubjectName 非空 —— 管理端确认线下收款前必须先补一个主体名。
AML_Backend/…/OrderService.cs:1835 / 1899 / 667 · AML/…/ConsumerPortalService.cs:653
前端现状 BFF 被迫塞占位:优先用联系人姓名,其次结果接收邮箱。这个占位会一路流进 Order.SubjectName 和 ConsumerCustomer.Name,也就是检测客户会被命名成下单人的名字或一串邮箱。
justsolutionsWebV2/server/routes/single-query.js:123-127
建议 请后端二选一并明确告知:
(a) 全链路允许空主体 —— 建单接受 null(input.SubjectName?.Trim())、ActivatePaygOrder 建链接时主体名可空并由用户在链接页填写后回填、管理端改单也放开必填;
(b) 主体仍必须在下单时收集 —— 那前端把这一栏加回购买页。
现在这种「校验放开了但 .Trim() 还在、管理端又强制」的中间态是最糟的:前端不敢传空,只能造假数据。
不做的后果订单表与检测客户表持续被占位数据污染,且这批数据事后无法区分真假。

A4 建单时校验 agent 是否承接 P2G 方案 P0

订阅侧严格按 AgentUserPlan 裁剪可售方案,PAYG 侧完全不看。

现状 CreatePayAsYouGoOrder 对 AgentCode 只校验「这个人存在」(SearchUserByCodeAndType 有结果就取 users[0].Id),不检查 AgentUserPlan 里有没有这条 P2G 计划的关联。
AML_Backend/…/OrderService.cs:1871-1879 ↔ 对照 PlanService.cs:100-116(GetPlanList 的 AgentCode 裁剪)
问题 隨付即用页的代理卡片列的是该市场的全部代理(GetAgentsByCountry 不带方案维度),用户完全可能选到一个根本不做隨付即用的代理。单照建、邮件照发给对方,对方收到一笔自己不承接的业务。
这跟订阅页的取向也不一致 —— 那边「代理没有关联方案 = 不能买」是前端硬阻断的(agentNoPlans)。
建议建单时校验 AgentUserPlan 是否包含该 PlanId/PlanDetailId,不含则明确报错(前端会把错误原文展示给用户)。如果更希望在选择阶段就挡住,见 B7 —— 两者做一个即可,都做更好。
不做的后果线下单派给错误的代理,用户等不到跟进;代理侧收到无法处理的订单邮件。

A5 权威的隨付即用报价端点 P1

现在价格是靠三个标签的约定,在两个地方各筛了一遍。

现状 没有专门的 PAYG 报价端点。前端和 BFF 各自调 GetPlanList(countryCode),再按 tag1Code=B · tag2Code=P2G · tag3Code=SP 三个标签从一百条方案里挑出「零售直客价」那条(P2G 里还混着代理批发价 A6/A8 与 jQuota 充值包,所以三个标签一个都不能少)。
justsolutionsWebV2/public/_shared/js/pay-as-you-go.js:104 · server/routes/single-query.js:77
问题
  • 同一套筛选逻辑写了两份,改一处漏一处就会出现「页面标价」与「实际结算价」不一致;
  • 标签是后台可配的自由文本 —— 运营改一次标签,页面就静默变成「暂无价格」,没有任何报错;
  • 某市场若配出两条 SP,取到哪条取决于返回顺序。
建议
POST /api/amlPortal/plan/portal/GetPayAsYouGoOffer
{ "CountryCode": "HKG" }

→ { "code": 0, "data": {
      "planId": "...", "planDetailId": "...",
      "price": 200, "taxAmount": 0, "currencyCode": "HKD",
      "nameEN": "Pay-As-You-Go", "nameCN": "隨付即用", "nameJP": "従量課金",
      "functionCodes": ["V","E","A","D"],      // 见 A7
      "isAvailable": true                       // 该市场是否开售,见 A9
   } }

要点是「本市场的隨付即用卖什么价」这个判断由后端一次给准,前端不再需要知道 tag 体系的存在。

不做的后果标签或目录一动,官网静默显示「暂无价格」,且下单入口一起失效;排查要跨两个仓库。

A6 按订单号查 PAYG 单状态 / 重发检测链接 P1

付款成功之后,前端就彻底失明了。

现状 唯一可查的是 QueryOrderPayable,它只回「能不能付 + 金额」(payable / reason / amount),供付款失败后的「重新付款」判断用。检测链接由 ActivatePaygOrder 生成后直接发邮件,前端拿不到任何状态。
AML_Backend/…/OrderService.cs:1545 · AML/…/ConsumerPortalService.cs:611
前端现状付款返回页只能说「处理中,结果发邮件」,且明确不宣称已开通。用户没收到邮件时,页面上没有任何自助出路。
justsolutionsWebV2/public/_shared/js/pay-return.js:1-20
建议
POST /api/amlPortal/Order/portal/QueryPaygOrder
{ "OrderCode": "P26...", "ContactEmail": "a@b.com" }   // 邮箱作为轻量校验,防止按单号枚举

→ { "code": 0, "data": {
      "orderCode": "...", "orderStatus": 400, "paymentStatus": 1,
      "linkIssued": true, "linkSentTo": "a***@b.com",
      "canResendLink": true
   } }

配套一个 ResendPaygLink(同样带邮箱校验 + 频率限制)就能把「没收到邮件」这条最常见的客诉在页面上自助解决。

不做的后果「付了钱没收到链接」只能走人工客服,且客服也要到后台一条条查。

A7 本单包含哪些检测项要能查 P1

现状 检测项固定 4 项(FunctionCodeEnums 的 V 证件核验 / E 名单筛查 / A AI 增强 / D 失信),由后端 GetPaygFunctionCodes() 内部写死,没有任何端点能读到。mock 环境下 BFF 自己造了一份 GET /api/single-query/options 供演示,真实环境没有对应物。
justsolutionsWebV2/server/mock/plans-plus.js:381(仅 mock)
问题正式页面因此干脆不展示「这次买的包含什么」—— 一个按次付费的产品,页面上说不清卖的是什么。
建议随 A5 的报价一起返回 functionCodes,外加一份多语言名称/说明(或给一个 GetFunctionCodeDisplay 字典端点,前端自行拼装)。若产品口径上这 4 项会随方案变化,就更必须由后端给了。
不做的后果页面继续只卖一个价格、不说明内容;文案若前端自己写死,后端调整检测项时官网不会跟着变。

A8 香港以外市场的在线收款网关 P1

现状PaymentGatewayEnums 只有 Offline=100 与 QFPay=200,QFPay 只做 HK/HKD。
问题global 站(市场 All,USD)已经开了隨付即用,但没有可用网关 —— 那里的用户只能走「联络代理商」线下。日本站目前整个不提供隨付即用,将来要开也是同一个问题。
建议确认第二个网关的选型与时间点;接入时需要 PaymentGatewayEnums 增值 + PaymentWebhook 增分支 + 代理支付方式配置里可选。前端侧只需在 ONLINE_PAYMENT_MARKETS 加一个市场码,其余逻辑已经按「市场 → 是否开通线上收款」写好了。
不做的后果非香港市场的隨付即用实际上只有线下一条路,转化率受限。

A9 站点配置表(IsPayAsYouGoEnabled 等) P2

现状设计上「本站是否提供隨付即用」应由后端站点配置 SiteConfigs.IsPayAsYouGoEnabled 决定,但该表与字段在后端各分支、本地库、线上 swagger 均查无。前端只能在注册表里按站点写死(香港 ✓ / 日本 ✗ / 其他國家 ✓)。同一档的还有各地区币别与支付方式(多地区设计 Q7 仍待定)。
justsolutionsWebV2/server/regions.js:182
建议确认这张表要不要做。要做的话给一个门户可读端点即可(GetSiteConfigs() → 每个市场的 isPayAsYouGoEnabled / isSubscriptionEnabled / currencyCode / paymentMethods),前端改一个函数即可接上,调用方全都不动。
不做的后果开关每次调整都要改前端代码 + 发版;运营改不了。

A10 TwoC.ExpireOrderMins 口径确认 P2

现状 PAYG 单的过期清理走独立任务 ClearExpiredPaygOrder,用的是 TwoC.ExpireOrderMins(不是订阅单那个 Portal.ExpireOrderMins),且配成 0 或不配就永不过期;过期只置 Expired 不软删(晚到的回调仍能复活)。
AML_Backend/modules/iCON.Abp.AML/…/ConsumerPortalService.cs:696
要确认各环境这两个值分别配的是多少、是否一致。前端「付款失败 → 还能不能重付」的时限提示目前是按 5 分钟写的(那是订阅单的口径)。若 PAYG 用了别的值,页面文案要跟着改。
不做的后果用户被告知的时限与实际不符;线上配 0 时会积压永不过期的待支付单。

二 · Agent 展示

三个页面共用一个组件 _shared/js/agent-cards.js:contact(侧栏代理卡片,选中即把查询指派给对方)、plans-plus(推薦人,决定可售方案与支付方式)、pay-as-you-go(联络代理商取件)。数据来自 GET /api/agents?countryCode= 与 GET /api/agents/:code,BFF 实现在 server/routes/agents.js。

B1 AgentByCountryDto 补 AgentRemark P0

卡片正文就是这段备注,而列表端点偏偏不给 —— BFF 只能一个一个再查一遍。

现状 GetAgentsByCountry 返回的 AgentByCountryDto 只有 AgentUserId / UserName / Name / UserCode / CountryCode。而卡片正文要的 AgentRemark(代理自填的介绍 / 联络方式 / 收款账号)是 IdentityUser 的扩展属性,后端其他地方都在用(SaveAgentUserSetting、SaveMyAgentRemark、AgentUserSettingDto、线下单通知邮件),唯独这个列表 DTO 没带。
AML_Backend/…/PlanAppLayer/AgentByCountryDto.cs · PlanService.cs:499-539
前端现状 BFF 拿到列表后,对每个 agent 再扇出一次 GET /api/identity/users/SearchUserByCodeAndType/{code}/true 去捞备注(N+1),靠一个 5 分钟的按市场整表缓存硬撑;单个查失败就让那张卡备注留空,不拖垮整张列表。
justsolutionsWebV2/server/routes/agents.js:70-84
建议AgentByCountryDto 增 AgentRemark(user.GetProperty<string>("AgentRemark"),PlanService.cs:415 已有现成写法)。一并考虑 B4 的其余展示字段,一次加完。
不做的后果每次打开三个页面中任意一个,后端就要多承受 N 次用户查询;且这个 N+1 正是 B2 那个泄露端点的唯一调用理由。

B2 匿名端点返回了完整用户对象 P0

这一条是安全问题,独立于前端要不要用它。

现状 GET /api/identity/users/SearchUserByCodeAndType/{userCode}/{isFullMatch} 标着 [AllowAnonymous],返回完整的 AppUserDto —— 含 email、phoneNumber 和全部 extraProperties。isFullMatchUserCode=false 时 UserCode 还是模糊匹配(Contains)。
AML_Backend/src/iCON.Abp.FX.HttpApi/Controllers/CustomIdentityUserController.cs:304-311 · EntityFrameworkCore/CustomIdentityUserRepository.cs:52
问题任何人不带 token、用一个短前缀做模糊匹配,就能把平台上的代理用户连同邮箱、电话、全部扩展属性成批拉走。
建议
  • 做完 B1 后,前端就不再需要这个端点(BFF 会把这段扇出删掉)—— 届时请把它收回鉴权,或至少禁掉匿名下的模糊匹配;
  • 若仍要保留匿名可调,请换成 portal 专用的精简 DTO(只给 userCode / name / agentRemark),别把 AppUserDto 直接吐出来。
顺带BFF 的 GET /api/agents/:code(详情)目前也靠它取邮箱/电话,用于「联络代理商」成功后展示对方联络方式。这个能力仍需要 —— 建议做成 portal 侧的 GetAgentPublicProfile(agentCode),字段由后端决定哪些可公开。
不做的后果用户联络方式持续处于可匿名枚举状态。

B3 列表要过滤停用用户、并按用户去重 P0

现状 GetAgentsByCountry 是 foreach (settings) 后按 AgentUserId 找用户拼结果,取用户用的 GetUsersByIDs 不看 IsActive。
AML_Backend/…/PlanService.cs:507-537 · CustomIdentityUserRepository.cs:108
问题
  • 停用/离职的代理仍会公开展示,而且可被选中下单 —— 线下单会派给一个已经停用的账号;
  • 遍历的是 AgentUserSetting 而不是用户:同一 agent 在同一国家有两条设置记录时,卡片会重复出现两张。
建议列表过滤 IsActive == true(或把 isActive 返回给前端由调用方决定),并按 AgentUserId 去重。前端不打算自己猜哪个代理还有效。
不做的后果官网公开页面上出现失效代理,用户的查询/订单进入无人跟进的黑洞。

B4 展示字段扩展与备注换行口径 P1

现在一张代理卡片上能显示的东西,只有姓名、代码和一段自由文本。

现状卡片 = 头像占位符(写死的 emoji)+ name + userCode + agentRemark 纯文本。
justsolutionsWebV2/public/_shared/js/agent-cards.js:76-92
建议

若产品上希望「代理展示」是一块真正的展示位(而不只是一个选择器),需要后端在 AgentUserSetting 或用户扩展属性上补字段,并从门户 DTO 返回。建议字段:

字段用途
CompanyName卡片主标题(个人名之外的机构身份)
City / CountryCode「就近选代理」的排序与筛选依据
Languages服务语言(zh-HK;en;ja),多语言站点按当前语言优先展示
WebsiteUrl卡片上的外链
LogoUrl / AvatarUrl替掉现在写死的 emoji 占位
Industries擅长行业,可与订阅页的所屬行業联动

另需确认 AgentRemark 的换行/富文本口径:邮件侧已经在做 \n → <br /> 的转换(EmailMessageService.cs:1302),而网页侧是纯转义输出(防 XSS),所以代理在后台敲的换行到了官网会被压平成一段。要么约定它是纯文本(后台输入框也别让人换行),要么约定它保留换行、由前端按行渲染。

不做的后果代理展示停留在「一段文字」的水平,无法做筛选、排序或任何视觉区分。

B5 上架开关 + 排序 P1

现状AgentUserSetting 只有 AgentUserId + CountryCode 两个业务字段。卡片顺序 = 数据库返回顺序,没有任何控制手段。
问题无法「让某个代理暂时不对外展示但保留其存量客户关系」,也无法把主推代理排在前面。当前唯一的下架手段是删掉设置记录 —— 那会连带影响已有归属关系。
建议AgentUserSetting 增 IsPublished(默认 true)与 DisplayOrder(默认 0,升序),GetAgentsByCountry 按 IsPublished=true 过滤并按 DisplayOrder, Name 排序;SaveAgentUserSettingParam 同步加这两个字段供后台维护。
不做的后果代理展示顺序不可控;下架只能靠删数据。

B6 线下收款方式要给出收款指引 P1

现状GetAgentPaymentMethodsByCode 返回的是网关枚举列表 —— { value, name, displayName },也就是 Offline / QFPay 两个名字。
AML_Backend/…/PlanService.cs:545-578
问题用户选了「联络代理商 / 线下付款」之后,页面上给不出任何下一步:转账到哪、联系谁、要备注什么,全靠事后邮件。现在只能把代理的 AgentRemark 当收款指引用 —— 但那是一段自由文本,格式无保证。
建议为 Offline 这一项附带结构化收款信息(收款账户 / 收款人 / 银行 / 备注模板,或至少一段专门的 OfflinePaymentInstruction 字段),与介绍性质的 AgentRemark 分开。
顺带请确认「该代理没有配置任何支付方式」的语义:BFF 现在把查询失败与明确的空列表分开处理(失败 → null → 退回市场默认方式;空数组 → 该代理确实不支持任何网关 → 关闭下单入口)。若后端在未配置时也返回空数组,会让这些代理整批不可下单。
不做的后果线下付款流程在页面上是断的,全量依赖人工邮件跟进。

B7 代理列表支持按方案品类过滤 P1

现状GetAgentsByCountryParam 只有 CountryCode,返回的是该国全部代理,不带任何业务维度。
问题隨付即用页因此列出了包括「不承接隨付即用」在内的所有代理(见 A4)。订阅页那边前端是靠改选代理后向 GET /api/plans/available 重查来发现「这个代理没有可售方案」的 —— 属于事后补救,而且多一次往返。
建议GetAgentsByCountryParam 增可选 PlanTag2Code(或 PlanId),按 AgentUserPlan 裁剪出真正承接该品类的代理。这样卡片列表从一开始就只显示可选项,用户不会选到死路。
与 A4 是同一问题的两个口子:A4 是建单兜底(必须做),B7 是选择阶段前置(体验)。
不做的后果用户在代理卡片里选中一个无效选项,要到提交时才被拒。

附录 · 现有端点与枚举速查

本单涉及的现有端点

端点鉴权用途 / 本单相关条目
POST /api/amlPortal/Order/portal/CreatePayAsYouGoOrderAbpAutoAuth("Portal")PAYG 建单 —— A1 / A2 / A3 / A4
POST /api/amlPortal/Order/portal/QueryOrderPayableAbpAutoAuth("Portal")订单能否重付 —— A6 的现有近亲
POST /api/amlPortal/Order/portal/PaymentWebhookAbpAutoAuth("Portal")QFPay 回调 —— A8
GET /api/amlPortal/Order/ActiveFreeOrderAbpAutoAuth("Portal")订阅侧零价单激活 —— A2 的参照物(PAYG 无对应)
POST /api/amlPortal/plan/portal/GetPlanListAbpAutoAuth("Portal")方案目录(PAYG 价格现在从这里筛)—— A5
POST /api/amlPortal/plan/portal/GetAgentsByCountryAbpAutoAuth("Portal")代理列表 —— B1 / B3 / B5 / B7
POST /api/amlPortal/plan/portal/GetAgentPaymentMethodsByCodeAbpAutoAuth("Portal")代理支付方式 —— B6
GET /api/identity/users/SearchUserByCodeAndType/{code}/{full}AllowAnonymousBFF 靠它补代理备注/联络方式 —— B1 / B2

枚举速查

枚举取值
OrderTypeEnumsNewReg=100 · Renewal=200 · PayAsYouGo=400
OrderStatusEnumsPendingActive=100 · PenddingPaid=200 · Paid=300 · Completed=400 · Expired=500 · PenddingAudit=600
PaymentGatewayEnumsOffline=100 · QFPay=200(免费单为 null)
FunctionCodeEnumsPAYG 固定 4 项:V 证件核验 · E ES 名单筛查 · A AI 检测 · D 失信查询
市场码HKG(香港站)· JPN(日本站)· All(其他國家站,注意与 Plan.CountryCode='*' 全国家通用不是一回事)

PAYG 现有链路(供对照)

路径订单落点后续
在线支付PenddingPaid + QFPay收银台 → PaymentWebhook → OnPaygPaymentSuccess → ActivatePaygOrder(建检测链接 + 发邮件 + 置 Completed)
联络代理商PenddingAudit + OfflineSendOfflinePaygOrderMailToAgent → 人工确认收款(管理端 UpdateOfflinePaygOrder 需补主体名)→ ActivatePaygOrder
优惠码免单缺 目前混用「联络代理商」那条线且金额为全价 —— 正是 A2 要解决的。
前端侧的对应改动 A1 就绪 → 改 server/routes/promos.js 的 verifyPromo();A5 就绪 → 删 pay-as-you-go.js 与 single-query.js 里两份标签筛选;A9 就绪 → 改 server/regions.js 的 payAsYouGoEnabledFor();B1 就绪 → 删 server/routes/agents.js 的 fetchAgentRemark 扇出(同时解掉 B2 的调用理由)。这些位置在代码里都已写好注释标明「后端就绪后改这一处」。