+1(469)278-6367 bill@vmshell.com
2026-09-19 完整接入手册

正式接口、签名、回调、退款、沙箱、PHP 示例与下载。下方旧版概览仅作参考,参数以新版完整手册为准。

VmShell Pay 商户接入 API 文档

下游商户统一调用 VmShell Pay,本系统完成应用审核、签名验证、支付通道路由、回调入账、余额同步与商户 Webhook 通知。

API Version v1

一、接入总览

VmShell Pay 采用支付中台模式。商户在前台提交应用并通过后台审核后,可获得 AppId 与 AppSecret。商户系统只需要调用本平台 API,本平台会按已启用通道进入 VmShellPAY 支付网络,并在收到支付或退款结果后更新商户钱包余额、订单状态与争议数据。

1. 创建应用
2. 后台审核
3. 商户签名下单
4. 平台支付处理
5. 平台回调商户
环境网关地址说明
正式支付https://vmshell.win/api/v1/pay.php创建中台交易并进入 VmShellPAY 支付网络。
正式查单https://vmshell.win/api/v1/query.php查询平台订单状态,可同步支付通道状态。
沙箱测试https://vmshell.win/api/sandbox/test_pay.php仅创建测试订单,不触发真实扣款。

正式环境前置条件:商户主体资料通过审核、应用状态为已启用、应用环境为正式、服务器请求 IP 命中应用白名单、回调地址公网可访问。未满足条件时,正式支付接口会拒绝创建订单。

二、支付通道能力

商户侧无需直接保管通道商户号、通道 AppId 或通道签名密钥。相关支付网络配置由平台后台统一托管,商户只需要使用自己的 AppId、AppSecret 与回调地址完成对接。

配置项当前值
可用支付方式支付宝.香港、支付宝.大陆、WeChat Pay
结算币种HKD
签名方式MD5
商户侧密钥以“我的应用”页面展示的 AppSecret 为准
支付网关https://vmshell.win/api/v1/pay.php

如商户需要透传支付扩展字段,可在下单请求中传入 customer_email、customer_name、customer_phone、client_ip、subject、body、remark、ext_param 等字段;平台会保存到交易详情,并在查单和 Webhook 回调中返回给商户。

三、通用请求格式

正式 API 支持 application/x-www-form-urlencoded 的 POST 参数和 GET 查询参数;生产环境建议统一使用 POST。所有金额使用 HKD 主币单位并保留两位小数,例如 10.00。支付宝.香港、支付宝.大陆、微信支付三个正式网关默认单笔范围为 5.00 - 500000.00 HKD,后台可在支付网关中心调整。所有字符串建议使用 UTF-8 编码。

规则要求说明
币种HKD正式环境只支持 HKD。不要传 CNY、USD 或其他币种。
金额格式^[0-9]+(\.[0-9]{1,2})?$金额必须位于当前网关配置范围内,默认 5.00 - 500000.00 HKD,建议固定两位小数,例如 10.00。
订单号商户应用内唯一order_id 同一 AppId 下不可重复,建议只使用字母、数字、下划线、短横线。
邮箱合法 Emailcustomer_email 必填,用于买家账单查询、争议中心和商户回调对账。
时间戳Unix 秒级时间戳timestamp 建议必传,商户可自行做 5 分钟有效期校验。
扩展参数字符串或 JSON 字符串ext_param 不要传数组对象,建议先 JSON 编码成字符串后参与签名。
签名字段名signature 或 sign两者均可;平台计算签名时会忽略这两个字段。
返回格式application/json; charset=utf-8成功和失败均返回 JSON,商户系统必须按 HTTP 状态码和业务字段共同判断。

四、签名认证

每个商户应用拥有独立 AppSecret。新应用默认使用 HMAC-SHA256、timestamp 和 nonce 防重放;旧应用可由后台切换为 MD5 兼容。请求参数按键名升序排序,过滤空值、数组、sign 与 signature 字段后拼接为 key=value 串,然后用 AppSecret 签名。平台会验证签名、时间窗口、nonce、应用审核状态、关停状态、IP 白名单、回调地址与可用通道。

