商户对接文档

沙箱测试指南

契约版本:2026-10-27.3

沙箱用于联调下单、查单、异步通知与签名验签,不连接真实支付通道,不产生真实资金流动。核心接口与签名协议共用本开发文档;产品开通、金额范围及模拟工具差异见环境说明。

沙箱凭据不能用于生产,生产凭据不能用于沙箱。不要把生产 appSecret 填入沙箱。

地址

开始步骤

  1. 打开沙箱商户后台自助注册:填写常用邮箱(不支持一次性/临时邮箱)、通过人机验证、填写邮箱验证码并设置登录密码。无需提交资质,无需人工审核。为防滥用,验证码发送与注册按 IP 和邮箱限频,验证码连续输错会暂时锁定,请稍后再试。
  2. 注册成功页一次性显示 应用ID(appId)、appSecret 和沙箱网关地址,请立即保存。之后可登录沙箱商户后台,在应用页面输入登录密码再次查看 appSecret;如需更换,可在应用页面“重置密钥”(登录密码 + 邮箱验证码,每个商户每天最多 3 次),旧密钥立即失效,新密钥只显示一次。新注册商户默认开通全部沙箱产品(含 THB 代收/代付);连续 90 天无登录、无下单的沙箱账户会被停用,如需恢复请联系平台。
  3. 每个币种账户有 1,000,000 测试余额(用于代付测试),开户后异步入账,通常几分钟内到账。
  4. 配置通知地址:在沙箱后台填写默认通知地址,或每笔下单传 notifyUrl。
  5. 沙箱默认不校验代付来源 IP(沙箱无真钱),注册后即可直接测试代付。如需演练生产白名单:先在API服务器运行 quickstart.py network 获取沙箱看到的来源IP,在沙箱测试页的“沙箱代付 IP 白名单”保存并输入沙箱登录密码,再用沙箱凭据调用 POST /transaction/sandbox/v1/settings/update,body {"payoutIpWhitelistMode":"PRODUCTION"} 开启“模拟生产白名单”;之后与生产一致,来源 IP 不在名单返回 10010。改回 "SANDBOX_OPEN" 即恢复不校验;当前设置用 /transaction/sandbox/v1/settings/query 查看。生产始终校验白名单。
  6. 5 分钟自检:注册成功页和应用页面提供 quickstart 包下载(已预填 appId 与沙箱网关,不含 appSecret)及复制即用的命令,运行 python3 selfcheck.py 依次完成 THB 余额查询、一笔 THB 代收(1000.00)下单与查单,全部 PASS 即说明网络、时间戳和签名正确。也可手动调用 POST {沙箱网关}/transaction/payout/v1/balance,body {"currency":"INR"},返回 code=0。签名规则与生产完全相同。

场景与查询状态

金额场景代收代付
100.00成功(未列出的尾数同此)约 3 秒后成功,发送成功通知约 3 秒后成功,通知 status="1"
100.01失败失败后 paymentStatus=FAILED、isFinal=true;旧payStatus仍为0,不发失败通知付款失败,通知 status="2"
100.02一直处理中保持待支付,不自动结束(可用测试收银台手动结束)保持处理中,查单 payStatus=0
100.03超时 / 结果未知下单结果不确定,订单不自动结束;用原 merchantOrderNo 查单确认受理后结果未知,查单保持 payStatus=0
100.04延迟成功约 60 秒后成功约 60 秒后成功
100.05重复回调模拟渠道重复通知,由平台去重;商户幂等用quickstart replay单独验证模拟重复渠道回调;商户是否收到重复通知以实际记录为准
100.06实收金额不同成功,realAmount = 金额 × 0.95(按币种精度四舍五入,如 100.06 → 95.06)不适用,按成功处理
100.07延迟失败约 60 秒后失败(不发通知)约 60 秒后失败,通知 status="2"
100.08明确拒绝下单返回 code=10009,未生成收银台;该商户单号已被占用,查单为 PROCESSING / isFinal=false 且不会自动转为失败。不要用同一单号重提,换新单号测试其他场景下单被拒绝
100.09收银台手动不自动结束;打开 payUrl,在测试收银台点「支付成功」或「支付失败」不适用,按一直处理中

