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 的小写十六进制结果

签名过程:

  1. 从最终待传参数中移除 signature 和 sign。
  2. 按字段名升序排序,与 PHP ksort 一致;字段名采用接口定义的 ASCII 字段名。
  3. 跳过值为 ""、null、数组的字段;数值 0 和字符串 "0" 保留。
  4. 把其余原始标量值组成 key=value,使用 & 拼接。不 URL 编码、不额外转义、不修剪字段值。
  5. 以 AppSecret 为密钥做 HMAC-SHA256,输出小写 hex。随后才进行 HTTP 表单编码或 JSON 序列化。

例如以下待签名字段:

amount=10.00&app_id=demo_app&currency=HKD&nonce=0123456789abcdef0123456789abcdef&order_id=DEMO_1001&sign_type=HMAC-SHA256&timestamp=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。

处理顺序:

  1. 按 AppId 从服务端查找预配置密钥,拒绝未知应用或错误算法;验签完整原始标量字段。
  2. 如果启用加密,先验签密文信封,再解密;解密失败不入账。
  3. 查询自己的原始订单,核对所属应用、商户订单号、平台交易号、HKD 币种和金额;使用整数分比较,禁止浮点误差比较。
  4. 在数据库事务中锁定订单,支付使用平台交易号加业务事件建立唯一约束;退款以平台退款号建立独立唯一约束。校验状态转换,不允许退款通知再次触发付款发货。
  5. 同一事务写入已处理通知、账单状态和可靠履约任务;提交成功后返回 HTTP 200、纯文本 success。已验证且已处理的重复通知也返回同样结果。
  6. 签名错误、金额不一致、数据库写入失败均不返回 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. 上线验收

附带 VmShellPayClient.php.txt 为 PHP 参考客户端(保存为 .php 后使用),示例仅封装签名、验签和请求,不替代商户自己的账单事务。先运行 sdk-selftest.php.txt 的离线校验,再在独立沙箱应用联调。下载包提供同样的源码和本文,版本应一起保存。