原始参数: app_id=vmp_xxx order_id=ORDER_10001 amount=10.00 currency=HKD customer_email=buyer@example.com subject=Demo Order timestamp=1781160000 排序拼接: amount=10.00&app_id=vmp_xxx&currency=HKD&customer_email=buyer@example.com&order_id=ORDER_10001&subject=Demo Order&timestamp=1781160000&key=YOUR_APP_SECRET 签名结果: signature=hash_hmac('sha256', 排序拼接字符串, YOUR_APP_SECRET)
认证字段是否必填说明
app_id是商户应用 ID,来自“我的应用”。
sign_type是新应用默认 HMAC-SHA256;MD5 仅用于旧商户兼容。
timestamp是Unix 时间戳,默认 300 秒有效期。
nonce是随机字符串,同一 AppId + endpoint + nonce 在有效期内只能使用一次。
encrypted_data可选启用 AES-256-GCM 时,敏感字段放入加密载荷;notify_url 保持明文。
signature / sign是按应用签名方式生成,建议统一使用 signature。

注意:空字符串、NULL、数组不参与签名;JSON 扩展参数必须作为字符串参与签名。签名原文不要 URL 编码后再计算,直接使用业务参数原值拼接。

五、支付下单 API

POST https://vmshell.win/api/v1/pay.php
参数类型/格式必填参与签名说明
app_idstring,1-80是是商户应用 ID。
method / payment_methodenum否是alipay_hk 支付宝.香港,alipay_cn 支付宝.大陆,wechat_pay WeChat Pay。建议显式传入。
order_idstring,1-120是是商户唯一订单号,同一 AppId 下不可重复。
amountdecimal,例 12.50是是订单金额,单位 HKD 主币;默认单笔范围 5.00 - 500000.00 HKD,超出时返回 AMOUNT_OUT_OF_RANGE。
currencyHKD否是默认 HKD,正式环境只支持 HKD。
customer_emailemail,最长 255是是付款用户邮箱,用于账单查询、争议中心和商户回调对账。
customer_namestring,建议 1-80否是付款用户姓名,平台保存并在查单与回调中返回。
customer_phonestring,建议 E.164否是付款用户电话,例如 +852 5000 0000。
subjectstring,建议 1-120建议是商品标题;不传时平台会按订单号生成默认标题。
body / descriptionstring,建议 1-255否是商品描述;不传时默认等于 subject。
remark / note / memostring,建议 1-255否是付款备注或商户内部备注,平台原样保存并返回。
ext_paramJSON 字符串否是商户扩展参数,例如 {"cart_id":"CART_10001"}。请作为字符串传入,不要传数组。
notify_urlhttps/http URL是是支付、退款、争议处理后通知商户的地址。若应用后台已配置,可不传。
return_urlhttps/http URL否是用户支付完成后跳转地址。
client_ip / clientIpIPv4/IPv6否是买家 IP,用于风控和支付路由;不传时平台使用请求来源 IP。
direct1/true/yes否是直接按指定支付方式下单,返回二维码/支付链接,不再让买家进入收银台选择通道。
return_qr / qrcode1否是同 direct=1。
sign_typeHMAC-SHA256建议是新应用默认 HMAC-SHA256;旧应用可由后台切换为 MD5 兼容。
timestampUnix 秒HMAC 必填是请求时间,默认 300 秒有效。
noncestringHMAC 必填是随机字符串,同一 AppId + endpoint + nonce 只能使用一次。
encrypted_data / iv / tagAES-256-GCM按应用配置是敏感字段密文;notify_url 保持明文,不放入密文。
signature / signHMAC/MD5是否请求签名。

普通收银台模式响应

{ "status": "success", "message": "Payment session created. The platform will process the payment and notify merchant callback after payment result is confirmed.", "pay_url": "https://vmshell.win/pages/checkout.php?session_id=vmp_sess_xxx", "checkout_url": "https://vmshell.win/pages/checkout.php?session_id=vmp_sess_xxx", "mobile_h5_url": "https://vmshell.win/pages/mobile_h5_pay.php?session_id=vmp_sess_xxx", "transaction_id": "VMP20260611180000ABCD1234", "order_id": "ORDER_10001", "customer_email": "buyer@example.com", "customer_name": "Test Buyer", "customer_phone": "+852 5000 0000", "subject": "Demo Order", "body": "Demo product description", "remark": "merchant-note-001", "ext_param": "{\"cart_id\":\"CART_10001\"}", "client_ip": "203.0.113.10", "payment_method": "alipay_hk", "amount": "10.00", "currency": "HKD", "expires_at": "2026-06-11 19:00:00", "sign_type": "HMAC-SHA256", "timestamp": "1781185200", "nonce": "b7d4f4b4a2d0e1c6c8a9f001", "signature": "hmac_sha256_signature" }