重复渠道回调不保证商户会收到两次通知;商户幂等请使用 quickstart replay,报告需显示至少两次验证投递、仅一笔入账。

  • 两位小数币种(USD、INR、EUR、GBP、AUD 等):取小数点后两位:100.05、2500.05均为重复渠道回调。沙箱当前代付最低100主币种单位;生产USD/AUD基础最低1,其他币种100,还受商户和通道配置限制。
  • USDT:最多 4 位小数,只看小数点后第 1、2 位:10.05、10.0512 都是「重复回调」,10.0001 是「成功」;5 位小数会被拒绝。USDT 代付最低 100,代付测试请用 100.05 这类金额。
  • JPY:整数金额,取整数最后两位:1000 成功、1001 失败、1005 重复回调、1006 实收 956;带小数(如 1000.50)返回 10006。
  • 任意币种:也可显式指定场景(优先于金额):代收 cardExtra = "sandbox:FAIL",代付收款账号 account = "SANDBOX-FAIL";cardExtra 参与签名。
  • THB(泰铢):同两位小数规则,但金额必须在 500.00–49999.00 之间:1000.00 成功、1000.01 失败、1000.02 处理中、1000.03 结果未知、1000.06 实收 950.06、1000.08 明确拒绝。THB 不支持 cardExtra 场景提示或 SANDBOX- 账号提示,只按金额尾数选择场景。

钱包与测试卡

方式currencypayType
Cash AppUSDCASHAPP
Apple PayUSDAPPLE_PAY
Google PayUSDGOOGLE_PAY
PayPalUSDPAYPAL
Cash (separate from Cash App)USDCASH
USDTUSDTCRYPTO

product 填沙箱账户已签约产品编码,不填 payType 或上游 channelCode。钱包测试只验证下单、查单、签名回调,不包含真实钱包唤起或扣款。USDT 地址和 txHash 均为模拟,切勿转账。 模拟收银台(payUrl)全页带“SANDBOX 测试环境 · 不会扣款 / NO REAL CHARGE”水印,不显示任何银行或商户品牌,只显示平台记录的订单号与金额;链接默认 30 分钟内有效,过期后页面不可提交,可用 trigger 接口或重新下单。页面禁止被 iframe 嵌入且不被搜索引擎收录。

仅供商户表单联调。模拟器不读取卡号或CVV,不扣款;不要传入API或cardExtra,不用于真实上游。

卡号:4242 4242 4242 4242;有效期:12/39;CVV:123;姓名:SANDBOX TEST。

回调规则

  • 平台以 POST JSON 请求 notifyUrl,头部带 appId、timestamp、nonce、sign,签名规则与请求签名相同;请先验签。
  • 处理成功后返回 HTTP 2xx,响应体为纯文本 SUCCESS;否则平台按生产规则重试。
  • 按 realAmount 幂等入账,只入账一次;不能只用 nonce 去重(每次通知的 nonce 不同)。沙箱代收失败不发通知,用查单发现(生产部分通道会发失败通知,见开发文档第 10 节)。
  • notifyUrl 必须是公网地址,只能用 80(http)或 443(https)端口,推荐 https;解析到回环、内网、链路本地、云元数据地址(169.254.169.254 及 IPv6 等价地址)的通知直接判失败、不再重试。平台使用解析时校验过的 IP 建立连接(防 DNS rebinding),不跟随重定向,连接约 5 秒、单次总计约 10 秒超时,响应体最多读取 64KB。
  • 通知金额:法币固定两位小数(JPY 也是,如 "1000.00"),USDT 为 4 位小数;USDT 成功通知带 txHash(沙箱模拟值,切勿向沙箱地址转账)。
  • 沙箱防滥用:每商户每分钟最多 60 次通知投递,同一目标主机每分钟最多 120 次(所有商户合计),超出的投递顺延到下一分钟、不占重试次数;同一 notifyUrl 连续失败 5 次后暂停投递 10 分钟(再次失败时长翻倍,最长 60 分钟),暂停期满只放一次探测,成功即恢复。每商户每日通知上限 10000 次。
  • 投递记录:用沙箱凭据调用 POST /transaction/sandbox/v1/notify-log/query(签名同查单,body {"limit":50},最多 100 条)查看本商户最近的投递结果:ACK、NOT_ACK、HTTP_ERROR、DEFERRED_MERCHANT_RATE / DEFERRED_HOST_RATE(限速顺延)、CIRCUIT_OPENED / CIRCUIT_OPEN(熔断)、BLOCKED_*(目标不允许)。记录只含主机与路径摘要,保留 7 天。
  • 沙箱回调来源 IP:3.109.43.0(固定)。若您的通知接收服务器有入站白名单,请加入该地址;生产回调来源 IP 不同,见生产开通通知。

错误排查

