+1(469)278-6367 bill@vmshell.com

VmShell Pay 商户接入 API 文档

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

API Version v1

一、接入总览

VmShell Pay 采用支付中台模式。商户在前台提交应用并通过后台审核后,可获得 AppIdAppSecret。商户系统只需要调用本平台 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_emailcustomer_namecustomer_phoneclient_ipsubjectbodyremarkext_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 编码成字符串后参与签名。
签名字段名signaturesign两者均可;平台计算签名时会忽略这两个字段。
返回格式application/json; charset=utf-8成功和失败均返回 JSON,商户系统必须按 HTTP 状态码和业务字段共同判断。

四、签名认证

每个商户应用拥有独立 AppSecret。新应用默认使用 HMAC-SHA256timestampnonce 防重放;旧应用可由后台切换为 MD5 兼容。请求参数按键名升序排序,过滤空值、数组、signsignature 字段后拼接为 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 仅用于旧商户兼容。
timestampUnix 时间戳,默认 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_methodenumalipay_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 / qrcode1direct=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_emailcustomer_namecustomer_phonesubjectbodyremarkext_paramclient_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_hkalipay_cnwechat_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_idtransaction_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。商户只需要维护自己的 AppIdAppSecret、服务器出口 IP、notify_urlreturn_url;支付网络路由、手续费计算、钱包入账、退款扣款和回调重试由平台统一处理。

1. 在商户后台创建应用

步骤操作说明
提交主体资料进入“商户入驻资料”页面主体资料审核通过后,才能创建应用或申请正式环境。
提交应用申请进入“我的应用”页面填写应用名称、商户网站、服务器 IP 白名单、联系人邮箱、notify_urlreturn_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_refundrefunded,并记录退款单号。
金额核对商户后台订单金额、平台服务手续费、商户净入账、退款手续费和钱包流水一致。
重复通知同一 order_id 重复回调不会重复发货、重复充值或重复退款。

十一、错误码与排查

HTTP/字段常见信息说明建议处理
200status=successsuccess=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_idtransaction_id 防重复处理。
发货依据只以平台回调验签成功且 status=paid/success/completed,或查单返回成功状态为准。
退款同步退款可在商户交易详情页发起,平台会同步支付网络并回调商户。