Direct QR 模式响应

{ "status": "success", "message": "Direct payment QR created. The platform will notify merchant callback after payment result is confirmed.", "checkout_url": "https://vmshell.win/pages/checkout.php?session_id=vmp_sess_xxx", "mobile_h5_url": "https://vmshell.win/pages/mobile_h5_pay.php?session_id=vmp_sess_xxx", "pay_url": "weixin://wxpay/bizpayurl?pr=xxx 或 https://...", "payment_url": "weixin://wxpay/bizpayurl?pr=xxx 或 https://...", "qr_content": "二维码原始内容,商户可自行生成二维码", "qr_code_url": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=...", "transaction_id": "VMP20260611180000ABCD1234", "gateway_order_id": "P2064945733190164482", "order_id": "ORDER_10001", "payment_method": "alipay_hk", "amount": "10.00", "currency": "HKD", "expires_at": "2026-06-11 19:00:00", "sign_type": "HMAC-SHA256", "timestamp": "1781185200", "nonce": "b7d4f4b4a2d0e1c6c8a9f001", "signature": "hmac_sha256_signature" }

六、订单查询 API

GET/POST https://vmshell.win/api/v1/query.php

商户可使用平台交易号或商户订单号查询订单。若请求中带有 sync_upstream=1,平台会同步支付通道状态,并写入查询日志。

参数类型/格式必填参与签名说明
app_idstring是是商户应用 ID。
order_idstring二选一是商户订单号。
transaction_idstring二选一是平台交易号。
sync / sync_upstream0/1否是是否同步支付网络状态。生产环境建议不要高频同步。
sign_typeHMAC-SHA256建议是按应用配置生成签名。
timestampUnix 秒HMAC 必填是请求时间。
noncestringHMAC 必填是防重放随机串。
signature / signHMAC/MD5是否签名。

查单成功后会返回下单时透传的 customer_email、customer_name、customer_phone、subject、body、remark、ext_param、client_ip 等字段,便于商户系统对账和售后。

{ "success": true, "message": "查询成功", "data": { "app_id": "vmp_xxx", "order_id": "ORDER_10001", "transaction_id": "VMP20260611180000ABCD1234", "checkout_session_id": "vmp_sess_xxx", "customer_email": "buyer@example.com", "customer_name": "Test Buyer", "customer_phone": "+852 5000 0000", "gateway_transaction_id": "P2064945733190164482", "gateway_code": "alipay_hk", "payment_method": "alipay_hk", "subject": "Demo Order", "body": "Demo product description", "remark": "merchant-note-001", "ext_param": "{\"cart_id\":\"CART_10001\"}", "client_ip": "203.0.113.10", "amount": "10.00", "currency": "HKD", "status": "paid", "refund_status": "none", "merchant_notify_status": "success", "channel": { "payOrderId": "P2064945733190164482", "channelOrderNo": "", "state": "SUCCESS", "successTime": "2026-06-11 18:05:00", "channelUser": "" }, "signature": "平台返回签名" } }

七、商户 Webhook 回调

支付结果先进入平台统一处理流程。平台完成验签、入账、服务费记录、退款同步或争议裁决后,再调用商户应用配置的 notify_url。回调请求使用 POST 表单格式,商户必须验证签名并做幂等处理。浏览器同步回跳只用于展示,不作为最终入账依据。

字段类型/格式说明
app_idstring商户应用 ID。
order_idstring商户订单号。
transaction_idstring平台交易号。
checkout_session_idstring平台收银台会话号。
gateway_transaction_idstring支付网络订单号,如已返回。
payment_methodenumalipay_hk、alipay_cn、wechat_pay 或平台内部兼容值。
customer_emailemail付款用户邮箱。
customer_namestring付款用户姓名。
customer_phonestring付款用户电话。
subjectstring商品标题。
bodystring商品描述。
remarkstring付款备注或商户内部备注。
ext_paramstring商户扩展参数,按下单请求原样返回。
client_ipstring下单时记录的客户端 IP。
amountdecimal订单金额。
currencyHKD订单币种。
statusenum当前订单或退款状态。
refund_statusenum退款状态,未退款时为空或 none。
refund_idstring退款回调时返回退款单号。
refund_amountdecimal退款回调时返回退款金额。
merchant_net_amountdecimal商户净入账金额。
timestampUnix 秒平台发起回调的时间。
noncestring平台回调随机串,商户侧可用于幂等/防重放。
sign_typestring平台回调签名方式,默认 HMAC-SHA256。
encrypted_data / iv / tagAES-256-GCM应用启用字段加密时,敏感字段会进入加密载荷。
signatureHMAC/MD5平台使用商户 AppSecret 生成的回调签名。

回调验签与响应

商户收到回调后,按应用签名方式用 AppSecret 验证 signature;如存在 encrypted_data,先验签外层参数,再用 AES-256-GCM 解密敏感字段。验签成功并完成订单处理后,建议返回 HTTP 200,响应内容可为 success 或 JSON。商户必须以 order_id 或 transaction_id 做幂等,重复回调不能重复发货或重复入账。

{ "app_id": "vmp_xxx", "order_id": "ORDER_10001", "transaction_id": "VMP20260611180000ABCD1234", "gateway_transaction_id": "P2064945733190164482", "payment_method": "alipay_hk", "customer_email": "buyer@example.com", "subject": "Demo Order", "remark": "merchant-note-001", "ext_param": "{\"cart_id\":\"CART_10001\"}", "amount": "10.00", "currency": "HKD", "status": "paid", "merchant_net_amount": "9.50", "timestamp": "1781160600", "signature": "平台回调签名" }

八、状态枚举

类型状态值说明
订单pending订单已创建,等待买家支付。
订单processing已进入支付网络处理,尚未确认最终结果。
订单paid / success / completed支付成功,可以发货或开通服务。
订单failed支付失败或支付网络未返回有效支付资料。
订单refunded订单已全额退款。
退款none未发生退款。
退款partial_refund订单已部分退款。
退款refunded订单已全额退款。
商户回调pending尚未通知或等待重试。
商户回调success商户回调地址返回 HTTP 2xx。
商户回调failed商户回调地址不可达或返回非 2xx。

九、多语言示例代码

PHP

<?php $params = [ 'app_id' => 'YOUR_APP_ID', 'order_id' => 'ORDER_' . time(), 'amount' => '10.00', 'currency' => 'HKD', 'customer_email' => 'buyer@example.com', 'customer_name' => 'Test Buyer', 'customer_phone' => '+852 5000 0000', 'subject' => 'Demo Order', 'body' => 'Demo product description', 'remark' => 'merchant-note-001', 'ext_param' => '{"cart_id":"CART_10001"}', 'notify_url' => 'https://merchant.example.com/pay/notify', 'sign_type' => 'HMAC-SHA256', 'timestamp' => time(), 'nonce' => bin2hex(random_bytes(12)) ]; ksort($params); $pairs = []; foreach ($params as $k => $v) { if ($v !== '' && $v !== null) $pairs[] = $k . '=' . $v; } $params['signature'] = strtolower(hash_hmac('sha256', implode('&', $pairs), 'YOUR_APP_SECRET')); $ch = curl_init('https://vmshell.win/api/v1/pay.php'); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_RETURNTRANSFER => true]); echo curl_exec($ch);