code含义处理
10010来源 IP 不在白名单(代付;沙箱仅在开启“模拟生产白名单”后出现)把服务器访问沙箱时的真实公网出口 IP 加入沙箱代付白名单,或用 /transaction/sandbox/v1/settings/update 改回 SANDBOX_OPEN
10009通道拒绝或暂不可用(沙箱 100.08 明确拒绝场景)按原 merchantOrderNo 查单;不要自动换单重付。沙箱 100.08 的原单会保持 PROCESSING
10020商户单号重复:该单号已存在(无论参数是否相同)同一单号只提交一次;结果不确定时查原单,不要重提
10013通道尚未接入,订单未创建视为该产品暂不可用,不要重试
10012payType 与币种不匹配,或传了空字符串对照文档「支付方式编码」表;不需要指定时请省略 payType 字段,不要传空字符串,也不要传 null(请求体任何字段为 null 都返回 400)
10006金额不允许(超出范围或币种精度)法币 2 位、JPY 整数、USDT 最多 4 位小数
10002币种不可用使用已开通的币种
10001应用不可用联系对接人核对应用状态

可运行自测工具

下载 quickstart.zip,解压后复制 sandbox-config.example.json 为 sandbox-config.json,填独立沙箱凭据、已签约 product 和自有 HTTPS notifyUrl,再执行 chmod 600 sandbox-config.json。

python3 quickstart.py balance --currency USD
python3 quickstart.py network
python3 quickstart.py serve

serve 监听本机127.0.0.1:8099/notify;由你的HTTPS反向代理将公网443的通知路径转到它。网关不能访问 localhost。保持接收器运行,在另一终端测试(每笔新业务换单号,未知结果先查原单):

python3 quickstart.py create --order DEMO_001 --amount 100.00 --pay-type CASHAPP
python3 quickstart.py query --order DEMO_001
python3 quickstart.py report
python3 quickstart.py replay --order 返回的平台orderNo
python3 quickstart.py report

工具先记录订单意图,再请求下单;回调验签并核对订单、产品、币种和原金额后,按有效realAmount写一次本地SQLite测试账。replay只对本机接收器重新签名投递已验证通知,报告中至少两次投递且只有一笔credit才完成该样例幂等验收。这不是上游真实重发验收,也不是可直接代替生产账务的服务。失败/处理中仍查单;不要把缺少回调当支付失败。配置与SQLite文件不应上传仓库。

环境差异

已开通且已签约的沙箱产品可测试,不代表生产账户已开通对应产品;沙箱后台提现不可用;每商户每日代收、代付各限2000笔、通知10000次;数据定期清理。沙箱代付当前最低100主币种单位,生产按币种和配置执行。

沙箱当前代付最低为100主币种单位;生产USD/AUD基础最低为1,其他币种为100,实际范围还受产品、商户和通道配置限制。示例统一用100避免误导;沙箱成功不表示生产已开通。

代付测试工具

美国 USD / 澳洲 AUD 银行卡代付模拟自测

商户沙箱模拟资金服务与正式银行卡代付是两套环境。模拟器不连接银行卡机构,不使用真实卡号,不要求服务商受益人编号,也不证明真实银行卡到账。生产 API 的 extra/收款卡资料只用于已开通的正式产品。

  1. 登录沙箱商户后台,分别确认 USD 或 AUD 账户、同币种应用、代付产品与路由已启用,并有足够模拟余额。不能用代收 product 或生产凭据。应用缺失请联系平台配置。
  2. 沙箱默认不校验代付来源 IP,可跳过本步。若已开启“模拟生产白名单”(开始步骤 5),需将调用服务器真实公网出口 IP 保存到沙箱代付白名单;执行 network 可查看平台看到的来源,10010 表示未加白。
  3. 下载 quickstart.zip,配置 sandbox-config.json:保留独立 appId/appSecret,增加 payoutCurrency(USD 或 AUD)及对应 payoutProduct(USD-LOCAL-PAYOUT 或 AUD-LOCAL-PAYOUT,须先签约);这两个 CARD 模拟产品不传 payoutBizCode/payoutBranchCode,notifyUrl 填您自己的 HTTPS 地址。执行 chmod 600 sandbox-config.json。
  4. 启动下面的独立代付接收器。将自有公网 HTTPS /notify 反向代理到 127.0.0.1:8100/notify。不要同时将代收与代付接收器代理到同一个路径。
python3 payout_sandbox.py balance
python3 payout_sandbox.py network
python3 payout_sandbox.py serve

