From 315a7de3e43a2260cf4f9efa8e1edd824ce5c0f2 Mon Sep 17 00:00:00 2001 From: break Date: Fri, 11 Sep 2026 15:49:17 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E5=BD=92=E6=A1=A3):=20=E5=BD=92=E6=A1=A3?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E8=87=AA=E5=85=85=E6=94=AF=E4=BB=98=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E4=B8=8E=E5=91=98=E5=B7=A5=E4=BB=A3=E6=94=B6=E6=AC=BE?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=89=8D=E7=BC=80=E4=B8=A4=E4=B8=AA=E5=8F=98?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add-agent-self-recharge-payment-methods 归档为 2026-09-11-add-agent-self-recharge-payment-methods,delta 应用后主规格 agent-funds-commission 完成 1 条 Requirement 改名并新增 4 条 Requirement - 在可达操作索引补充代理自充支付方式配置与付款凭证识别共 3 个端点 - 同步 requirement-evidence.json 与 entry-capability-requirement-matrix.json 证据链 - fix-employee-collection-route-prefix 归档为 2026-09-11-fix-employee-collection-route-prefix 并勾选任务 2.5 门禁:context-health 通过、openspec validate --all 40 passed / 0 failed、doctor healthy --- .../entry-capability-requirement-matrix.json | 45 ++++- .../context-reset/requirement-evidence.json | 124 +++++++++++++- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/agent-funds-commission/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../tasks.md | 2 +- openspec/specs/agent-funds-commission/spec.md | 154 +++++++++++++++++- 12 files changed, 304 insertions(+), 21 deletions(-) rename openspec/changes/{add-agent-self-recharge-payment-methods => archive/2026-09-11-add-agent-self-recharge-payment-methods}/.openspec.yaml (100%) rename openspec/changes/{add-agent-self-recharge-payment-methods => archive/2026-09-11-add-agent-self-recharge-payment-methods}/design.md (100%) rename openspec/changes/{add-agent-self-recharge-payment-methods => archive/2026-09-11-add-agent-self-recharge-payment-methods}/proposal.md (100%) rename openspec/changes/{add-agent-self-recharge-payment-methods => archive/2026-09-11-add-agent-self-recharge-payment-methods}/specs/agent-funds-commission/spec.md (100%) rename openspec/changes/{add-agent-self-recharge-payment-methods => archive/2026-09-11-add-agent-self-recharge-payment-methods}/tasks.md (100%) rename openspec/changes/{fix-employee-collection-route-prefix => archive/2026-09-11-fix-employee-collection-route-prefix}/.openspec.yaml (100%) rename openspec/changes/{fix-employee-collection-route-prefix => archive/2026-09-11-fix-employee-collection-route-prefix}/design.md (100%) rename openspec/changes/{fix-employee-collection-route-prefix => archive/2026-09-11-fix-employee-collection-route-prefix}/proposal.md (100%) rename openspec/changes/{fix-employee-collection-route-prefix => archive/2026-09-11-fix-employee-collection-route-prefix}/tasks.md (83%) diff --git a/docs/verification/context-reset/entry-capability-requirement-matrix.json b/docs/verification/context-reset/entry-capability-requirement-matrix.json index 3ea0b62..e9190c0 100644 --- a/docs/verification/context-reset/entry-capability-requirement-matrix.json +++ b/docs/verification/context-reset/entry-capability-requirement-matrix.json @@ -199,7 +199,8 @@ "requirements": [ "agent-funds-commission::充值边界", "identity-access::数据范围拒绝", - "order-payment-wallet::金额单位" + "order-payment-wallet::金额单位", + "agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号" ], "classification": "behavior" }, @@ -211,7 +212,7 @@ "agent-funds-commission::充值边界", "identity-access::数据范围拒绝", "order-payment-wallet::金额单位", - "agent-funds-commission::代理在线充值可用支付方式按支付配置判定" + "agent-funds-commission::代理在线充值可用支付方式按允许范围与可用商户池判定" ], "classification": "behavior" }, @@ -222,7 +223,8 @@ "requirements": [ "agent-funds-commission::充值边界", "identity-access::数据范围拒绝", - "order-payment-wallet::金额单位" + "order-payment-wallet::金额单位", + "agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号" ], "classification": "behavior" }, @@ -1770,7 +1772,8 @@ "identity-access::数据范围拒绝", "order-payment-wallet::金额单位", "external-integration::富友主扫统一下单与订单查询", - "merchant-payment-routing::新支付商户快照与历史兼容" + "merchant-payment-routing::新支付商户快照与历史兼容", + "agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号" ], "classification": "behavior" }, @@ -3797,7 +3800,8 @@ "entry": "PUT /api/admin/employee-collection-payment-methods/{id}", "capability": "employee-collection-bill", "requirements": [ - "employee-collection-bill::收款方式字典、退款联动与可追溯性" + "employee-collection-bill::收款方式字典、退款联动与可追溯性", + "agent-funds-commission::线下收款方式字典对代理充值引用的可见性" ], "classification": "behavior" }, @@ -3806,7 +3810,8 @@ "entry": "DELETE /api/admin/employee-collection-payment-methods/{id}", "capability": "employee-collection-bill", "requirements": [ - "employee-collection-bill::收款方式字典、退款联动与可追溯性" + "employee-collection-bill::收款方式字典、退款联动与可追溯性", + "agent-funds-commission::线下收款方式字典对代理充值引用的可见性" ], "classification": "behavior" }, @@ -3884,5 +3889,33 @@ "employee-collection-bill::核销申请审批、重提与幂等" ], "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/agent-self-recharge-payment-methods", + "capability": "agent-funds-commission", + "requirements": [ + "agent-funds-commission::代理在线充值可用支付方式按允许范围与可用商户池判定", + "agent-funds-commission::代理自充允许方式配置可审计" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "PUT /api/admin/agent-self-recharge-payment-methods", + "capability": "agent-funds-commission", + "requirements": [ + "agent-funds-commission::代理自充允许方式配置可审计" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "POST /api/admin/agent-recharges/payment-voucher-ocr", + "capability": "agent-funds-commission", + "requirements": [ + "agent-funds-commission::付款凭证识别仅作交易流水号预填" + ], + "classification": "behavior" } ] diff --git a/docs/verification/context-reset/requirement-evidence.json b/docs/verification/context-reset/requirement-evidence.json index 3ae36c1..d778fea 100644 --- a/docs/verification/context-reset/requirement-evidence.json +++ b/docs/verification/context-reset/requirement-evidence.json @@ -1611,16 +1611,19 @@ }, { "capability": "agent-funds-commission", - "requirement": "代理在线充值可用支付方式按支付配置判定", + "requirement": "代理在线充值可用支付方式按允许范围与可用商户池判定", "spec": "openspec/specs/agent-funds-commission/spec.md", "entries": [ - "GET /api/admin/agent-recharges/payment-methods" + "GET /api/admin/agent-recharges/payment-methods", + "GET /api/admin/agent-self-recharge-payment-methods", + "POST /api/admin/agent-recharges" ], "handler_consumer_job": [ "internal/handler/admin/agent_recharge.go:AgentRechargeHandler.PaymentMethods" ], "application_service_query": [ - "internal/application/agentrecharge/online_creation.go:OnlineCreationService.AvailablePaymentMethods" + "internal/application/agentrecharge/online_creation.go:OnlineCreationService.AvailablePaymentMethods", + "internal/application/agentrecharge/online_payment_policy.go:OnlinePaymentMethodPolicy.AllowedMethods" ], "domain_state_amount": [ "internal/model/agent_wallet.go:AgentRechargeRecord.PaymentMethod/PaymentChannel" @@ -1629,9 +1632,9 @@ "internal/application/agentrecharge/online_creation.go:adapter.Available" ], "verification": { - "command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/admin/agent_recharge.go'\"'\"', '\"'\"'AvailablePaymentMethods'\"'\"'), ('\"'\"'internal/application/agentrecharge/online_creation.go'\"'\"', '\"'\"'adapter.Available(config)'\"'\"'), ('\"'\"'internal/model/agent_wallet.go'\"'\"', '\"'\"'PaymentChannel'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: agent-funds-commission::代理在线充值可用支付方式按支付配置判定'\"'\"')'", + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/agentrecharge/online_creation.go\", \"allowed, err := s.policy.AllowedMethods(ctx)\"), (\"internal/application/agentrecharge/online_creation.go\", \"adapter.Available(config)\"), (\"internal/application/agentrecharge/online_payment_policy.go\", \"func (p *OnlinePaymentMethodPolicy) AllowedMethods(\"), (\"internal/application/agentrecharge/online_payment_policy.go\", \"func (p *OnlinePaymentMethodPolicy) IsAllowed(\"), (\"internal/handler/admin/agent_recharge.go\", \"func (h *AgentRechargeHandler) SelfRechargePaymentMethods(\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::代理在线充值可用支付方式按允许范围与可用商户池判定\")'", "literal_output": [ - "OK: agent-funds-commission::代理在线充值可用支付方式按支付配置判定" + "OK: agent-funds-commission::代理在线充值可用支付方式按允许范围与可用商户池判定" ], "exit_status": 0 } @@ -2862,5 +2865,116 @@ ], "exit_status": 0 } + }, + { + "capability": "agent-funds-commission", + "requirement": "代理自充允许方式配置可审计", + "spec": "openspec/specs/agent-funds-commission/spec.md", + "entries": [ + "GET /api/admin/agent-self-recharge-payment-methods", + "PUT /api/admin/agent-self-recharge-payment-methods" + ], + "handler_consumer_job": [ + "internal/routes/agent_self_recharge_payment_method.go", + "internal/handler/admin/agent_recharge.go" + ], + "application_service_query": [ + "internal/application/agentrecharge/online_payment_policy.go" + ], + "domain_state_amount": [ + "pkg/constants/system_config.go" + ], + "store_migration_config": [ + "internal/bootstrap/payment_method_config.go" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/system_config.go\", \"SystemConfigAgentSelfRechargeAllowedMethods\"), (\"pkg/constants/system_config.go\", \"AgentSelfRechargeAllowedBoth\"), (\"internal/bootstrap/payment_method_config.go\", \"DefaultValue: constants.AgentSelfRechargeAllowedBoth\"), (\"internal/bootstrap/payment_method_config.go\", \"EnumValues: []string{constants.AgentSelfRechargeAllowedWechatOnly\"), (\"internal/handler/admin/agent_recharge.go\", \"func (h *AgentRechargeHandler) UpdateSelfRechargePaymentMethods(\"), (\"internal/routes/agent_self_recharge_payment_method.go\", \"仅超级管理员可以修改代理自充允许范围\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::代理自充允许方式配置可审计\")'", + "literal_output": [ + "OK: agent-funds-commission::代理自充允许方式配置可审计" + ], + "exit_status": 0 + } + }, + { + "capability": "agent-funds-commission", + "requirement": "线下预存款审批收款方式、其他凭证与交易流水号", + "spec": "openspec/specs/agent-funds-commission/spec.md", + "entries": [ + "POST /api/admin/agent-recharges" + ], + "handler_consumer_job": [ + "internal/handler/admin/agent_recharge.go", + "internal/application/agentrecharge/offline_creation.go" + ], + "application_service_query": [ + "internal/application/agentrecharge/offline_creation.go" + ], + "domain_state_amount": [ + "internal/model/agent_wallet.go" + ], + "store_migration_config": [ + "migrations/000213_add_agent_recharge_offline_fields.up.sql" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/agentrecharge/offline_creation.go\", \"func loadEnabledOfflinePaymentMethod(\"), (\"internal/application/agentrecharge/offline_creation.go\", \"func validateRechargeTransactionNo(\"), (\"internal/application/agentrecharge/offline_creation.go\", \"func validateVoucherKeys(\"), (\"migrations/000213_add_agent_recharge_offline_fields.up.sql\", \"external_transaction_no varchar(128)\"), (\"migrations/000213_add_agent_recharge_offline_fields.up.sql\", \"other_voucher_keys jsonb\"), (\"internal/model/agent_wallet.go\", \"column:external_transaction_no\"), (\"internal/model/agent_wallet.go\", \"column:offline_payment_method_code\"), (\"internal/application/wecom/scene.go\", \"constants.ApprovalFieldExternalTransactionNo\"), (\"internal/application/wecom/scene.go\", \"constants.ApprovalFieldOtherVoucherKey\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号\")'", + "literal_output": [ + "OK: agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号" + ], + "exit_status": 0 + } + }, + { + "capability": "agent-funds-commission", + "requirement": "付款凭证识别仅作交易流水号预填", + "spec": "openspec/specs/agent-funds-commission/spec.md", + "entries": [ + "POST /api/admin/agent-recharges/payment-voucher-ocr" + ], + "handler_consumer_job": [ + "internal/handler/admin/agent_recharge.go", + "internal/routes/agent_recharge.go" + ], + "application_service_query": [ + "internal/application/agentrecharge/payment_voucher_ocr.go" + ], + "domain_state_amount": [], + "store_migration_config": [ + "internal/gateway/payment_voucher.go" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/gateway/payment_voucher.go\", \"func (c *Client) ExtractPaymentVoucherOrderNumber(\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"func (s *PaymentVoucherOCRService) Recognize(\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"ExternalTransactionNo string\"), (\"internal/handler/admin/agent_recharge.go\", \"func (h *AgentRechargeHandler) PaymentVoucherOCR(\"), (\"internal/routes/agent_recharge.go\", \"/payment-voucher-ocr\")]; negative=[(\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"AmountCent\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"Payee\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"PaymentTime\")]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::付款凭证识别仅作交易流水号预填\")'", + "literal_output": [ + "OK: agent-funds-commission::付款凭证识别仅作交易流水号预填" + ], + "exit_status": 0 + } + }, + { + "capability": "agent-funds-commission", + "requirement": "线下收款方式字典对代理充值引用的可见性", + "spec": "openspec/specs/agent-funds-commission/spec.md", + "entries": [ + "PUT /api/admin/employee-collection-payment-methods/{id}", + "DELETE /api/admin/employee-collection-payment-methods/{id}" + ], + "handler_consumer_job": [ + "internal/handler/admin/employee_collection.go" + ], + "application_service_query": [ + "internal/application/employeecollection/payment_method.go" + ], + "domain_state_amount": [ + "internal/domain/employeecollection/payment_method.go" + ], + "store_migration_config": [ + "internal/model/agent_wallet.go" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/employeecollection/payment_method.go\", \"func countPaymentMethodReferences(\"), (\"internal/application/employeecollection/payment_method.go\", \"model.AgentRechargeRecord{}\"), (\"internal/application/employeecollection/payment_method.go\", \"offline_payment_method_id = ?\"), (\"internal/application/employeecollection/payment_method.go\", \"线下收款方式已被核销申请或代理充值申请引用,不能修改稳定编码\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::线下收款方式字典对代理充值引用的可见性\")'", + "literal_output": [ + "OK: agent-funds-commission::线下收款方式字典对代理充值引用的可见性" + ], + "exit_status": 0 + } } ] diff --git a/openspec/changes/add-agent-self-recharge-payment-methods/.openspec.yaml b/openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/.openspec.yaml similarity index 100% rename from openspec/changes/add-agent-self-recharge-payment-methods/.openspec.yaml rename to openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/.openspec.yaml diff --git a/openspec/changes/add-agent-self-recharge-payment-methods/design.md b/openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/design.md similarity index 100% rename from openspec/changes/add-agent-self-recharge-payment-methods/design.md rename to openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/design.md diff --git a/openspec/changes/add-agent-self-recharge-payment-methods/proposal.md b/openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/proposal.md similarity index 100% rename from openspec/changes/add-agent-self-recharge-payment-methods/proposal.md rename to openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/proposal.md diff --git a/openspec/changes/add-agent-self-recharge-payment-methods/specs/agent-funds-commission/spec.md b/openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/specs/agent-funds-commission/spec.md similarity index 100% rename from openspec/changes/add-agent-self-recharge-payment-methods/specs/agent-funds-commission/spec.md rename to openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/specs/agent-funds-commission/spec.md diff --git a/openspec/changes/add-agent-self-recharge-payment-methods/tasks.md b/openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/tasks.md similarity index 100% rename from openspec/changes/add-agent-self-recharge-payment-methods/tasks.md rename to openspec/changes/archive/2026-09-11-add-agent-self-recharge-payment-methods/tasks.md diff --git a/openspec/changes/fix-employee-collection-route-prefix/.openspec.yaml b/openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/.openspec.yaml similarity index 100% rename from openspec/changes/fix-employee-collection-route-prefix/.openspec.yaml rename to openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/.openspec.yaml diff --git a/openspec/changes/fix-employee-collection-route-prefix/design.md b/openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/design.md similarity index 100% rename from openspec/changes/fix-employee-collection-route-prefix/design.md rename to openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/design.md diff --git a/openspec/changes/fix-employee-collection-route-prefix/proposal.md b/openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/proposal.md similarity index 100% rename from openspec/changes/fix-employee-collection-route-prefix/proposal.md rename to openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/proposal.md diff --git a/openspec/changes/fix-employee-collection-route-prefix/tasks.md b/openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/tasks.md similarity index 83% rename from openspec/changes/fix-employee-collection-route-prefix/tasks.md rename to openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/tasks.md index 5891bf8..4249cfe 100644 --- a/openspec/changes/fix-employee-collection-route-prefix/tasks.md +++ b/openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix/tasks.md @@ -13,4 +13,4 @@ - [x] 2.2 全量校验与既有条目共存。验证:同一副本 `openspec validate --all` 为 42 passed / 0 failed;真实仓库对照为 41 passed / 0 failed(差异仅为副本多出的本 Change)。 - [x] 2.3 变更不破坏上下文健康门禁。验证:把本 Change 放入仓库快照的 `/tmp` 全量副本(含 `e687a26` 的证据链与可达操作索引)后执行 `zsh ./scripts/context-health.sh`,输出「Context 健康检查通过」、退出码 0。 - [x] 2.4 落地后在真实仓库运行 `./scripts/context-health.sh` 并确认输出「Context 健康检查通过」;该脚本会重写 `docs/admin-openapi.yaml`,须在并发流水线停止后执行。验证:在真实仓库(`HEAD=7891189`,含 `e687a26` 的证据链与可达操作索引)执行,输出「Context 健康检查通过」、退出码 0;同时 `docs/admin-openapi.yaml` md5 仍为 `274f45440597aaafb1e168ac266cbb85`。 -- [ ] 2.5 归档本 Change(`openspec archive`,不改动 `openspec/specs/**` 与 `docs/verification/context-reset/*.json`;归档预演结果见 design 的 Risks)。 +- [x] 2.5 归档本 Change(`openspec archive`,不改动 `openspec/specs/**` 与 `docs/verification/context-reset/*.json`;归档预演结果见 design 的 Risks)。验证:`openspec archive fix-employee-collection-route-prefix --yes` 将本 Change 移入 `openspec/changes/archive/2026-09-11-fix-employee-collection-route-prefix`;因本 Change 无 delta 且 `.openspec.yaml` 声明 `skip_specs: true`,`openspec/specs/**` 与两个证据 JSON 均无改动;归档后 `./scripts/context-health.sh` 输出「Context 健康检查通过」退出码 0,`openspec validate --all` 为 41 passed / 0 failed,`openspec doctor --json` 为 `healthy = true`。 diff --git a/openspec/specs/agent-funds-commission/spec.md b/openspec/specs/agent-funds-commission/spec.md index 7deeba3..f2e3b2a 100644 --- a/openspec/specs/agent-funds-commission/spec.md +++ b/openspec/specs/agent-funds-commission/spec.md @@ -3,9 +3,7 @@ ## Purpose 描述代理充值、钱包、佣金、提现及其金额边界的当前可观察行为。 - ## Requirements - ### Requirement: 代理钱包与提现状态门禁 系统 SHALL 仅允许正常钱包执行资金操作;冻结或关闭钱包拒绝扣款、冻结、释放或提现,提现申请按待审核、已通过、已拒绝、已到账状态推进,重复终态决定不得再次扣减或入账。 @@ -122,41 +120,179 @@ - **WHEN** 充值申请原创建账号不可用,或企业微信线下充值审批场景不可用 - **THEN** 系统返回相应错误,充值申请保持未关联审批实例,修复条件后可再次发起 -### Requirement: 代理在线充值可用支付方式按支付配置判定 +### Requirement: 代理在线充值可用支付方式按允许范围与可用商户池判定 -系统 SHALL 按当前生效支付配置判定代理在线充值可用支付方式:微信直连(`wechat` 或 `wechat_v2`)配置完整,或富友(`fuiou`)配置完整时,返回 `wechat`;支付宝字段完整时返回 `alipay`。对外支付方式枚举 MUST 固定为 `wechat` 与 `alipay`,MUST NOT 返回 `fuiou`。代理账号以 `wechat` 创建在线充值单时,若生效配置为富友,系统 MUST 使用富友主扫统一下单并将返回的二维码链接作为支付链接。 +系统 SHALL 以超级管理员维护的允许范围与当前可用商户池支付方式的交集判定代理在线充值的实际可用支付方式。允许范围 MUST 仅取仅微信、仅支付宝、同时支持三者之一;商户池侧 MUST 沿用"该支付方式存在启用商户池且存在启用、凭证完整且当前适配器可用的商户成员"的既有判定。对外支付方式枚举 MUST 固定为 `wechat` 与 `alipay`,MUST NOT 返回 `fuiou`、商户身份、凭证或允许范围本身。代理账号与平台账号均可查询该交集列表,查询 MUST 返回有序列且不因交集为空而报错;交集为空时创建对应方式的在线充值单 MUST 被拒绝且 MUST NOT 新建充值单或支付单,MUST NOT 回退任何历史生效支付配置。代理以 `wechat` 创建在线充值单时,若命中的商户为富友服务商,系统 MUST 使用富友主扫统一下单并将返回的二维码链接作为支付链接。允许范围变更 MUST 只影响后续新单;已创建未支付充值单 MUST 保留其支付方式与商户路由快照。 #### Scenario: 富友配置完整时微信可用 -- **GIVEN** 当前生效支付配置 `provider_type=fuiou` 且富友机构号、商户号、终端号、私钥、公钥、API 地址、通知地址均非空 +- **GIVEN** 允许范围包含微信,且微信商户池存在启用、凭证完整的富友服务商商户成员 - **WHEN** 代理账号查询可用支付方式 - **THEN** 系统返回包含 `wechat` 的方式列表且不包含 `fuiou` #### Scenario: 微信直连配置完整时微信可用 -- **GIVEN** 当前生效支付配置为微信直连且对应字段完整 +- **GIVEN** 允许范围包含微信,且微信商户池存在启用、凭证完整的微信直连商户成员 - **WHEN** 代理账号查询可用支付方式 - **THEN** 系统返回包含 `wechat` 的方式列表 #### Scenario: 支付宝字段完整时支付宝可用 -- **GIVEN** 当前生效支付配置的支付宝应用 ID、应用私钥、支付宝公钥、通知地址均非空 +- **GIVEN** 允许范围包含支付宝,且支付宝商户池存在启用、凭证完整的商户成员 - **WHEN** 代理账号查询可用支付方式 - **THEN** 系统返回包含 `alipay` 的方式列表 #### Scenario: 富友配置下微信创建走主扫下单 -- **GIVEN** 当前生效支付配置为富友且字段完整 +- **GIVEN** 允许范围包含微信,且微信商户池命中的商户为富友服务商且字段完整 - **WHEN** 代理账号以 `wechat` 创建在线充值单 - **THEN** 系统调用富友主扫统一下单并返回二维码链接作为支付链接,本地充值单支付方式为 `wechat`、支付渠道为 `fuiou` +#### Scenario: 允许范围收窄后代理只看到被允许的方式 + +- **GIVEN** 微信与支付宝商户池均存在可用成员 +- **WHEN** 超级管理员将允许范围设置为仅支付宝,代理账号随后查询可用支付方式 +- **THEN** 系统只返回 `alipay` + +#### Scenario: 交集为空 + +- **GIVEN** 允许范围只包含微信,而当前微信商户池不存在可用成员 +- **WHEN** 代理账号查询可用支付方式并尝试以 `wechat` 创建在线充值单 +- **THEN** 查询返回空列表且不报错,创建被拒绝且不新建充值单或支付单,不发生任何历史配置回退 + +#### Scenario: 配置变更不影响已创建未支付单 + +- **GIVEN** 代理已创建待支付在线充值单并冻结了支付方式与商户路由快照 +- **WHEN** 超级管理员修改允许范围 +- **THEN** 该充值单的支付方式与商户快照保持不变,仅后续新单按新范围判定 + +#### Scenario: 平台账号可查询且看不到允许范围 + +- **GIVEN** 请求账号为平台账号 +- **WHEN** 查询可用支付方式 +- **THEN** 系统返回与代理一致的交集列表,且响应不含允许范围本身、商户身份或凭证 + +#### Scenario: 非超级管理员不能读取或修改允许范围 + +- **GIVEN** 请求账号为代理或平台账号 +- **WHEN** 读取或修改全局允许范围 +- **THEN** 系统拒绝访问且不返回允许范围取值 + +### Requirement: 代理自充允许方式配置可审计 + +系统 SHALL 允许超级管理员读取并修改代理在线自充允许方式,取值 MUST 仅限仅微信、仅支付宝、同时支持三种之一;每次成功修改 MUST 记录操作者、修改前后值与修改时间。其他角色 MUST NOT 读取或修改该允许范围。允许范围未被修改过时,系统 MUST 使用代码注册的安全默认值,MUST NOT 因缺少配置而拒绝查询或创建。 + +#### Scenario: 超级管理员修改允许范围 + +- **GIVEN** 当前允许范围为同时支持 +- **WHEN** 超级管理员将其修改为仅微信 +- **THEN** 系统保存新值并记录操作者、修改前后值与修改时间 + +#### Scenario: 非法取值被拒绝 + +- **WHEN** 提交三种允许取值之外的任何值 +- **THEN** 系统拒绝保存并保留原值 + +#### Scenario: 未配置时使用默认值 + +- **GIVEN** 允许范围从未被修改 +- **WHEN** 查询实际可用支付方式 +- **THEN** 系统按代码注册的默认允许范围计算交集,不因缺少配置而失败 + +### Requirement: 线下预存款审批收款方式、其他凭证与交易流水号 + +线下代理预存款/主钱包充值申请 SHALL 由提交人选择一项启用的线下收款方式字典项,并保存其标识、稳定编码与名称快照;字典项改名或停用 MUST NOT 改变历史申请已冻结的快照,已停用项 MUST NOT 可被新申请选取。申请 MUST 保存该笔充值对应的交易流水号,其取值来源为付款凭证识别出的支付单号,且 MUST 以人工确认或更正后的值为准;该系统字段 MUST 独立于在线支付由渠道返回的第三方交易号,MUST NOT 因该值影响任何在线支付事实、钱包入账或幂等判定,且 MUST NOT 作为跨记录去重键或入账前置条件。申请 MUST 支持至少一个支付凭证,并 MUST 支持可选的其他凭证;两类凭证均只保存既有附件对象键引用。系统 MUST 拒绝引用不存在或已停用的收款方式创建申请,MUST NOT 因未填写交易流水号而创建申请。 + +#### Scenario: 收款方式快照冻结 + +- **GIVEN** 一笔线下预存款申请引用了某启用的线下收款方式 +- **WHEN** 超级管理员随后修改该字典项名称或将其停用 +- **THEN** 历史申请仍展示提交时冻结的编码与名称,新申请不可再选择该已停用项 + +#### Scenario: 引用不存在或已停用的收款方式被拒绝 + +- **WHEN** 提交的收款方式字典项不存在或已停用 +- **THEN** 系统拒绝创建申请且不保存审批实例 + +#### Scenario: 支付凭证与其他凭证分别留存 + +- **WHEN** 提交人上传支付凭证与若干其他凭证并创建申请 +- **THEN** 系统分别保存支付凭证与其他凭证的对象键引用,且企业微信审批详情可取得两类凭证 + +#### Scenario: 交易流水号留存并可在审批详情取得 + +- **WHEN** 线下预存款申请创建成功 +- **THEN** 该充值记录保存人工确认后的交易流水号,并可在企业微信审批详情中取得 + +#### Scenario: 缺少交易流水号时拒绝创建 + +- **WHEN** 提交人未填写或填写空白交易流水号 +- **THEN** 系统拒绝创建申请且不增加钱包余额 + +#### Scenario: 交易流水号独立于在线渠道交易号 + +- **GIVEN** 一笔线下预存款申请已保存人工确认的交易流水号 +- **WHEN** 向同一充值记录写入或修正该交易流水号 +- **THEN** 在线支付由渠道返回的第三方交易号、支付渠道与支付状态均不受影响,在线充值的入账对账与幂等判定结果不变 + +#### Scenario: 相同交易流水号不被系统拒绝 + +- **WHEN** 两笔不同的线下预存款申请保存了完全相同的交易流水号 +- **THEN** 系统均接受创建,不因该值重复而拒绝或去重 + +### Requirement: 付款凭证识别仅作交易流水号预填 + +系统 SHALL 提供付款凭证识别,以上传附件的对象键请求识别并只返回支付单号供交易流水号表单预填。识别结果 MUST NOT 写入任何资金事实字段;申请最终保存的交易流水号 MUST 以人工确认或更正后的提交值为准。支付金额、备注、付款人、支付方式与支付时间即使由 Gateway 返回,MUST NOT 被本接口返回或自动预填。识别失败 MUST 返回可理解的失败结果且 MUST NOT 阻断人工填写与提交。系统 MUST NOT 在日志、审计或错误响应中记录凭证内容或识别原始结果。 + +#### Scenario: 识别成功仅返回交易流水号预填值 + +- **GIVEN** 提交人已上传一张图片类型的支付凭证并以其对象键请求识别 +- **WHEN** 识别成功 +- **THEN** 系统只返回识别出的支付单号供交易流水号预填,且此时未创建任何充值申请 + +#### Scenario: 识别结果被人工更正 + +- **GIVEN** 识别返回的支付单号与实际不符 +- **WHEN** 提交人更正后提交申请 +- **THEN** 系统保存更正后的值,识别原值不影响任何业务事实 + +#### Scenario: 识别失败不阻断人工填写 + +- **WHEN** 凭证非图片、对象不存在或识别服务失败 +- **THEN** 系统返回明确失败,且提交人仍可人工填写全部字段后提交 + +#### Scenario: 日志不记录识别原始结果 + +- **WHEN** 付款凭证识别被调用并返回结果 +- **THEN** 日志、审计与错误记录只包含业务标识与结果摘要,不包含凭证内容或识别原始结果 + +### Requirement: 线下收款方式字典对代理充值引用的可见性 + +系统 SHALL 使线下收款方式字典的引用保护覆盖引用它的代理充值申请:已被任一代理充值申请引用的字典项 MUST NOT 被物理删除,MUST 只允许停用,且 MUST 保留历史申请的名称快照。字典项稳定编码在存在引用后 MUST NOT 被修改。 + +#### Scenario: 被代理充值引用的字典项不可删除 + +- **GIVEN** 某线下收款方式字典项已被至少一笔代理充值申请引用 +- **WHEN** 超级管理员请求删除该字典项 +- **THEN** 系统拒绝删除并返回只能停用的稳定错误 + +#### Scenario: 被代理充值引用的字典项编码不可修改 + +- **GIVEN** 某线下收款方式字典项已被至少一笔代理充值申请引用 +- **WHEN** 超级管理员请求修改该字典项的稳定编码 +- **THEN** 系统拒绝修改编码,但允许修改名称、排序、启停与备注 + ## 可达操作索引 本节只用于入口导航,不是行为 Requirement;业务义务以上述 Requirements 为准。 ### 代理预充值 -`GET /api/admin/agent-recharges`(查询代理充值订单列表);`POST /api/admin/agent-recharges`(创建代理充值订单);`GET /api/admin/agent-recharges/{id}`(查询代理充值订单详情);`POST /api/admin/agent-recharges/{id}/trigger-approval`(补发历史线下代理充值审批);`POST /api/admin/agent-recharges/{id}/offline-pay`(确认线下充值);`GET /api/admin/agent-recharges/{id}/payment-status`(查询代理充值本地支付与到账状态);`POST /api/admin/agent-recharges/{id}/reject`(驳回代理充值订单);`GET /api/admin/agent-recharges/payment-methods`(查询代理在线充值可用支付方式)。 +`GET /api/admin/agent-recharges`(查询代理充值订单列表);`POST /api/admin/agent-recharges`(创建代理充值订单);`GET /api/admin/agent-recharges/{id}`(查询代理充值订单详情);`POST /api/admin/agent-recharges/{id}/trigger-approval`(补发历史线下代理充值审批);`POST /api/admin/agent-recharges/{id}/offline-pay`(确认线下充值);`GET /api/admin/agent-recharges/{id}/payment-status`(查询代理充值本地支付与到账状态);`POST /api/admin/agent-recharges/{id}/reject`(驳回代理充值订单);`GET /api/admin/agent-recharges/payment-methods`(查询代理在线充值可用支付方式);`POST /api/admin/agent-recharges/payment-voucher-ocr`(识别付款凭证以预填交易流水号)。 + +### 代理自充支付方式配置 + +`GET /api/admin/agent-self-recharge-payment-methods`(查询代理自充实际可用支付方式);`PUT /api/admin/agent-self-recharge-payment-methods`(修改代理在线自充允许范围)。 ### 代理商资金管理