Node.js

const crypto = require('crypto'); const axios = require('axios'); const params = { app_id:'YOUR_APP_ID', method:'alipay_hk', order_id:'ORDER_' + Date.now(), amount:'10.00', currency:'HKD', customer_email:'buyer@example.com', subject:'Demo Order', body:'Demo product description', remark:'merchant-note-001', ext_param:'{"cart_id":"CART_10001"}', notify_url:'https://merchant.example.com/pay/notify', sign_type:'HMAC-SHA256', timestamp:Math.floor(Date.now()/1000), nonce:crypto.randomBytes(12).toString('hex') }; const base = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('&'); params.signature = crypto.createHmac('sha256', 'YOUR_APP_SECRET').update(base).digest('hex').toLowerCase(); axios.post('https://vmshell.win/api/v1/pay.php', params).then(r => console.log(r.data));

Python

import hashlib, hmac, secrets, time, requests params = {'app_id':'YOUR_APP_ID','method':'alipay_hk','order_id':f'ORDER_{int(time.time())}','amount':'10.00','currency':'HKD','customer_email':'buyer@example.com','subject':'Demo Order','body':'Demo product description','remark':'merchant-note-001','ext_param':'{"cart_id":"CART_10001"}','notify_url':'https://merchant.example.com/pay/notify','sign_type':'HMAC-SHA256','timestamp':str(int(time.time())),'nonce':secrets.token_hex(12)} base = '&'.join([f'{k}={params[k]}' for k in sorted(params.keys()) if params[k]]) params['signature'] = hmac.new(b'YOUR_APP_SECRET', base.encode(), hashlib.sha256).hexdigest().lower() print(requests.post('https://vmshell.win/api/v1/pay.php', data=params).json())