另一终端依次执行(每个 create 为新的模拟订单,使用唯一单号;失败或超时后仅查询原单):

python3 payout_sandbox.py create --order OUT_OK_001 --scenario SUCCESS
python3 payout_sandbox.py create --order OUT_FAIL_001 --scenario FAIL
python3 payout_sandbox.py create --order OUT_UNKNOWN_001 --scenario TIMEOUT
python3 payout_sandbox.py query --order OUT_OK_001
python3 payout_sandbox.py query --order OUT_FAIL_001
python3 payout_sandbox.py query --order OUT_UNKNOWN_001
python3 payout_sandbox.py simulation --order OUT_UNKNOWN_001
python3 payout_sandbox.py trigger --order OUT_UNKNOWN_001 --event PAID
python3 payout_sandbox.py query --order OUT_UNKNOWN_001
python3 payout_sandbox.py report

初次查询可能仍处理中,间隔至少 3–5 秒再查。预期 SUCCESS 最终 SUCCEEDED/true;FAIL 最终 FAILED/true;TIMEOUT 在手动触发前非终态,不能自行解冻、重付或判失败。trigger 仅在沙箱、该应用自己的可手动场景中有效;生产没有这个接口。可另建 PENDING 单并 trigger FAILED 验证另一终态。下单未知后工具会保留原订单意图,拒绝同号再次提交。

收到通知后 report 应显示 outcome、verifiedDeliveries、localResultWrites=1;成功、失败分别记录一次测试结果,未知且未通知时为 PENDING。接收器先验签并核对应用、单号、产品、币种、金额,再事务保存,最后 HTTP200纯文本SUCCESS。重复同状态只增加投递计数,冲突状态标为 REVIEW 并拒绝 ACK。

python3 payout_sandbox.py replay --order 已验证的HissPay平台订单号
python3 payout_sandbox.py report
python3 payout_sandbox.py resend --order 原商户单号

replay 是本机重新签名的函数级去重测试,不是网关发送;只有已验签的通知才允许重放。resend 才请求沙箱平台重发最近终态通知,最短间隔10秒、每单最多20次;实际送达以接收器记录为准。simulation 的 deliveries 是模拟渠道投递到平台的记录,不能代替商户接收日志。测试报告不能把这三类投递混为一谈。

10005 检查代付路由,10011 检查代付产品,10006 检查额度,48001 表示该订单当前不允许手动终态或重发;429 退避。查询与回调最终核对一致才算该模拟用例完成。测试不使用真实资金,SQLite 结果记录不是生产账务系统。

两个币种分开配置

美国测试使用 payoutCurrency: "USD",澳洲测试使用 payoutCurrency: "AUD"。分别保存 sandbox-usd.json、sandbox-aud.json,使用各自币种的应用凭据和代付产品;不要只改币种却沿用另一币种应用或代收产品。缺少应用、产品、路由或模拟余额时请平台配置。默认金额100分别代表100 USD和100 AUD,不进行汇兑。

chmod 600 sandbox-usd.json sandbox-aud.json
python3 payout_sandbox.py --config sandbox-usd.json --db usd-payout.sqlite balance
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite balance
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite serve --port 8101
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite create --order AUD_OK_001 --scenario SUCCESS
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite create --order AUD_FAIL_001 --scenario FAIL
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite create --order AUD_UNKNOWN_001 --scenario TIMEOUT
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite query --order AUD_UNKNOWN_001
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite trigger --order AUD_UNKNOWN_001 --event FAILED
python3 payout_sandbox.py --config sandbox-aud.json --db aud-payout.sqlite report

USD同样依次执行成功、失败、未知、手动终态与resend流程,替换配置、数据库和独立单号即可。USD接收器使用8100,AUD使用8101,各配置的notifyUrl必须指向相应接收器。每次新测试使用全新单号;未知仅查原单,不能重新提交。

Python 和 Java 单次请求

ZIP内含 payout-sandbox-usd.json / .properties 和 payout-sandbox-aud.json / .properties。这些文件仅用 SANDBOX-SUCCESS 合成收款标记,不填写真实卡号、有效期、CVV或受益人。使用已签约的 USD-LOCAL-PAYOUT 或 AUD-LOCAL-PAYOUT 模拟产品,省略 bizCode/branchCode,填写自己的 HTTPS 通知地址;需要失败或未知场景时将account改为SANDBOX-FAIL或SANDBOX-TIMEOUT。

