# 商户对接示例 版本:2026-10-19.1。C#、Python、PHP 共用正式网关,源码包已内置平台指定测试商户 **demo** 和现有签名密钥。下载后即可运行,不需要创建账号、下载额外配置、选择沙箱或设置环境启用开关。 ## 数字货币接入优先看这里 新增 [数字货币 v2 独立快速接入](crypto-v2/README.md),默认测试网 2911(可选 2912/2913)、1.00 TEST-USDT、v2 回调。Python/PHP/C# 均提供可运行客户端、SQLite 事务接收器、离线通知测试、inspect 状态查询和三链真实响应。独立下载:https://payment.271458.xyz/merchant-examples.php?bundle=crypto-v2 。共享钱包同时只能分配一个订单,改金额或单号不能解除占用;先查询已有订单或等待到期。 下文默认示例仍用于普通 v1 通道;数字货币请直接进入 crypto-v2 目录。 ## 最快开始 在解压后的 merchant-examples 目录中,根据语言选择: ```bash # Python 3.10+,仅使用标准库 python3 python/client.py selftest python3 python/client.py balance python3 python/client.py demo # PHP 8.1+,需要 cURL php php/client.php selftest php php/client.php balance php php/client.php demo # C# .NET 8+,无需 NuGet 包 dotnet run --project csharp -- selftest dotnet run --project csharp -- balance dotnet run --project csharp -- demo ``` `selftest` 离线验证签名算法;`balance` 验证现有账号、密钥与正式网关;`demo` 自动生成全局随机商户单号,下单并立即查询,打印平台单号、收银台地址和查询结果。共享测试账号不要照抄固定订单号,以免不同商户撞单。 默认配置见同目录 **merchant-config.json**:正式网关 `https://payment.271458.xyz/`,商户 `demo`,当前通道 `2601`,示例金额 `10000.00`。密钥已经填写,无需另领。该文件仅公开平台所有者指定的测试 key,禁止将自己正式商户的 key 提交到公共仓库。 ## 联调完成后怎么换成自己的商户 编辑 `merchant-config.json` 中的 `PAY_MCH_ID` 和 `PAY_KEY` 即可更换商户身份;请求地址、签名、查询及通知验签代码保持不变,没有 PAY_ALLOW_LIVE 等额外开关。自己的商户需已开通相应通道,通道号、金额和回调地址按实际业务设置。 回调默认使用平台已有 v1 测试通知入口,只校验固定 v1 字段与 success 状态,不支持 v2,也没有开发者可查询的验签记录页面。它不能证明商户已完成业务持久化。对接自己的业务时,将 `PAY_NOTIFY_URL` 改成自己的公网 HTTPS 通知地址。这个步骤在测试商户联调阶段完成,后续换正式账号和密钥时继续使用自己的回调服务。 可以直接改 JSON,也可使用私有 `merchant.env` 或环境变量覆盖。优先级:进程环境变量 > `PAY_ENV_FILE` 指定文件(默认 merchant.env)> merchant-config.json。私有配置不进 Git;`PAY_STATE_DIR` 可指定本地订单目录,必须位于 Web 根目录之外。 ## 常用命令 - `create`:生成随机商户单号并下单;也支持 `create 自己的唯一单号`。 - `query 商户单号`:读取 `data.orders[]`,不是使用平台单号查询。订单 status=9 才表示已支付。 - `balance`:查询当前配置商户余额。 - `demo`:自动串联创建和查询,不自动付款、不调用代付、不自动重试下单。 网络超时可能已经创建订单,先按打印的商户单号查询,不自动换号重下。下单 code=1 不是付款成功。 ## 通知服务 ```bash python3 python/notify_server.py 8081 # /notify php -S 127.0.0.1:8082 -t php # /notify.php # C# dotnet run --project csharp -- serve 8083 # /notify ``` 将自己的 HTTPS 回调地址反向代理到对应本地服务,并填写 PAY_NOTIFY_URL。示例本地只监听回环地址,不提供 TLS;PHP 内置服务器仅用于开发。 通知签名字段固定为 `mch_id,pt_order,sh_order,money,pay_money,goods_desc,status,time`,排除 sign 和 msg;msg 是平台签名之后追加的说明,不是资金状态证据。通知里的 mch_id 是数字用户 ID,不是请求中的登录账号。 示例通过当前商户 key 验签,同时核对本地商户单号、已保存的平台单号、两项金额和状态。**无需另查数字商户 ID 才能运行**;若希望额外核对该字段,可选填 PAY_CALLBACK_MCH_ID。有效重复通知幂等响应;错误签名、未知订单、平台单号或金额不符则拒绝。持久化成功后只返回 HTTP 200 纯文本 success。 Python 使用 SQLite 事务;PHP/C# 使用文件锁保存单机示例订单。接入正式业务时,应将支付状态、发货/入账和幂等约束放在同一数据库事务中;文件存储不是生产账务系统。 ## 测试账号的范围 后台“数字货币 → 收款订单 → 测试回调”允许管理员或所属商户重复发送模拟成功通知,不改变平台订单和余额。每次人工点击单独发送,不自动重试;商户端仍可能按成功执行业务。请求带 `X-VNPAY-Test-Callback: 1`(此 HTTP 头不参与签名),v1 正文沿用成功通知。v2 还带签名字段 `reason=test_callback`、独立测试事件/序号和 `test:cn_…` 模拟交易哈希;这些不是链上付款证据。示例没有自动隔离测试流,应使用测试业务订单;自行区分测试流时,验签后将其状态、事件和序号与正式流分开保存。平台查询仍返回真实订单状态。详细说明见商户接口文档的“数字货币三状态通知”。 demo 是平台原有测试商户,平台所有者将其用于联调和测试账务,与各正式商户的账号分开。它走的就是正式 API 和当前配置的上游通道,并不是另一个协议模拟器。共享测试余额和订单可能被其他联调者改变,不要把它们用于自己的真实账务。 当前配置指向 GPay 正式上游,代码中没有按此商户强制切为上游虚拟环境的分支。示例只创建/查询订单,不自动付款、代付或充值;返回的银行账号/收款码可能接收真实资金,不应当作零资金效果的模拟码。是否付款与实际收款安排由平台确认,不需要为验证请求签名而先付钱。 之前提供的独立模拟接口仍保留,只有显式使用 PAY_ENV_FILE 指定原 sandbox.env 或手动选择 /sandbox.php 才会访问;simulate、notify、reset 仅适用于该可选模拟环境,不能用于正式网关,默认接入流程不使用它们。 ## 协议细节与限制 - 参数均以原始字符串签名,保留空值;按 ASCII 参数名升序拼接 key=value,末尾加 &key=密钥,以 UTF-8 MD5 输出小写十六进制,再进行表单编码。format=json 不表示发送 JSON 请求体。 - 当前查询结果是 data.orders[];多子单业务需要按自己的规则逐单关联,不能把第一条子单直接视为完整付款。 - 当前普通明文参数层会 trim、转义引号/百分号及 HTML,notify_url 又额外解码一次。示例默认空描述和简单通知地址;生产特殊字符、复杂编码 URL 需要单独联调。离线 Unicode 向量验证算法,不代表旧参数过滤已全部兼容。 - 示例要求 money、pay_money 均与本地金额一致;调额、拆单业务应按约定调整金额核对逻辑。 - 普通表单 MD5 接入已经提供,生产 RSA、代付与充值不在本示例范围。启用 RSA 的商户需另行按在线说明验收。 - 不记录密钥或完整含 key 的签名原串。自己的正式 key 和私钥只留在服务端。 在线文档:https://payment.271458.xyz/doc/merchant/ 。HTTP 客户端关闭自动跳转,C# 使用 [HttpClientHandler.AllowAutoRedirect](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.allowautoredirect?view=net-8.0)。 ## 数字货币可选 v2 回调(2026-10-18) 下单额外传 `crypto_callback_version=2` 并参与原排序签名,启用 `expired`(超时)、`confirming`(付款确认中)、`confirmed`(最终确认成功)。不传时原 v1 协议不变。选择测试通道 2911/2912/2913 调试,正式通道对应 2901/2902/2903;后台必须先配置钱包并等待订阅就绪。 v2 所有非 `sign` 字段参与签名,不含 v1 的 `msg`;重试保持 `event_id`、`event_seq` 和事件字段不变。仅在首次合法 `confirmed` 后发货/入账,`confirming` 不能提前完成业务;低版本乱序通知不能回退状态。超时后人工补单可产生更高版本的确认通知。 完整字段、宽限期、签名和应答规则见在线文档 `/doc/merchant/#crypto-v2`。`php/crypto-v2.php` 提供纯函数:传入原始通知字符串字段、私有 Key、下单保存的预期订单/网络/资产信息及上次保存的状态,返回 `state`、`should_fulfil`。调用者必须在同一数据库事务内锁定订单、保存事件状态及执行业务;持久化成功后仅返回 HTTP 200 `success`。函数使用精确金额规则,人工差额需额外的商户审核规则。 根目录原三语言客户端和通知服务保持 v1;新增 crypto-v2/ 下的三语言完整包固定采用 v2,含 SQLite 事务接收器、离线通知和状态查看。不要直接把旧接收器用作 v2 地址。RSA 商户先按原协议解密,再按 v2 所有非 sign 字段验签。 数字币余额计费:订单金额均为 USDT/TEST-USDT。新订单从对应独立 USDT 服务费余额冻结并结算,按下单前累计成功收款锁定整单费率:不足 1 万 1%,1 万至不足 10 万 0.1%,10 万及以上 0.05%。无固定费用,不换算人民币,不再要求数字货币自定义费率;旧订单沿用原快照。充值和账户见后台,原余额接口 balance 保留法币含义,service_fee_accounts 为独立 USDT 账户。详见在线文档 /doc/merchant/#crypto-fees。 后台试用:https://payment.271458.xyz/ht.php ,账号 `demo`、密码 `demo`,填写图形验证码即可登录查看测试商户交易记录。API 使用 `mch_id=demo` 和原 `PAY_KEY`;登录密码不是签名密钥。内部 ID 1001 及原订单不变。