Java

Map<String, String> p = new TreeMap<>(); p.put("app_id", "YOUR_APP_ID"); p.put("method", "alipay_hk"); p.put("order_id", "ORDER_10001"); p.put("amount", "10.00"); p.put("currency", "HKD"); p.put("customer_email", "buyer@example.com"); p.put("subject", "Demo Order"); p.put("sign_type", "HMAC-SHA256"); p.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); p.put("nonce", UUID.randomUUID().toString().replace("-", "")); String base = p.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); String signature = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, "YOUR_APP_SECRET").hmacHex(base).toLowerCase(); // 使用 HttpClient POST 至 https://vmshell.win/api/v1/pay.php

Go

params := map[string]string{"app_id":"YOUR_APP_ID","method":"alipay_hk","order_id":"ORDER_10001","amount":"10.00","currency":"HKD","customer_email":"buyer@example.com","subject":"Demo Order","sign_type":"HMAC-SHA256","timestamp":strconv.FormatInt(time.Now().Unix(),10),"nonce":randomHex(12)} keys := make([]string, 0, len(params)); for k := range params { keys = append(keys, k) }; sort.Strings(keys) parts := []string{}; for _, k := range keys { parts = append(parts, k+"="+params[k]) } h := hmac.New(sha256.New, []byte("YOUR_APP_SECRET")); h.Write([]byte(strings.Join(parts, "&"))) params["signature"] = hex.EncodeToString(h.Sum(nil)) // http.PostForm("https://vmshell.win/api/v1/pay.php", url.Values{...})

十、下游商户接入教程

本章节按真实上线顺序说明商户系统如何接入 VmShell PAY。商户只需要维护自己的 AppId、AppSecret、服务器出口 IP、notify_url 和 return_url;支付网络路由、手续费计算、钱包入账、退款扣款和回调重试由平台统一处理。

1. 在商户后台创建应用

步骤操作说明
提交主体资料进入“商户入驻资料”页面主体资料审核通过后,才能创建应用或申请正式环境。
提交应用申请进入“我的应用”页面填写应用名称、商户网站、服务器 IP 白名单、联系人邮箱、notify_url、return_url。
完成沙箱联调进入“沙箱联调”页面使用真实 AppId 和 AppSecret 创建沙箱订单、查询订单、测试 Webhook。
申请正式环境沙箱通过后提交正式申请后台审核通过后,应用才能调用正式 /api/v1/pay.php。
复制密钥在“我的应用”查看 AppId/AppSecretAppSecret 只保存在商户服务器端,不要写入浏览器、前端 JS、App 客户端。

2. 准备商户服务器文件

下面示例假设商户网站路径为 https://merchant.example.com/pay/,商户需要准备四个 PHP 文件:

文件用途公网地址示例
create_order.php商户服务端创建订单并调用 VmShell PAY 下单接口https://merchant.example.com/pay/create_order.php
notify.php接收 VmShell PAY 支付/退款异步通知https://merchant.example.com/pay/notify.php
return.php买家支付后浏览器跳回页面https://merchant.example.com/pay/return.php
query.php商户后台或定时任务主动查单https://merchant.example.com/pay/query.php

重要:return.php 只是浏览器跳转,不代表支付成功;发货、开通服务、充值到账必须以 notify.php 验签成功或 query.php 查单成功为准。手机端如收到 mobile_h5_url,应优先打开它,不要直接把 checkout_url 当作支付宝/微信 App 唤起地址。

3. 商户配置文件 config.php