export HISSPAY_GATEWAY=https://sandbox-api.hisspay.com
# HISSPAY_APP_ID、HISSPAY_APP_SECRET由您在本机安全设置,勿提交仓库。
chmod 600 payout-sandbox-aud.json payout-sandbox-aud.properties
python3 payout_request.py apply payout-sandbox-aud.json
python3 payout_request.py apply payout-sandbox-aud.json --send
python3 payout_request.py query payout-sandbox-aud.json --send
javac PayoutRequest.java
java PayoutRequest apply payout-sandbox-aud.properties
# Java测试须换新单号;不要重复执行刚才Python的下单。
java PayoutRequest apply payout-sandbox-aud.properties --send
java PayoutRequest query payout-sandbox-aud.properties --send

不带--send仅预览。单次请求示例不维护本地订单意图或回调账,完整回调与幂等验收请使用payout_sandbox.py;不要混用两种工具对同一笔订单下单。USD示例替换文件并设置USD应用凭据。合成收款标记禁止发送到正式网关。AUD模拟通过不代表正式AUD银行卡代付已开放;真实产品需另行确认资格及接口验收。

泰国 THB 模拟

沙箱 THB 产品与生产 THB 使用同一套字段规则(开发文档第 16 节),不连接任何银行或 PromptPay,不产生真实付款。沙箱商户需已签约 THB-PROMPTPAY-PAYIN(代收)和 THB-BANK-PAYOUT(代付),并有 THB 账户;新注册的沙箱商户自动开通,已有沙箱商户由平台统一补开(如仍未开通请联系平台)。

代收:currency=THB、product=THB-PROMPTPAY-PAYIN,不传 payType;cardExtra 为字符串 {"payerAccountNo":"1234567890","payerBankCode":"SCB"}(账号 6–20 位数字,银行为 SCB/KTB/BAY/GSB/KBANK/TTB),name 用字母户名,phone 6–15 位数字可带 +,email 必填(不传或空字符串返回 400 email invalid,格式形如 payer@example.com,不超过 64 个字符),returnUrl 不传或为不含 ?/# 的 https 地址(不超过 128 个字符)。字段不合规时返回 9999,原单查单为 CANCELLED,没有付款链接——这与生产一致,用来自查请求。沙箱 payUrl 是模拟收银台(带“SANDBOX 测试环境 · 不会扣款”水印,链接默认 30 分钟内有效,过期后请用 trigger 或重新下单),代替生产的 PromptPay 二维码页;尾数 .09 可在页面上点「支付成功/失败」。

python3 quickstart.py create --order THB1026_DEMO_001 --amount 1000.00 --currency THB --payer-bank SCB
python3 quickstart.py query --order THB1026_DEMO_001

代付:currency=THB、product=THB-BANK-PAYOUT、bizCode=BANK、branchCode 为 GSB/TTB/BBL/BAY/SCB/KBANK/KTB、account 为 6–20 位数字(不能用 SANDBOX- 前缀)、accountName 字母户名(沙箱默认不超过 10 个字符,与生产付款通道的默认上限一致;生产以开通通知为准)、phone、email 同上,payCode 填 BANK。沙箱默认 7 家银行都可用(生产可用银行以开通通知为准,可能更少)。字段不合规时与开发文档第 11 节「代付下单的拒绝与 msg」及 16.4 所述的生产现行行为一致:下单同步被拒、不建单,用原单号查单返回 30001;按 16.4 自查清单修正后用新单号重下。尾数 .03(结果未知)的订单保持处理中,用于验证「超时不当失败、不重复代付」;可在模拟工具里手动触发终态。

# sandbox-config.json: "payoutCurrency":"THB","payoutProduct":"THB-BANK-PAYOUT",可选 "payoutBranchCode":"KBANK"
python3 payout_sandbox.py create --order THB1026_OUT_001 --amount 2000 --scenario SUCCESS
python3 payout_sandbox.py create --order THB1026_OUT_002 --amount 2000 --scenario TIMEOUT
python3 payout_sandbox.py query --order THB1026_OUT_002

THB 场景只由金额尾数决定:--scenario 把整数金额的两位小数设成对应尾数(SUCCESS .00、FAIL .01、PENDING .02、TIMEOUT .03、DELAYED_SUCCESS .04、DUPLICATE_CALLBACK .05、DELAYED_FAIL .07、REJECTED .08),金额须在 500.00–49999.00 之间。沙箱代付手续费按沙箱签约费率从 THB 测试余额中扣除。沙箱成功不代表生产 THB 已为您开通。