VmShell PAY 下游商户 API 接入手册
版本:2026-09-19。产品:VmShell INC · VmShell PAY。本文依据当前生产接口源码整理,适用于商户服务端、WHMCS、Xboard 和自建商城。所有示例都是虚构数据。接口支持情况以商户已审核的应用、通道权限、费率和风控配置为准。
商务与技术联系:bill@vmshell.com;Telegram。
1. 接入地址和准备工作
正式网关统一使用 https://vmshell.win,后台管理站 https://xiu.vmshell.win 不作为商户支付网关。
| 接口 |
方法 |
正式地址 |
| 创建支付 |
POST |
https://vmshell.win/api/v1/pay.php |
| 查询交易 |
GET / POST |
https://vmshell.win/api/v1/query.php |
| 申请退款 |
POST |
https://vmshell.win/api/v1/refund.php |
| 支付方式目录 |
GET |
https://vmshell.win/api/v1/gateways.php |
接入顺序:注册并完成平台要求的 KYC/KYB → 创建商户应用 → 保存 AppId、AppSecret、服务器出口 IP 白名单和通知地址 → 完成沙箱联调 → 申请并获批正式环境 → 使用 live 应用进行正式交易。
AppSecret 只放在服务器环境变量或受控配置中,不放在网页、APP 安装包、Git 仓库或日志中。不同商户、不同应用分别配置。服务器校时,启用 TLS 1.2 或以上并验证 HTTPS 证书。实际请求出口 IP 应与白名单一致,client_ip 是买家 IP,不能代替服务器白名单。
应用地址配置:notify_url 接收支付异步通知;return_url 是浏览器付款后的返回页面;refund_notify_url 是退款专用异步通知地址;dispute_notify_url 是争议通知地址。通知端点需要公网 HTTPS 可达,不依赖登录 Cookie,不重定向,不设置浏览器验证码。
返回页面、邮件、客户端截图,以及下单返回的 status=success,都不是支付完成的入账依据。商户必须使用验签后的支付通知或主动查单。
2. 请求格式、签名与重放保护
建议 POST 使用 application/x-www-form-urlencoded;下单、查单、退款也接受 JSON 对象。不要在查询串、表单和 JSON 中重复传同名参数。业务金额使用两位小数字符串,如 "10.00"。标识、时间戳、开关也建议使用字符串,避免跨语言数字、布尔值和浮点序列化差异。
| 公共字段 |
要求 |
| app_id |
对应环境的应用 AppId |
| sign_type |
推荐固定 HMAC-SHA256 |
| timestamp |
Unix 秒级时间戳,不是毫秒 |
| nonce |
每次 HTTP 请求生成不同的随机字符串,8–128 字节;推荐 32 位十六进制 |
| signature |
HMAC-SHA256 的小写十六进制结果 |
签名过程:
- 从最终待传参数中移除
signature 和 sign。
- 按字段名升序排序,与 PHP
ksort 一致;字段名采用接口定义的 ASCII 字段名。
- 跳过值为
""、null、数组的字段;数值 0 和字符串 "0" 保留。
- 把其余原始标量值组成
key=value,使用 & 拼接。不 URL 编码、不额外转义、不修剪字段值。
- 以 AppSecret 为密钥做 HMAC-SHA256,输出小写 hex。随后才进行 HTTP 表单编码或 JSON 序列化。
例如以下待签名字段:
amount=10.00&app_id=demo_app¤cy=HKD&nonce=0123456789abcdef0123456789abcdef&order_id=DEMO_1001&sign_type=HMAC-SHA256×tamp=1800000000
该时间戳仅用于离线测试,真实请求必须使用当前时间。签名包含 sign_type、timestamp、nonce 和所有非空标量扩展字段。ext_param 推荐先编码成 JSON 字符串 再参加签名,不要传嵌套数组。不要自行重复 URL 解码。
默认时间窗口为 300 秒,按应用可配置为 60–1800 秒。同一应用、同一接口重复 nonce 会被拒绝。网络重试应使用新时间戳、新 nonce 和新签名,但保留原业务订单号或退款请求号。
兼容算法 MD5 / MD5_KSORT_KEY 为 md5(待签名字符串 + "&key=" + AppSecret),仅供已有旧应用迁移,不推荐新商户使用。接收通知时应按本应用固定算法验签,不允许由外部通知任意降级算法。
3. 创建支付
POST /api/v1/pay.php
| 字段 |
必填 |
说明 |
| 公共签名字段 |
是 |
见第 2 节 |
| order_id |
是 |
商户唯一订单号;每个应用内不可重复 |
| amount |
是 |
大于 0 的 HKD 金额,两位小数;限额由商户通道配置决定 |
| currency |
推荐显式传 |
当前正式接口只支持 HKD,省略默认 HKD |
| customer_email |
是 |
买家有效邮箱 |
| payment_method |
推荐显式传 |
alipay_cn 中国大陆支付宝;alipay_hk 香港支付宝;wechat_pay 微信支付 |
| notify_url |
是或应用预设 |
支付结果通知地址 |
| return_url |
否 |
付款后浏览器返回地址,可继承应用配置 |
| terminal |
否 |
pc / mobile |
| payment_scene |
否 |
qr / wap / web;未传时平台推断 |
| direct |
否 |
"1" 请求直接创建上游订单;默认使用平台收银台 |
| customer_name / customer_phone |
否 |
买家姓名、电话 |
| subject / body |
否 |
商品标题、描述;建议 body 不超过 128 字符 |
| remark |
否 |
商户备注 |
| ext_param |
否 |
商户透传字符串,建议使用 JSON 字符串 |
| client_ip |
否 |
买家实际 IP;不得用于绕过服务器 IP 白名单 |
兼容字段 method 的优先级高于 payment_method,新接入只传 payment_method,避免两者不一致。alipay_cn 和 alipay_hk 必须明确区分。不要用中文显示名称作为接口值。wayCode、paymentInst、channelExtra 属于高级上游参数,普通商户无需传,启用前联系平台确认。
请求示例,签名由服务端计算后填入:
{
"app_id":"YOUR_APP_ID", "order_id":"ORDER_20260919_1001",
"amount":"10.00", "currency":"HKD", "payment_method":"alipay_cn",
"customer_email":"buyer@example.com", "subject":"订阅服务",
"notify_url":"https://merchant.example.com/payment/notify",
"return_url":"https://merchant.example.com/orders/1001",
"terminal":"pc", "payment_scene":"qr",
"sign_type":"HMAC-SHA256", "timestamp":"CURRENT_UNIX_SECONDS",
"nonce":"FRESH_RANDOM_HEX", "signature":"CALCULATED_SIGNATURE"
}
默认成功响应为根对象字段,不是 data 内的字段。下面省略可选字段,签名需按完整实际响应验算:
{
"status":"success", "message":"...",
"pay_url":"https://vmshell.win/...",
"checkout_url":"https://vmshell.win/...",
"transaction_id":"PLATFORM_TRANSACTION_ID", "order_id":"ORDER_20260919_1001",
"amount":"10.00", "currency":"HKD", "payment_method":"alipay_cn",
"expires_at":"2026-09-19 23:00:00",
"sign_type":"HMAC-SHA256", "timestamp":"...", "nonce":"...", "signature":"..."
}
其他返回字段包括 customer_email、terminal、payment_scene、payment_mode 及买家/备注透传字段。默认下单响应不包含 AppId,应使用此次请求已知的应用密钥验签。
打开 pay_url 完成付款。直接模式可能额外返回 payment_url、mobile_h5_url、qr_content、qr_code_url、gateway_order_id、way_code、if_code。依照返回的 payment_mode 和有效字段展示二维码或跳转,不能把全部手机支付都硬编码为同一种跳转。
重复订单通常返回 HTTP 409,可能包含 code=DUPLICATE_ORDER_ID 和已有交易号。不要换订单号盲目再次扣款,应查询原订单。 超时或直接模式上游失败(可能 HTTP 502)时,平台可能已经创建本地交易,应先查单,再决定后续处理。
4. 查询交易
GET /api/v1/query.php 或 POST。同样需要签名、时间戳和随机 nonce。
| 字段 |
说明 |
| app_id |
必填 |
| order_id 或 transaction_id |
二选一;不要同时提供相互矛盾的编号 |
| sync |
可选 "1",请求平台向上游同步状态;正确名称是 sync,不是 sync_upstream |
成功响应结构:
{
"success":true, "message":"查询成功",
"data":{
"app_id":"YOUR_APP_ID", "order_id":"ORDER_20260919_1001",
"transaction_id":"PLATFORM_TRANSACTION_ID", "amount":"10.00", "currency":"HKD",
"status":"paid", "refund_status":"none", "merchant_notify_status":"success",
"sign_type":"HMAC-SHA256", "timestamp":"...", "nonce":"...", "signature":"..."
}
}
查单验签对象是 data,不是外层。 还可能返回 checkout_session_id、买家信息、gateway_transaction_id、gateway_code、payment_method、商品与透传字段、created_at、updated_at、channel、sync_status。
channel 包含 payOrderId、channelOrderNo、state、successTime、channelUser 等诊断信息;sync_status 表示本次上游同步结果,不等于最终支付状态。嵌套对象不参与当前标量签名,不得只依据嵌套对象进行入账。
| 状态 |
商户行为 |
| pending / processing |
尚未确认成功;等待通知或继续查单 |
| paid / success / completed |
支付成功;验签并核对原订单金额、币种、应用后幂等入账 |
| failed / cancelled / expired |
不发货;展示失败或过期,并保留对账能力 |
| refunded |
已退款,不作为新支付再次发货 |
| 其他值 |
保留原值,进入待核查,不猜测成功 |
付款结果未明确时可在商户服务端以 5、10、20、30 秒逐步查询,之后降低频率。这是客户端建议,不是平台频率配额承诺。持续异常记录交易号后联系平台。不要让每个浏览器标签各自高频查询。
5. 申请退款
POST /api/v1/refund.php
| 字段 |
要求 |
| app_id + 签名字段 |
必填 |
| order_id 或 transaction_id |
二选一,必须属于当前应用 |
| refund_amount |
大于 0 的两位小数 HKD 金额,不超过可退余额 |
| refund_id |
强烈建议必填:商户唯一退款请求号;兼容字段为 merchant_refund_id |
| reason |
可选退款原因 |
refund_id 在请求中表示商户退款请求号;在响应中 refund_id 表示平台生成的退款号,merchant_refund_id 才对应商户请求号。务必分别保存。
支持原订单允许范围内的部分退款。不同部分退款使用不同商户退款号;相同退款请求网络重试仍用原商户退款号,新 nonce 与新签名。重复请求可能返回 409,不代表可再次发起一笔新退款。商户余额须覆盖退款金额和平台退款手续费,费率按实际配置计算,不在 SDK 中写死。
响应 success=true,业务字段位于签名的 data:
{
"success":true, "message":"...",
"data":{
"app_id":"YOUR_APP_ID", "order_id":"ORDER_20260919_1001",
"transaction_id":"PLATFORM_TRANSACTION_ID",
"refund_id":"PLATFORM_REFUND_ID", "merchant_refund_id":"MERCHANT_REFUND_1001",
"refund_amount":"4.00", "currency":"HKD", "status":"partial_refund",
"sign_type":"HMAC-SHA256", "timestamp":"...", "nonce":"...", "signature":"..."
}
}
其他字段:payment_method、refund_fee、debit_amount、order_amount、refunded_before、remaining_before、remaining_after、merchant_balance_before、merchant_balance_after、created_at。status 可能为 partial_refund 或 refunded。
退款接口的成功结果表示平台当前处理结果,不保证客户钱包或银行已经显示到账。后续以退款通知和对账结果为准。当前文档没有独立退款查询接口;可查原交易的退款状态、查看平台记录或联系支持。多次部分退款的精确明细请按平台退款号逐笔保存,不能只保存最后一次订单状态。
6. 支付方式目录
GET /api/v1/gateways.php?app_id=YOUR_APP_ID
AppId 可选,建议传以取得对应应用目录。响应格式为 code=0、message、data.environment、data.gateways、data.signature。这里的 data.signature 是签名方式说明文字,不是可验证的数字签名。目录只用于展示能力,不替代正式应用审核、商户权限、金额限制或风控。
7. 异步通知与幂等入账
平台向商户通知地址发送 POST 表单,Content-Type: application/x-www-form-urlencoded。支付通知使用 notify_url;退款通知优先使用配置的 refund_notify_url,缺失时回退到支付通知地址。不要把平台的 /api/heepay_notify.php 或 /api/heepay_refund_notify.php 配成商户接收地址,它们是平台接收上游通知的入口。
常见字段:app_id、order_id、transaction_id、checkout_session_id、gateway_transaction_id、payment_method、amount、currency、status、refund_status、refund_id、refund_amount、merchant_net_amount、买家信息、商品与透传字段、sign_type、timestamp、nonce、signature。
处理顺序:
- 按 AppId 从服务端查找预配置密钥,拒绝未知应用或错误算法;验签完整原始标量字段。
- 如果启用加密,先验签密文信封,再解密;解密失败不入账。
- 查询自己的原始订单,核对所属应用、商户订单号、平台交易号、HKD 币种和金额;使用整数分比较,禁止浮点误差比较。
- 在数据库事务中锁定订单,支付使用平台交易号加业务事件建立唯一约束;退款以平台退款号建立独立唯一约束。校验状态转换,不允许退款通知再次触发付款发货。
- 同一事务写入已处理通知、账单状态和可靠履约任务;提交成功后返回
HTTP 200、纯文本 success。已验证且已处理的重复通知也返回同样结果。
- 签名错误、金额不一致、数据库写入失败均不返回 success,记录脱敏原因并进行人工或自动查单核验。
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
success
不要返回 HTML、登录页、重定向、空白调试输出或带 BOM 的内容。建议快速持久化后异步发货,避免回调超时。WHMCS 专用回调明确要求 2xx 且正文 success;普通商户也统一按 200 + success 实现。
通知可能重复或乱序。某些重试路径复用原通知的时间戳和 nonce,所以不能把“超过 5 分钟”作为已验签合法重试的唯一拒绝依据:已处理重复事件可直接确认;首次收到的过期通知先使用新签名请求主动查单核实,无法核实则不发货。不能因 nonce 不同就重复入账。
平台有通知记录和补发机制,但本文不承诺未经验证的固定重试间隔或次数;商户始终保留主动查单与定期对账兜底。邮件仅是业务提醒,不替代 Webhook。
争议通知地址可在应用中配置,但争议回调报文不属于上述支付/退款字段契约;未单独完成争议事件联调时,应通过平台争议中心处理,不要将它交给付款成功处理器。
8. 可选 AES-256-GCM 加密
仅在应用已开启加密时使用;普通接入使用 HTTPS + HMAC。加密并不替代签名。
派生密钥:SHA256(AppSecret + "|vmshellpay:aes-256-gcm|" + key_version) 的原始 32 字节。算法 AES-256-GCM,随机 IV 12 字节,tag 16 字节,AAD 为空,明文为 UTF-8 JSON。
信封字段:encrypt_type="AES-256-GCM"、key_version="1"(按应用配置)、encrypted_data(密文 base64)、iv(base64)、tag(base64)。把信封连同外层公共字段参加 HMAC。
当前接口在解密前会检查部分业务字段,因此 app_id、订单定位字段、下单 amount 或退款 refund_amount 保留在外层;若也放内层必须一致。notify_url、return_url、refund_notify_url、dispute_notify_url 放外层,内层这些地址会被忽略。
加密通知可能把买家信息、商品信息、备注、透传内容、客户 IP、上游交易号、商户净额和退款信息放入密文。务必先使用未解密的信封验签,再取解密后的字段处理。
9. 沙箱联调
| 方法 |
地址 |
作用 |
| POST |
https://vmshell.win/api/sandbox/test_pay.php |
支付格式模拟 |
| GET / POST |
https://vmshell.win/api/sandbox/query.php |
查单格式模拟 |
| POST |
https://vmshell.win/api/sandbox/webhook.php |
向商户测试通知地址发送模拟通知 |
沙箱可能被平台关闭;正式 live 应用不得用于沙箱支付。使用单独沙箱应用和测试接收地址,不能把模拟成功当成真实收入。查单参数支持 sandbox_order_no 或 order_id,并传 AppId 和签名字段。沙箱查单不是查询真实订单数据库,返回 validation_only=true、database_recorded=false 等标记;模拟 paid 不证明发生过真实扣款。
沙箱下单通常返回 code=0、success=true、外层 status=success 和 data;与正式下单的扁平响应不同,不要直接复用同一解析层级。使用完整的实际数据对象验签。Webhook 测试参数包括 event(例如 payment.completed)、订单号、金额、买家邮箱、notify_url 和签名字段;返回 data.payload 与 data.delivery,用于核对本次测试。该接口会实际访问填写的测试回调地址。
不要调用不明在线“签名工具”上传 AppSecret,优先在商户服务器使用附带 SDK 签名。
10. 错误处理与常见故障
HTTP 状态和 JSON 结构需同时检查,不是每个错误都带签名,不是每个接口都用统一数字 code。
| HTTP |
常见原因 |
处理 |
| 400 |
必填字段、金额、格式错误 |
修正参数后重新签名 |
| 403 |
签名、时间、nonce、白名单、应用状态或环境权限不符 |
检查完整请求和应用配置,不能盲目重试 |
| 404 |
对应应用下找不到交易 |
核对环境、AppId、订单号 |
| 405 |
方法错误,例如退款不是 POST |
改用文档规定方法 |
| 409 |
订单重复或幂等锁命中 |
查询原业务结果,保留原业务编号 |
| 422 |
退款金额、余额、状态或上游业务限制 |
查看 message,核对余额及可退金额 |
| 500 / 502 / 超时 |
服务端或上游异常,结果可能未知 |
先查单和对账,避免重复收费 |
下单成功不回调:检查通知 URL 是否公网可达、证书有效、无跳转、无登录拦截;核对是否在处理表单;日志中不得泄露密钥;核对签名对象与金额。平台通知 HTTP 200 不等于商户账单已经更新,须核对自己的账单和幂等记录。
支付宝通道不匹配:使用 alipay_cn 或 alipay_hk 的准确枚举,不根据中文名称猜测。
签名不一致:最常见原因是先 URL 编码、遗漏非空字段、金额 10 / 10.00 不一致、错误处理 0、对整个外层响应验签、混用 sign/signature、数组序列化差异,或使用了另一环境的密钥。
11. 货币、费用、退款与插件接入
平台当前对外交易和退款使用 HKD。商城原币种为 USD/CNY 等时,由商户自行选择汇率来源,付款创建前锁定汇率并保存原币种金额、汇率时间、换算后的 HKD 分金额。退款按照原交易 HKD 金额和已退余额计算,不按退款当日汇率重新计算平台可退金额。
平台收款费、退款费和商户向终端客户收取的手续费是不同口径。不要使用平台净入账额代替买家的实付总额验证回调。商户自行配置“退款时是否扣除收款手续费/固定退款手续费”时,需要明确展示退款计算,平台只接受最终合法的 HKD 退款金额,并另外按平台配置计费。
WHMCS/Xboard 插件应让每个安装实例分别配置公共网关域名、AppId、AppSecret、签名方式、通知地址、环境、原币种和汇率策略;密钥字段隐藏。前端返回页只展示状态并触发服务器查单。Webhook 必须通过平台原生账单入账函数并防止重复。本文为接口契约,不代表任意第三方插件已经通过所有版本兼容测试。
12. 上线验收
- 普通收银台及启用的直接模式能显示对应 PC / 手机付款页面。
- 中国大陆支付宝、香港支付宝和微信支付分别按已开通权限验证。
- 正确签名通过;错误签名、重复 nonce、过期时间、错误白名单被拒绝。
- 用户中途关闭页面仍能收到服务端通知并入账;浏览器刷新不能重复入账。
- 重复、乱序通知及通知超时后重试不会重复发货。
- 回调金额/币种/应用不一致不会入账。
- 全额/部分退款、重复退款请求、余额不足分别处理;退款不触发支付发货逻辑。
- 沙箱与正式数据隔离;日志脱敏;对账任务、告警和人工处理入口可用。
附带 VmShellPayClient.php.txt 为 PHP 参考客户端(保存为 .php 后使用),示例仅封装签名、验签和请求,不替代商户自己的账单事务。先运行 sdk-selftest.php.txt 的离线校验,再在独立沙箱应用联调。下载包提供同样的源码和本文,版本应一起保存。