<?php // config.php return [ 'api_base' => 'https://vmshell.win', 'app_id' => 'YOUR_APP_ID', 'app_secret' => 'YOUR_APP_SECRET', 'notify_url' => 'https://merchant.example.com/pay/notify.php', 'return_url' => 'https://merchant.example.com/pay/return.php', ];

4. 公共签名函数 common.php

<?php // common.php function vmshell_sign(array $params, string $secret): string { unset($params['sign'], $params['signature']); ksort($params); $pairs = []; foreach ($params as $key => $value) { if ($value === '' || $value === null || is_array($value)) continue; $pairs[] = $key . '=' . $value; } $canonical = implode('&', $pairs); $signType = strtoupper($params['sign_type'] ?? 'HMAC-SHA256'); return $signType === 'MD5' ? strtolower(md5($canonical . '&key=' . $secret)) : strtolower(hash_hmac('sha256', $canonical, $secret)); } function vmshell_post(string $url, array $params): array { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $raw = curl_exec($ch); $error = curl_error($ch); $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ['http_code' => $http, 'error' => $error, 'raw' => $raw, 'json' => json_decode((string)$raw, true)]; }

5. 创建支付订单 create_order.php

<?php require __DIR__ . '/common.php'; $cfg = require __DIR__ . '/config.php'; // 实际项目中请从数据库读取订单,并确保 order_id 唯一。 $orderId = 'ORDER_' . date('YmdHis'); $amount = '10.00'; $params = [ 'app_id' => $cfg['app_id'], 'method' => 'alipay_hk', // alipay_hk / alipay_cn / wechat_pay 'order_id' => $orderId, 'amount' => $amount, 'currency' => 'HKD', 'customer_email' => 'buyer@example.com', 'customer_name' => 'Test Buyer', 'customer_phone' => '+852 5000 0000', 'subject' => 'Demo Product', 'body' => 'Demo product description', 'remark' => 'merchant internal note', 'ext_param' => json_encode(['cart_id' => 'CART_10001'], JSON_UNESCAPED_UNICODE), 'notify_url' => $cfg['notify_url'], 'return_url' => $cfg['return_url'], 'sign_type' => 'HMAC-SHA256', 'timestamp' => time(), 'nonce' => bin2hex(random_bytes(12)), ]; $params['signature'] = vmshell_sign($params, $cfg['app_secret']); $result = vmshell_post($cfg['api_base'] . '/api/v1/pay.php', $params); if (($result['json']['status'] ?? '') === 'success') { // 普通收银台模式跳转 pay_url;direct=1 时可读取 qr_content/qr_code_url 自行展示二维码。 header('Location: ' . $result['json']['pay_url']); exit; } header('Content-Type: application/json; charset=utf-8'); echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

6. 直接返回二维码模式

如果商户希望调用接口后直接得到二维码或支付链接,可在下单参数中加入 direct=1,并指定 method。商户页面可使用返回的 qr_code_url 图片,或用 qr_content 自行生成二维码。

$params['method'] = 'alipay_cn'; $params['direct'] = '1'; $params['timestamp'] = time(); $params['nonce'] = bin2hex(random_bytes(12)); $params['signature'] = vmshell_sign($params, $cfg['app_secret']); $result = vmshell_post($cfg['api_base'] . '/api/v1/pay.php', $params); if (($result['json']['status'] ?? '') === 'success') { echo '<img src="' . htmlspecialchars($result['json']['qr_code_url'] ?? '') . '" alt="Pay QR">'; echo '<p>请使用对应钱包扫码支付</p>'; }

7. 接收异步通知 notify.php

<?php require __DIR__ . '/common.php'; $cfg = require __DIR__ . '/config.php'; $payload = $_POST ?: $_GET; $receivedSign = $payload['signature'] ?? ($payload['sign'] ?? ''); $calculated = vmshell_sign($payload, $cfg['app_secret']); if (!$receivedSign || !hash_equals($calculated, strtolower($receivedSign))) { http_response_code(403); echo 'invalid signature'; exit; } $orderId = $payload['order_id'] ?? ''; $transactionId = $payload['transaction_id'] ?? ''; $status = $payload['status'] ?? ''; $amount = $payload['amount'] ?? '0.00'; $currency = $payload['currency'] ?? 'HKD'; $refundId = $payload['refund_id'] ?? ''; $refundAmount = $payload['refund_amount'] ?? ''; // 1. 从数据库查询本地订单。 // 2. 校验 order_id、amount、currency 是否和本地订单一致。 // 3. 以 order_id 或 transaction_id 做幂等,重复通知直接返回 success。 // 4. paid/success/completed 才能发货或开通服务。 // 5. partial_refund/refunded 需要更新退款状态和售后记录。 if (in_array($status, ['paid', 'success', 'completed'], true)) { // markOrderPaid($orderId, $transactionId, $payload); } elseif (in_array($status, ['partial_refund', 'refunded'], true)) { // markOrderRefunded($orderId, $refundId, $refundAmount, $payload); } echo 'success';

