# 数字货币 v2 快速接入(Python / PHP / C#) 2026-10-19。独立下载: https://payment.271458.xyz/merchant-examples.php?bundle=crypto-v2 。解压后进入 `merchant-examples/crypto-v2`,保留其上级目录中的共享 HTTP 客户端文件。 此包固定在下单签名中加入 `crypto_callback_version=2`。内置平台所有者指定的公开测试商户 demo 和测试 Key,默认 `2911`、`1.00 TEST-USDT`;可在 `config.json` 改为 2912(TRON Nile)或 2913(BSC Testnet)。不需要修改三个语言的签名代码。 **共享钱包限制:一个钱包同时服务一笔订单,多个地址可轮转。更换金额、单号不能解除占用。** 如果已提交过订单,先按原商户单号查询;如果是其他联调者占用,等待原订单到期或联系平台核查。成功订单也可能保持占用至原到期时间。不要循环随机下单。 ## 1. 下载后先运行离线闭环 离线测试不联网,不需要公网回调或测试币,使用独立临时 SQLite 数据库,结束自动删除。`fixtures.json` 提供签好名的通知、独立离线 Key 和预期数据库结果;均为虚构订单/合约/地址,不能发送到平台。 ```bash cd merchant-examples/crypto-v2 # Python 3.10+,标准库,无 pip 依赖 python3 python/app.py selftest # PHP 8.1+,扩展 curl、pdo_sqlite(Ubuntu 可安装 php8.3-sqlite3) php php/app.php selftest # .NET 8+;首次自动还原 Microsoft.Data.Sqlite NuGet 依赖 dotnet run --project csharp -- selftest ``` 三语言运行相同的 28 组数据:验签、两种订单号/商户/网络/资产/金额核对、重复通知、低版本乱序、同版本不同事件、同 ID 篡改、confirmed 后状态回退、过期后人工补单、18 位精度,以及人工测试回调与正常付款流隔离。每个拒绝案例都检查数据库没有变化。 ## 2. 启动自己的回调服务 三个实现任选一个,后续下单与接收通知使用同一语言、同一配置和同一 `PAY_STATE_DIR`。先保持接收器运行,再在另一终端运行下单命令。 ```bash # Python python3 python/app.py serve 8081 # http://127.0.0.1:8081/notify # PHP(仅用于开发) php -S 127.0.0.1:8082 -t php # http://127.0.0.1:8082/notify.php # C# dotnet run --project csharp -- serve 8083 # http://127.0.0.1:8083/notify ``` 将自己的公网 HTTPS 通知地址反代到上面的回环服务。在 `config.json` 填写 `PAY_NOTIFY_URL` 后重启接收器。PHP 可以将公网 `/notify` 转发到本地 `/notify.php`。示例不会替你注册公网域名或提供 TLS。 默认 `https://YOUR-MERCHANT-HOST/notify` 是明确占位值:未替换时在线下单会提示配置错误。它不妨碍 `selftest` 或 `balance`。原普通示例的 `https://payment.271458.xyz/?c=Pay&a=notify_url` **只支持 v1**,仅核对固定 v1 签名字段并检查 status=success,不提供业务数据库事务、去重或开发者通知记录页面,不能用于 v2 闭环验收。 ## 3. 下单 → 查询 → 接收通知 ```bash # Python python3 python/app.py balance python3 python/app.py demo python3 python/app.py query 打印出的商户单号 python3 python/app.py inspect # PHP php php/app.php balance php php/app.php demo php php/app.php query 打印出的商户单号 php php/app.php inspect # C# dotnet run --project csharp -- balance dotnet run --project csharp -- demo dotnet run --project csharp -- query 打印出的商户单号 dotnet run --project csharp -- inspect ``` `demo` 先持久化随机商户单号,再发送下单请求,保存响应中的订单身份、网络、资产、地址和数量快照,最后查询。它不会自动付款。也支持 `create 自己的唯一单号`。 若网络超时或响应丢失,先 `query 原商户单号`,查询会从唯一子单恢复本地快照;不要改号重下。尚未保存快照的通知暂不接受,平台后续重试或修复后重新投递。多子单业务不在本示例范围,发现多条子单时拒绝自动绑定。 按本次响应中的网络、合约、地址、amount 支付测试币;不要对照示例 JSON 的旧地址转账。通道必须开通、对应环境的 USDT 服务费余额充足,钱包必须已启用且监听就绪。确认中不能发货;最终确认耗时按链而异。Sepolia finalized 可能需十几分钟。不能用“下单 code=1”代替付款成功。 `inspect` 读取本机 SQLite 的三张表: - `events`:已通过验签及订单核对并提交事务的 event_id、event_seq、正文摘要。没有原始 Key。 - `states`:每单、每条通知流的最大版本和状态;确认中的状态为 confirming,最终为 confirmed。 - `outbox`:confirmed 在同一事务内产生的业务待办。同一订单/流最多一条;本示例不实际发货或记账。 重复通知返回 HTTP 200 纯文本 `success`,事件/待办数量不增加;低版本合法事件留存但不覆盖新状态。数据库锁等待失败等临时问题返回 503,要求平台重试;验签或核对失败返回 400。**收到 `success` 且 events 中存在该 event_id 才证明这个接收器已验签并持久化**。平台“通知状态/回复内容”证明平台收到的 HTTP 应答,本地事务记录才是商户业务处理依据。 后台“测试回调”签名字段 `reason=test_callback` 使用独立 `test` 流;它不会修改示例的 `payment` 状态。test outbox 仅供测试观察,禁止真实发货。正常测试网链上通知属于 payment 流,但订单仍有 `is_testnet=1`,商户业务须使用测试账务。不要根据未签名 HTTP 测试头判断付款性质。 ## 4. 数量与精度,禁止用浮点数 | 当前测试通道 | decimals | money(请求/订单) | amount(下单展示) | expected_units | | --- | --- | --- | --- | --- | | 2911 Ethereum Sepolia | 6 | "1.00" | "1.000000" | "1000000" | | 2912 TRON Nile | 6 | "1.00" | "1.000000" | "1000000" | | 2913 BSC Testnet | 18 | "1.00" | "1.000000000000000000" | "1000000000000000000" | - `money` 是原订单 USDT/TEST-USDT 数量,现行下单协议最多两位小数;不是人民币金额。 - `crypto.amount = crypto.expected_units / 10^crypto.decimals`,展示用十进制字符串。`expected_units` 是精确最小单位整数字符串。 - 查询返回 `crypto.original_amount` 和 `expected_units`,没有下单响应的 amount 字段;可用整数/字符串算法自行显示。 - v2 通知的 `expected_amount_raw` 对应 expected_units;`amount_raw` 是已核验实际转账数量,未检测到付款时为空字符串。 - decimals 必须从该订单响应保存,不按链名写死成 6;不同代币/网络配置可能不同。C#、JavaScript 等也不要把 18 位数量放入浮点数。 - `crypto.fee` 是独立服务费快照:新订单使用 USDT/TEST-USDT,旧单可能仍为 CNY,按 currency 识别;从对应服务费账户扣除,不从应付 TEST-USDT 中减去。 - 此示例只接受精确数量相等。人工补单接受差额时,应增加商户人工审核策略,不能直接放宽金额判断。 完整真实响应在 `responses/2911-create.json` / `2911-query.json`,2912、2913 同理。2026-10-19 通过既有测试订单**相同请求幂等重放与签名查询**取得,没有新增订单。示例订单已取消,故 payment_status=cancelled、fee.state=released;code=1 表示接口处理成功,不能把它当成订单付款成功。仅收银台 token 已替换为占位值,其余保留实际结构和值。新的待付订单通常为 awaiting/held,实际以响应为准。 ## 5. 数据库事务与正式业务接入 `schema.sql` 为三语言共同结构。接收时先验签,再 `BEGIN IMMEDIATE` 锁住 SQLite 写事务,核对下单快照,检查 event_id 与 `(sh_order,stream,seq)` 唯一约束,保存事件、推进状态、写入唯一业务 outbox,然后 COMMIT,最后应答 success。重试不要求 time 在 300 秒内,因为平台重试保留事件原时间。 外部发货/发送消息不能与数据库事务原子提交;应由 worker 消费 outbox,并以订单号+stream 去重,完成后记录处理结果。不要在 COMMIT 前返回 success,也不要在回调每次重试时直接发货。生产多实例请改为共享数据库,并使用对应行锁/唯一约束;不得让每个实例各自保存一份 SQLite。C# 事务背景见 [Microsoft.Data.Sqlite 官方文档](https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/transactions)。 `config.json` 与 `PAY_STATE_DIR` 必须放在 Web 根目录之外。默认状态目录为 `crypto-v2/.state/{python|php|csharp}`;可用环境变量覆盖 JSON 中同名配置及 PAY_STATE_DIR。推荐 `umask 077`。不支持把自己正式 Key 写入公开下载包。RSA 商户需先解密后验签,本包针对普通 MD5 表单商户。 调通后换自己的账号/Key、主网钱包和对应 ptype(2901/2902/2903),使用新订单号。旧订单快照不随配置变动;请求和接收进程必须使用匹配的商户配置。正式币与测试币不能混用。 ## 错误排查 | 提示 | 处理 | | --- | --- | | 收款地址暂被占用 | 查询已经创建的原订单;无自己订单时等待共享地址释放或联系平台。换金额/单号无效。 | | 商户未配置已启用的收款钱包 | 在后台配置该网络的钱包。 | | 钱包监听尚未就绪 | 等订阅 active、start_block 非空、网络就绪。 | | USDT 服务费余额不足 | 在服务费账户充值,测试网使用独立 TEST-USDT 余额;统一阶梯,无需配置数字货币费率。 | | 本地订单已存在 / 网络超时 | 使用 query 原商户单号,禁止无条件重新下单。 | | 回调 400 | 检查原始字符串签名、v2 全字段、订单快照和金额;不要接到旧 v1 接收器。 | | 回调 503 | 检查状态目录权限、SQLite 扩展、数据库锁;修复后由平台重试。 | | 下单/查询正常但 events 为空 | 核对 notify_url 公网可达、TLS、路由、接收器运行状态;下单本身不产生付款确认通知。 | 后台试用:https://payment.271458.xyz/ht.php ,账号 `demo`、密码 `demo`,填写图形验证码即可登录查看测试商户交易记录。API 使用 `mch_id=demo` 和原 `PAY_KEY`;登录密码不是签名密钥。内部 ID 1001 及原订单不变。