测试指南
测试环境与生产环境如何隔离、用哪些测试场景、如何测试收银台、退款、Webhook 与幂等,以及 33 条商户自测清单。
最后更新于 2026-10-15 · 契约 v1.0.0
测试环境说明
| 项目 | 测试环境(test) | 生产环境(live) |
|---|---|---|
| API 地址 | https://api.payhub.example(与生产相同) | 同左 |
| 环境由什么决定 | 测试 API Key | 生产 API Key |
| Key 前缀 | sk_test_ | sk_live_ |
| 能访问的数据 | 只有测试对象;查询生产对象返回 404 | 只有生产对象 |
| 上游 | 选定上游的沙箱账户 | 正式上游账户 |
| 卡 | 只接受沙箱测试卡 | 真实卡 |
| 资金 | 不产生真实资金 | 真实扣款、结算、打款 |
| Webhook | 测试环境单独的端点与签名密钥 | 生产端点与签名密钥 |
| 费率 | 未单独配置测试费率时为 0,不计费;需要演练计费时联系平台配置测试费率 | 按合同配置的生产费率 |
| 退款审核 | 默认自动批准(不设金额上限,仍需通过资金与风控校验);需要演练人工审核时联系平台改为人工审核 | 默认人工审核 |
| 结算与打款 | 按测试结算条款生成结算单(未单独配置时为 T+1 自然日、每日结算、结算手续费 0);审核与打款由平台演练,不发生真实打款,不使用你的收款账户 | 真实 |
| 商户后台标识 | 橙色“测试”徽章与横幅 | 绿色“生产”徽章 |
| 何时可用 | 商户开通后 | 平台完成开通检查并激活生产后 |
- 测试 Key 查询生产对象返回 404;生产 Key 不能把交易发到测试上游。
- 测试数据在生产停用后不会被删除;测试对象只能在测试环境查询。
警告
只能使用沙箱测试卡。禁止在测试环境、工单、聊天、截图中使用或传播真实卡数据。测试环境不产生真实资金。
准备工作
- 测试 API Key 及所需权限范围:创建测试 Key(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者)。
- 测试环境的 Webhook 端点与签名密钥:配置 Webhook(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者)。
- 登记回跳地址(测试环境可登记
http://localhost):回跳地址(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者)。 - 公网可达的 HTTPS 测试地址:用公开测试端点或受控隧道把本地服务暴露出去;平台不会访问内网或本机地址。
测试卡与场景
表中的占位符是场景标识:实际卡号、有效期规则与 CVC 取自选定上游的沙箱资料,只在商户后台登录后展示(查看测试卡取值(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者))。公开文档不发布任何测试卡取值。
| # | 场景 | 场景标识 | 其他输入 | 预期 API 结果 | 预期事件 | 对应 AC |
|---|---|---|---|---|---|---|
| C1 | 直接成功(无需 3DS) | <UPSTREAM_TEST_CARD_SUCCESS>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 任一未来有效期、<TEST_CVC> | 201 succeeded,captured_amount = amount | payment.succeeded | AC-008、AC-010 |
| C2 | 3DS 免交互通过 | <UPSTREAM_TEST_CARD_3DS_FRICTIONLESS>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 同上 | 201 succeeded,可能不经过 requires_action | payment.succeeded(可能没有 payment.requires_action) | AC-008 |
| C3 | 3DS 挑战后成功 | <UPSTREAM_TEST_CARD_3DS_CHALLENGE>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 测试认证页选择“通过” | 201 requires_action → 认证后 succeeded | payment.requires_action、payment.succeeded | AC-009、AC-011 |
| C4 | 3DS 认证失败 | <UPSTREAM_TEST_CARD_3DS_FAIL>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 测试认证页选择“失败” | requires_action → failed(authentication_failed) | payment.failed | — |
| C5 | 3DS 成功但发卡行拒绝 | <UPSTREAM_TEST_CARD_3DS_THEN_DECLINE>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 认证通过 | requires_action → failed(card_declined),认证结果为成功 | payment.failed | AC-012 |
| C6 | 发卡行拒绝 | <UPSTREAM_TEST_CARD_DECLINED>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | — | failed(card_declined) | payment.failed | — |
| C7 | 拒绝后换卡成功 | <UPSTREAM_TEST_CARD_SUCCESS>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 先做 C6,再用新幂等键 confirm 成功卡 | 同一 Payment,attempt_count=2,succeeded | payment.failed、payment.succeeded(version 递增) | AC-026 |
| C8 | 卡信息错误 | <UPSTREAM_TEST_CARD_INCORRECT_DATA>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | — | failed(incorrect_card_data) | payment.failed | — |
| C9 | 超时 / 结果未知后成功(暂不支持模拟) | —(暂不支持模拟) | 自测方法见表下说明 | 生产中的表现:202 processing + needs_query → 稍后 succeeded | payment.succeeded | AC-019 |
| C10 | 超时 / 结果未知后失败(暂不支持模拟) | —(暂不支持模拟) | 自测方法见表下说明 | 生产中的表现:202 processing → 稍后 failed | payment.failed | — |
| C11 | 上游重复回调(暂不支持模拟) | —(暂不支持模拟) | 自测方法见表下说明 | 生产中的表现:succeeded | 只有一个 payment.succeeded 事件 ID(可能被投递多次) | AC-021(商户侧对应点) |
| C12 | 卡品牌不支持 | <UPSTREAM_TEST_CARD_UNSUPPORTED_BRAND>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | — | 422 card_brand_not_supported | 无 | — |
| C13 | 退款将失败 | <UPSTREAM_TEST_CARD_REFUND_FAILS>登录商户后台查看(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) | 支付成功后发起退款 | 支付 succeeded;退款 processing → failed | refund.failed | — |
说明:C9 ~ C11 暂不支持模拟
测试环境不提供上游模拟器,上游沙箱通常无法触发上游超时与重复回调。自测方法:
- 结果未知(C9、C10):用单元测试或模拟服务把 202
processing+needs_query响应(创建支付的processingNeedsQuery示例)交给你的代码,确认显示“正在确认”、不重扣,之后按查询或事件处理;再把客户端超时设为 100 毫秒发起 C1 支付,用同一个键重试或按订单号查询,确认只得到一个 Payment。 - 重复回调(C11):按下面 Webhook 测试的第 6 步(超时重投)与第 8 步(平台人工重发同一事件)让同一事件 ID 再次到达,确认只处理一次;用“发送测试事件”核对接收、验签与按事件 ID 存档。
部分上游用特定金额触发场景,触发规则与测试卡取值一起在商户后台展示;失败原因码以失败原因码为准。每笔测试支付的状态、尝试与事件都可以在 支付订单(在新标签页打开商户后台)(测试环境) 中查看。
托管收银台测试
| 场景 | 操作 | 预期 | AC |
|---|---|---|---|
| 无需 3DS | 创建会话 → 打开 checkout_url → 用 C1 卡支付 | 结果页成功、回到 return_url;查询与 Webhook 均为 succeeded | AC-010 |
| 需要 3DS | 用 C3 卡在收银台支付 | 收银台内完成认证;你的服务端密钥不出现在浏览器 | AC-011 |
| 两个标签页同时提交 | 同一 checkout_url 开两个标签页同时点击支付 | 只产生一笔在途尝试 | AC-024 |
| 会话过期 | 创建会话后不操作,等待超过 expires_at | 页面不再接受新提交;若此前有在途支付,结果仍会确认 | AC-025 |
| 点击返回商户 | 在收银台点“返回商户” | 跳到 cancel_url;你的页面先查询状态,不直接标记“已取消” | — |
| 已成功订单再创建会话 | 对已支付订单再次创建会话 | 200 + status=completed 只读会话 | — |
| USD 与 JPY | 分别为 USD 10000 与 JPY 10000 创建会话 | 收银台显示 USD 100.00 与 JPY 10,000 | AC-028 |
退款测试
基于一笔 USD 10000 的成功支付:
| 场景 | 操作 | 预期 | AC |
|---|---|---|---|
| 部分退款 | 退 4000 | 201 processing(测试环境默认自动批准)→ succeeded;支付 refund_status=partial | AC-031 |
| 多次部分退款 | 再退 6000(新的退款号与幂等键) | 累计 10000,refund_status=full,支付状态仍为 succeeded | AC-031、AC-030 |
| 全额退款 | 显式传 amount 等于可退金额 | 成功后原支付成功事实保留 | AC-030 |
| 超过可退本金 | 已退 4000 后再申请 8000 | 422 refund_amount_exceeds | AC-032 |
| 并发超额 | 同时发起两笔 6000(不同退款号) | 只受理符合额度的一笔,另一笔 422 | AC-032 |
| 资金不足 | 请平台为你配置非零测试费率后,刚支付完成就全额退款 | 422 insufficient_refund_funds,message 给出差额,不生成退款单 | AC-034 |
| 同一退款号重复 | 换幂等键、同一 merchant_refund_no | 不创建第二笔(409 refund_no_conflict) | — |
| 退款超时 | 客户端超时后用同一个键重试 | 返回同一笔退款,不创建第二笔 | AC-033 |
| 退款失败 | 用 C13 卡支付后退款 | refund.failed,资金占用解除 | — |
| 只读 Key 退款 | 用只有 refunds:read 的 Key 创建退款 | 403 forbidden | AC-003 |
注意:测试环境的资金与审核
- 刚完成的支付资金处于待结算;平台可在上游支持时占用本支付自身的待结算净额。测试费率默认 0,全额退款通常不会出现资金差额;配置了非零测试费率时,原支付手续费默认不退,全额退款会差一个手续费——例如支付 100.00、手续费 3.50 时差 3.50。这是正确行为:可以先做部分退款,或联系平台在测试环境登记补款后用同一退款号与金额重新提交。
- 测试环境退款默认自动批准,创建后直接进入
processing;需要演练人工审核时,联系平台在测试环境把你的商户改为人工审核,之后退款停留在pending_review,由平台运营审批。
Webhook 测试
- 准备公网可达的 HTTPS 测试地址:使用公开测试端点或受控隧道把本地服务暴露出去;平台不会访问内网或本机地址。
- 在商户后台(测试环境)配置端点、订阅事件(需二次认证);测试签名密钥只在创建端点时显示一次,立即写入你的测试环境配置(遗失时轮换)。
- 点击“发送测试事件”:投递记录显示成功(2xx);你的日志显示验签通过并按事件 ID 存档。测试事件带
X-Pay-Test-Event: true,只投递 1 次、不重试,不产生真实支付、退款或分录。 - 篡改测试:用 Webhook 测试向量验证你的验签函数——改动请求体后必须拒绝(AC-059)。
- 失败重试:让端点临时返回 500,再用 C1 卡完成一笔测试支付,观察该
payment.succeeded事件在首次投递后 1 分钟、5 分钟的重试——事件 ID 相同、投递 ID 与签名时间不同;恢复后返回 2xx,确认能被接受并去重(AC-060)。测试事件不重试,所以这一步用真实的测试支付事件。 - 超时去重:让端点等 6 秒才返回 200(超过 5 秒),平台判定超时并重投;确认业务只执行一次。
- 乱序:用 C7 场景观察
payment.failed与payment.succeeded的version,确认你的逻辑按version处理。 - 最终失败(可选,完整周期约 48 小时):端点持续失败,8 次后进入失败列表并收到邮件通知;用
GET /v1/events/{event_id}补齐,或请平台人工重发(事件 ID 不变,可同时验证重复投递只处理一次,AC-061)。 - 内网地址:尝试把端点配置为内网或
localhost地址,应被拒绝(AC-062)。
投递结果在 事件投递记录(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者) 查看。
幂等与异常测试
| 场景 | 操作 | 预期 | AC |
|---|---|---|---|
| 同键顺序重复 | 同一个键创建支付 10 次 | 同一个 Payment,只扣一次 | AC-014 |
| 同键并发 | 50 个并发请求使用同一个键 | 只有一个执行,其他返回同一对象或 409 operation_in_progress | AC-015 |
| 同键不同金额 | 同一个键改金额再提交 | 409 idempotency_conflict | AC-016 |
| 换键同订单号 | 新键 + 同一订单号 | 不产生第二个 Payment | AC-017 |
| 同键重试换卡 | 同一个键重试但换成另一张测试卡 | 复用原操作,不用新卡扣款 | AC-018 |
| 处理中用新键 confirm | C3 场景停在 requires_action 时用新键 confirm | 409 payment_in_progress | AC-027 |
| 网络超时后同键重试 | 客户端超时设为 100 毫秒发起 C1 支付,再用同一个键重试 | 得到同一 Payment,只扣一次 | AC-069 |
| 取消可能已提交的支付 | C3 场景 requires_action 时调用取消 | 202 或保持原状态;只有上游确认安全取消后才会变成 canceled | AC-029 |
| 环境隔离 | 生产开通后,用生产 Key 查询测试支付号(或反之) | 404 | AC-002 |
| 只读 Key 写操作 | 用只有 payments:read 的 Key 创建支付 | 403 forbidden | AC-003 |
商户自测用例清单
勾选状态只保存在本机。“怎么测”会定位到上面对应的场景行。
已完成 0 / 33
支付 0/7收银台 0/5幂等 0/8退款 0/5Webhook 0/5权限与隔离 0/3
支付
MT-01无需 3DS 的直接支付成功,并只履约一次支付 · 对应 AC AC-008 ·MT-023DS 挑战:整页跳转认证地址,return_url 页面以服务端查询为准支付 · 对应 AC AC-009 ·MT-033DS 成功但发卡行拒绝:不履约,提示换卡支付 · 对应 AC AC-012 ·MT-04拒绝后换卡:用新幂等键 confirm,沿用同一 Payment支付 · 对应 AC AC-026 ·MT-05结果未知(202):显示“正在确认”,不重扣,最终按通知或查询处理支付 · 对应 AC AC-019 ·MT-06USD 与 JPY 金额按最小货币单位换算正确支付 · 对应 AC AC-028 ·MT-07手动给 return_url 加 ?status=succeeded 之类的参数不会触发履约支付 · 对应 AC AC-013收银台
MT-08收银台无需 3DS 支付成功,页面、查询、通知结果一致收银台 · 对应 AC AC-010 ·MT-09收银台 3DS 支付成功收银台 · 对应 AC AC-011 ·MT-10两个标签页同时提交只产生一笔在途尝试收银台 · 对应 AC AC-024 ·MT-11会话过期时不把订单判为失败收银台 · 对应 AC AC-025 ·MT-12点击返回商户后先查询,不直接标记已取消收银台 · 对应 AC 商户侧对应点 ·幂等
MT-13同键重复提交 10 次只有一个 Payment幂等 · 对应 AC AC-014 ·MT-14同键并发只有一个执行幂等 · 对应 AC AC-015 ·MT-15同键不同金额收到 409 并触发告警幂等 · 对应 AC AC-016 ·MT-16换键同订单号不产生第二个 Payment幂等 · 对应 AC AC-017 ·MT-17同键重试换卡不会用新卡扣款幂等 · 对应 AC AC-018 ·MT-18处理中用新键 confirm 被拒,不新扣款幂等 · 对应 AC AC-027 ·MT-195xx / 超时后同键重试或查单;代码中不存在“换键重扣”的路径幂等 · 对应 AC AC-069 ·MT-20取消可能已提交的支付时,以 canceled 结果为准幂等 · 对应 AC AC-029 ·退款
MT-21全额退款显式传金额退款 · 对应 AC AC-030 ·MT-22多次部分退款累计与 refund_status 正确退款 · 对应 AC AC-031 ·MT-23超额退款被拒并正确提示退款 · 对应 AC AC-032 ·MT-24退款超时后同键重试不产生第二笔退款 · 对应 AC AC-033 ·MT-25资金不足时向操作人展示差额,不自动缩减金额退款 · 对应 AC AC-034 ·Webhook
MT-26篡改请求体后验签失败,不执行业务Webhook · 对应 AC AC-059 ·MT-27旧事件以新签名时间重投时被接受并按事件 ID 去重Webhook · 对应 AC AC-060 ·MT-28端点失败后按计划重试;恢复后能补齐遗漏事件Webhook · 对应 AC AC-061 ·MT-29内网或本机地址无法配置为端点Webhook · 对应 AC AC-062 ·MT-30乱序事件按 version 处理,旧事件不覆盖新状态Webhook · 对应 AC AC-022(商户侧对应点) ·权限与隔离
MT-31测试 Key 与生产 Key 互相访问不到对方的对象权限与隔离 · 对应 AC AC-002 ·MT-32只读 Key 无法创建退款权限与隔离 · 对应 AC AC-003 ·MT-33你的日志、错误上报中不出现卡号、CVC、API Key、签名密钥、认证链接权限与隔离 · 对应 AC AC-064(商户侧对应点)
从测试到生产
| 项目 | 测试 | 生产需要重新做的事 |
|---|---|---|
| API Key | 测试 Key | 所有者或管理员在生产环境创建(需二次认证),按最小权限范围 |
| Webhook | 测试端点与密钥 | 配置生产端点,保存生产签名密钥 |
| 回跳地址 | 测试地址 | 登记生产域名下的地址 |
| 卡 | 沙箱测试卡 | 真实卡;不要在生产使用测试卡 |
| 退款 | 默认自动批准(可请平台改为人工审核演练) | 默认人工审核 |
| 下一步 | — | 按上线检查清单逐项确认 |
这页有帮助吗?