8. 浏览器跳转 return.php

<?php // return.php // 这里仅展示支付结果页。不要在这里发货,必须等待 notify.php 或 query.php 确认。 $orderId = $_GET['order_id'] ?? ''; ?> <h1>支付处理中</h1> <p>订单号:<?php echo htmlspecialchars($orderId); ?></p> <p>请稍候,系统会在支付确认后自动更新订单状态。</p>

9. 主动查单 query.php

<?php require __DIR__ . '/common.php'; $cfg = require __DIR__ . '/config.php'; $params = [ 'app_id' => $cfg['app_id'], 'order_id' => $_GET['order_id'] ?? 'ORDER_10001', 'sign_type' => 'HMAC-SHA256', 'timestamp' => time(), 'nonce' => bin2hex(random_bytes(12)), ]; $params['signature'] = vmshell_sign($params, $cfg['app_secret']); $result = vmshell_post($cfg['api_base'] . '/api/v1/query.php', $params); header('Content-Type: application/json; charset=utf-8'); echo json_encode($result['json'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

10. 上线前必测清单

测试项验收标准
签名测试错误密钥会被拒绝,正确密钥可创建订单。
IP 白名单商户服务器出口 IP 不在白名单时,正式接口返回 403。
支付成功商户收到 notify_url,验签成功,订单只处理一次。
浏览器跳转return_url 只展示结果,不做发货依据。
主动查单商户可用 order_id 查询到订单和付款邮箱。
退款通知退款后商户收到 partial_refund 或 refunded,并记录退款单号。
金额核对商户后台订单金额、平台服务手续费、商户净入账、退款手续费和钱包流水一致。
重复通知同一 order_id 重复回调不会重复发货、重复充值或重复退款。

十一、错误码与排查

HTTP/字段常见信息说明建议处理
200status=success 或 success=true请求成功。读取返回字段并继续业务处理。
400AMOUNT_OUT_OF_RANGE / Missing required parameters缺少必要参数、金额格式错误或金额不在网关限额内。检查 app_id、order_id、amount、currency、customer_email、notify_url 与 signature;默认限额为 5.00 - 500000.00 HKD。
400Only HKD is supported正式环境只允许 HKD。把订单币种固定为 HKD。
403Signature verification failed签名验证失败。确认参数排序、空值过滤、JSON 字符串、AppSecret 与 sign/signature 字段处理。
403Unauthorized IP address请求 IP 不在应用白名单内。到应用资料中填写服务器出口 IP,或联系平台后台审核更新。
403Application is still in sandbox environment应用仍为沙箱或未正式启用。完成沙箱测试并提交正式环境审批。
400Duplicate order_id订单号重复。每次新订单使用唯一 order_id;重试时先查单,不要直接换金额复用。
502Payment channel order failedDirect QR 请求已创建平台订单,但支付网络未返回二维码或支付链接。查看返回的 diagnosis 与 upstream.response,必要时换支付方式或联系平台技术。
404订单不存在查单没有找到订单。确认 app_id、order_id/transaction_id 是否属于同一个应用。

上线检查清单

检查项要求
应用状态已审核通过并切换为正式环境。
IP 白名单填写商户服务器真实出口 IP。浏览器用户 IP 不等于服务器出口 IP。
回调地址notify_url 必须公网可访问,支持 POST,处理成功返回 HTTP 2xx。
幂等处理商户系统以 order_id 或 transaction_id 防重复处理。
发货依据只以平台回调验签成功且 status=paid/success/completed,或查单返回成功状态为准。
退款同步退款可在商户交易详情页发起,平台会同步支付网络并回调商户。