这一批探针是为 2026-09-11 ~ 2026-09-18 完成的汇付天下/斗拱支付链路交接准备的:遇到线上问题时,
不用回来找人,用它直接回答「这笔钱 / 这个商户在通道侧到底是什么状态」。
1. 只读。 允许调用的只有两类:
SELECT**。v3/trade/payment/scanpay/query)、**关单查询**(v2/trade/payment/scanpay/closequery)、v2/merchant/basicdata/query)、**结算记录查询**(v2/merchant/basicdata/settlement/query)。严禁:发起支付、关单、退款、交易确认、交易确认退款、下发业务开通、上传图片、以及任何 INSERT/UPDATE/DELETE
(包括「只是顺手刷新一下能力快照」——那是写库,属于 MerchantPaymentReadinessService.refresh 的活,
要走后台的「立即核实」)。
这不是口头约定:这五个探针的代码里**没有任何一条写路径**。MerchantCapabilityProbe 特意绕开了refresh / capability / refreshQuietly / merchantVerify 这四个会写快照的方法,只用只读的readiness 与 channelCapabilityNormal。
2. 生产保护。 探针启动时读汇付配置里的 app.payment.huifu.environment(等价于运行时的 profile 判断);
判断为 prod 时打印显著警告。它**只是提示、不是闸门**——因为没有写路径,环境不改变探针能不能跑,
只改变你该不该紧张。用 --huifu-environment=prod 可以直接声明环境(不必给整个配置文件)。
3. 输出脱敏。 以下内容**绝对不打印**:
| 不打印 | 本探针的处理 |
|---|---|
| 私钥、密钥 | 从不读进内存。只输出「已配置 / 未配置」(布尔) |
| 完整商户号、完整汇付号 | 只留**前 6 位** + *** |
| 完整银行卡号 | 从不选取;结算记录里的卡号类字段整个替换成 ***;本地只印 card_masked 与尾号 4 位 |
| 完整公网 IP | 只留主机段**前两段** + x.x,端口与路径照常打印(排查要看的正是这两段) |
| 用户 openid、签名材料 | 回执打印前统一替换成 *** |
4. 参数从命令行传。 五个探针与五个运行脚本里**没有任何写死的目标**:不给 --db-url 就不知道连哪个库,
不给 --order-no 就不知道查哪一单,缺参数一律打印用法并以退出码 2 退出,绝不拿默认值瞎猜。
反面教材就在本仓库:
.scratch/下早先会话留下的一次性探针(*Probe*.java、Inspect*.java)大多把
JDBC URL 写死成127.0.0.1:8134/talent_card。**那个地址上放的是 2026-09-16 把本机库并入服务器库之前
的旧副本,不是开发库**(见docs/agents/testing.md)。本目录的探针不接受任何默认地址。
| 文件 | 作用 |
|---|---|
PaymentStateProbe.java / run-PaymentStateProbe.ps1 |
一笔支付:本地状态 vs 通道真实状态,指出差异 |
MerchantCapabilityProbe.java / run-MerchantCapabilityProbe.ps1 |
一个商户:本地能力快照 vs 通道真值,并算「能不能收款」 |
OrderFundBoundaryProbe.java / run-OrderFundBoundaryProbe.ps1 |
一笔订单:资金边界三列 + 支付/退款/核销/对账问题记录 |
SettlementRecordProbe.java / run-SettlementRecordProbe.ps1 |
一个商户按日期区间:通道结算记录 |
CallbackUrlAuditProbe.java / run-CallbackUrlAuditProbe.ps1 |
五个回调地址:哪个空、哪个是推导出来的 |
huifu-probe.properties.example |
需要连通道时用的配置文件模板 |
与 docs/audits/2026-09-10/run-probe.ps1 完全一致,没有另创一套:
mvn -q -DskipTests compile dependency:build-classpath -Dmdep.outputFile=target/probes/classpath.txt -Dmdep.includeScope=testtarget/classes 与依赖 classpath;javac -encoding UTF-8 -cp "target/classes;<cp>" -d target/probes <探针>.javasrc/main/java 下,所以**不进 Maven 编译**,靠脚本单独编译;java -cp "target/probes;target/classes;<cp>" <探针> <参数>两条与仓库环境有关的注意:
-SkipBuild,它会复用 target/probes/classpath.txt。pwsh(PowerShell 7)跑,理由见 docs/agents/testing.md。连通道的探针要先有 system-id / product-id / 私钥 / 平台公钥。**故意不读 application.yml**:那里面的
私钥与密钥是凭据,探针「读进来只为用、绝不打印」,就没有必要把它读进去。改成读一个由你自备的 properties 文件:
Copy-Item scripts\probes\huifu-probe.properties.example scripts\probes\huifu-probe.properties
# 填好后:huifu-probe.properties 是凭据文件,不要提交进仓库
键名与 application.yml 里 app.payment.huifu.* 一致,前缀可写可不写
(system-id 与 app.payment.huifu.system-id 都认)。enabled 不给时按 true 处理,
否则网关自己的 ensureConfigured 会把查询拦住。
.\scripts\probes\run-PaymentStateProbe.ps1 -PaymentNo HF20260917001 `
-DbUrl $env:PROBE_DB_URL -DbUser root -DbPassword 123456 -Config .\scripts\probes\huifu-probe.properties
打印 payment_transaction 的 status / funds_confirmation_status / 金额 / 幂等键 / attempt_no /provider_huifu_id / close_attempts / last_reconciled_at 与 funds_* 证据列,再问一次通道
(org_req_date 取自 created_at,org_hf_seq_id 取自 provider_transaction_no),然后逐条列出差异:
通道已收款而本地没落成已支付、金额不一致、收款子商户漂移、通道说「交易不存在」(23000001)等。
本地状态是 CLOSING / CANCEL_UNCONFIRMED 时,会再问一次**关单查询**(关单在通道侧是异步的,
结论只能靠这条查询)。
注意:本地 provider_huifu_id 为空(V131 之前的支付,由平台商户收款)时,按HuifuRefundService.refundHuifuId 的同一条回退,用配置里的平台商户号去问通道——否则那批历史流水永远问不出结论。
.\scripts\probes\run-MerchantCapabilityProbe.ps1 -ApplicationId 128 `
-DbUrl $env:PROBE_DB_URL -DbUser root -DbPassword 123456 -Config .\scripts\probes\huifu-probe.properties
定位方式三选一:-ApplicationId / -HuifuId / -MerchantId(可加 -StoreId)。
打印本地快照(merchant_stat / auto_settle_status / cash_switch_status / cash_type /capability_verified_at / capability_response_*)与通道 v2/merchant/basicdata/query 的真值,
逐列指出差异,再用 MerchantPaymentReadinessService.readiness(merchantId, storeId) 给出**能不能收款**
的结论与原因——那句文案与用户下单被拒时的文案同源。
两个容易看错的点,探针会直接说明:
capability_verified_at 为空 = 本地**从没核实过**通道状态。在要求通道确认的部署上,闸门会直接拒auto_settle_status 已经不是收款闸门(未核销的钱由延迟户兜住),只作参考,不要拿它下结论。不给 -Config 时也会给结论,但判定会退化成「跳过通道确认」那一档(merchant_capability_required=false),
与线上默认不一致,探针会明确警告。
.\scripts\probes\run-OrderFundBoundaryProbe.ps1 -OrderNo ORD20260917001 `
-DbUrl $env:PROBE_DB_URL -DbUser root -DbPassword 123456
打印 benefit_order 的资金边界三列(order_status / verify_status / refund_status)、创建时固定的收款方
(collect_onboarding_id / collect_store_id / collect_huifu_id)、全部支付流水、全部退款记录、核销记录,
以及 payment_reconciliation_issue 里按订单号与流水号/退款号命中的问题记录。
它**不问通道**,因此不需要 -Config(给了只用来看 environment 打生产警告)。三列的口径:
钱进商户基本户要 order_status=1 且 verify_status=1 且 refund_status=0(资金确认重试就是这么筛的);
退款在途(refund_status=1)会让整单退出可核销与可确认范围。
.\scripts\probes\run-SettlementRecordProbe.ps1 -HuifuId 6666000241335656 `
-BeginDate 20260911 -EndDate 20260918 -Config .\scripts\probes\huifu-probe.properties
直查 /v2/merchant/basicdata/settlement/query。**pageSize 被钉在 1..50**:通道对 pageSize 的硬上限就是 50,
超了直接返回 00000003「…最大值为50」而且**一条记录都拿不到**(后台此前默认传 100,于是「结算记录」页
永远空白,而通道里其实有流水)。要看得更多只能**缩小日期窗口**,通道不返回总页数。
.\scripts\probes\run-CallbackUrlAuditProbe.ps1 -Config .\scripts\probes\huifu-probe.properties
逐个打印五个回调地址(脱敏后)与状态:**显式配置 / 由 notify-url 推导 / 为空**,并明确列出
「为空的地址(回调未启用)」。取值规则直接调用应用里那份 HuifuCallbackUrlAudit(同一个类、同一批 getter),
所以不会与启动日志里的结论不一致。
一句最容易被误解的话:**某个回调地址留空不等于「不回调」**。四个非支付回调在配置为空时,会由 notify-url
推导出同主机同前缀的地址(HuifuPaymentProperties#callbackUrl);真正的「回调未启用」只有一种,
那就是这个地址算出来是空串。探针还会标两件事:路径与 PaymentCallbackController 暴露的五个端点对不上
(会 404)、以及某个地址的主机与 notify-url 不同(那台机器也必须能收到并处理这个回调)。
javac 对 target/classes + 依赖 jar 编译通过;docs/agents/testing.md)上实跑过——本地快照、支付/退款/核销/问题记录、PaymentStateProbe 的查单与关单查询、MerchantCapabilityProbe 的商户信息查询、SettlementRecordProbe 的结算记录查询都只做到「编译通过 + 请求参数装配正确」。第一次用可以拿一个flyway.enabled=false)。缺列的表现是探针里那条查询直接报错,Unknown column 就说明该库还缺某条迁移的等价 DDL,docs/agents/testing.md 与 scripts/dev-schema-*.sql 补。PaymentStateProbe 的 funds_* 证据列-DbPassword 是命令行参数。共享机器上排查时自己注意。--db-url 建议带上 serverTimezone=Asia/Shanghai(应用就是这么配的)。PaymentStateProbe 靠created_at 推 org_req_date,时区解释不同会整体偏一天,通道只会回「交易不存在」——那会看起来像「钱丢了」。