按商户订单号查单并验证响应
用自己的商户订单号取得支付状态,完整演示 RSA 请求签名与响应验签。
← 返回平台功能接入准备
- 已完成中国大陆普通商户入驻、签约与对应支付产品配置,且查询自己的已有订单或退款单。
- 管理员在商户平台 账户中心 → API 安全 申请商户 API 证书并保管私钥,记录对应证书序列号。
- 本例使用微信支付公钥模式:在同一 API 安全页面申请并下载公钥,记录 PUB_KEY_ID;平台证书模式不能直接套用本例。
- 安装 Node.js 22,把密钥与公钥材料放在服务端受保护目录;不要上传到本网站或浏览器。
准备环境变量
示例值需要替换为自己的凭证或资源 ID。代码在本机或服务端运行;密钥不应放进公开网页。
WX_MCH_ID- 普通商户号,在商户平台获取;不是 AppID。
WX_MCH_SERIAL- 商户 API 证书序列号,对应下方私钥;不是微信支付公钥 ID。
WX_PRIVATE_KEY_PATH- 自己服务器上商户 API 证书私钥 apiclient_key.pem 路径。
WX_PAY_PUBLIC_KEY_ID- API 安全页申请下载的微信支付公钥 ID,必须匹配当前验签公钥。
WX_PAY_PUBLIC_KEY_PATH- 同一 API 安全页下载的微信支付公钥文件路径,事先可信配置。
WX_OUT_TRADE_NO- 自己下单时保存的 out_trade_no,不是微信 transaction_id。
export WX_MCH_ID='1900000001'
export WX_MCH_SERIAL='REPLACE_WITH_MERCHANT_CERT_SERIAL'
export WX_PRIVATE_KEY_PATH='/secure/apiclient_key.pem'
export WX_PAY_PUBLIC_KEY_ID='PUB_KEY_ID_replace_me'
export WX_PAY_PUBLIC_KEY_PATH='/secure/wechatpay_public_key.pem'
export WX_OUT_TRADE_NO='DEMO_ORDER_20261015'以上 export 适用于 macOS / Linux 的终端,在同一终端内运行后续代码。Python 示例保存为 request.py 后执行 python3 request.py。
调用步骤
- 01
准备自己的商户号、商户 API 证书序列号及对应私钥,设置 WX_MCH_ID、WX_MCH_SERIAL、WX_PRIVATE_KEY_PATH。
- 02
在商户平台下载可信微信支付公钥,设置 WX_PAY_PUBLIC_KEY_ID 和 WX_PAY_PUBLIC_KEY_PATH;这两个参数负责验签,不参与商户请求签名。
- 03
设置该场景的商户订单号或退款单号;必须是自己业务后台创建时保存的值,不能用页面里的示意值作为真实资源。
- 04
把完整 JavaScript 保存为 request.mjs,使用 node request.mjs 运行。代码会生成当次 RSA 请求签名、验证原始响应再解析 JSON。
- 05
只有已验签的业务状态才能更新自己的订单;查询程序本身不会发起下单、退款、关单或付款。
查询订单
普通商户路径不带 /partner;请求不需要 body,mchid 在 query 中并参与签名。
https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${WX_OUT_TRADE_NO}?mchid=${WX_MCH_ID}请求头
Acceptapplication/json
参数说明
参数表可左右滑动,查看完整说明。
| 字段 / 位置 | 类型 / 必填 | 说明 |
|---|---|---|
out_trade_noPath | string必填 | 原支付下单时的商户订单号,最长 32 字符;本例限定为 URL 安全的常用合法字符。 |
mchidQuery | string必填 | 该原订单所属的普通商户号。 |
AuthorizationHeader | string必填 | WECHATPAY2-SHA256-RSA2048 认证串;由完整代码按当次时间戳、随机串及实际 URL 计算。 |
Wechatpay-SerialHeader | string可选 | 本例指定已配置的微信支付公钥 ID,并核对响应使用同一 ID。 |
调用代码
替换示例 ID 与请求体内容后运行。
import { readFileSync } from 'node:fs';
import { constants, randomBytes, sign, verify } from 'node:crypto';
const required = (name) => {
const value = process.env[name];
if (!value) throw new Error(`缺少环境变量 ${name}`);
return value;
};
const mchid = required('WX_MCH_ID');
const mchSerial = required('WX_MCH_SERIAL');
const payPublicKeyId = required('WX_PAY_PUBLIC_KEY_ID');
if (!/^\d+$/.test(mchid)) throw new Error('商户号应为数字字符串');
if (!/^[A-Fa-f0-9]+$/.test(mchSerial)) throw new Error('请填写真实商户 API 证书序列号');
if (!/^PUB_KEY_ID_\d+$/.test(payPublicKeyId)) throw new Error('本例仅支持已配置的微信支付公钥模式');
const privateKey = readFileSync(required('WX_PRIVATE_KEY_PATH'));
const payPublicKey = readFileSync(required('WX_PAY_PUBLIC_KEY_PATH'));
const outTradeNo = required('WX_OUT_TRADE_NO');
if (!/^[A-Za-z0-9_\-*|@]{6,32}$/.test(outTradeNo)) throw new Error('请填写自己的合法商户订单号');
const url = new URL('https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' + encodeURIComponent(outTradeNo));
url.searchParams.set('mchid', mchid);
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomBytes(16).toString('hex');
// 五行都以换行结束;GET 的最后一行是空 body,仍保留末尾换行。
const message = `GET\n${url.pathname}${url.search}\n${timestamp}\n${nonce}\n\n`;
const signature = sign('RSA-SHA256', Buffer.from(message), {
key: privateKey, padding: constants.RSA_PKCS1_PADDING,
}).toString('base64');
const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonce}",timestamp="${timestamp}",serial_no="${mchSerial}",signature="${signature}"`;
const response = await fetch(url, {
method: 'GET', redirect: 'error', signal: AbortSignal.timeout(20000),
headers: { Authorization: authorization, Accept: 'application/json',
'Wechatpay-Serial': payPublicKeyId, 'User-Agent': 'MerchantReadOnlyExample/1.0' },
});
const raw = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
// 非 2xx 只作为错误提示,不将错误体作为可信业务结果。
throw new Error(`HTTP ${response.status}: ${raw.toString('utf8')}`);
}
const respSerial = response.headers.get('Wechatpay-Serial');
const respTimestamp = response.headers.get('Wechatpay-Timestamp') || '';
const respNonce = response.headers.get('Wechatpay-Nonce');
const respSignature = response.headers.get('Wechatpay-Signature');
if (respSerial !== payPublicKeyId) throw new Error('验签公钥 ID 不匹配,请从可信渠道核对轮换材料');
if (!/^\d+$/.test(respTimestamp) || !respNonce || !respSignature) throw new Error('响应缺少有效签名头');
if (Math.abs(Date.now() / 1000 - Number(respTimestamp)) > 300) throw new Error('响应时间偏差超过 5 分钟');
// 验签使用收到的原始字节;不能 JSON.parse 后重新序列化再验签。
const verificationMessage = Buffer.concat([
Buffer.from(`${respTimestamp}\n${respNonce}\n`), raw, Buffer.from('\n'),
]);
const verified = verify('RSA-SHA256', verificationMessage, {
key: payPublicKey, padding: constants.RSA_PKCS1_PADDING,
}, Buffer.from(respSignature, 'base64'));
if (!verified) throw new Error('响应验签失败,丢弃响应,不更新业务状态');
const data = JSON.parse(raw.toString('utf8'));
if (data.mchid !== mchid || data.out_trade_no !== outTradeNo) throw new Error('返回的商户号或订单号不匹配');
console.log(JSON.stringify({ out_trade_no: data.out_trade_no, transaction_id: data.transaction_id,
trade_state: data.trade_state, amount: data.amount }, null, 2));
// 正式业务还必须核对 AppID、订单金额和币种,再幂等更新本地订单。
返回示例
按官方结构整理的节选示意,非实测结果。trade_state=SUCCESS 才表示支付成功;NOTPAY 是未支付。核对 amount.total(分)、currency、商户号、AppID 与本地订单后再处理。 以下为结构示意,非本站实测结果。
{
"mchid": "1900000001",
"appid": "wx_example_appid",
"out_trade_no": "DEMO_ORDER_20261015",
"transaction_id": "4200000000000000000000000000",
"trade_state": "SUCCESS",
"trade_state_desc": "支付成功",
"amount": {
"total": 100,
"payer_total": 100,
"currency": "CNY",
"payer_currency": "CNY"
}
}常见错误与排查
401 / SIGN_ERROR请求签名未通过- 核对商户私钥、商户证书序列号、五行换行和实际发送的 path+query。
404 / ORDER_NOT_EXIST订单不存在- 检查原商户订单号、商户号,以及之前是否成功下单。
429 / FREQUENCY_LIMITED请求频率超限- 降低查单频率,依据服务端结果合理退避。
调用注意
- 先验证原始响应签名,再解析和使用业务结果;错误的探测签名也应失败,不可旁路。
- 查询成功不一定已支付。未支付订单只能按商户订单号查询;本例不会替你创建订单。
- 示例代码完整包含本次 GET 认证与验签;仅展示 Node.js 22,避免把无签名 cURL 误当成可用调用。
注意事项
- 这些是正式 API 的只读查询,必须有自己的商户和已有业务资源;本例不是通用免资金沙盒,也不创建测试支付。
- 本页无统一查单单价承诺;正式收款手续费、退款费用处理按经营类目和商户签约规则,查询示例不代表减免交易费用。
- 商户请求签名使用商户私钥,响应验签使用微信支付公钥;API v3 密钥用于其他需要对称解密的场景,不能替代任何一方。
核验来源
本站按官方资料整理本场景的必要调用步骤与代码,覆盖范围以本页列出的接口为准。官方资料用于追溯规范及查看变更。
- 01普通商户开发必要参数核验日期 2026-10-15
- 02带路径参数的 API v3 请求签名核验日期 2026-10-15
- 03微信支付公钥验签核验日期 2026-10-15
- 04普通商户按商户订单号查单核验日期 2026-10-15
- 05验签前序列号与 5 分钟时间偏差检查核验日期 2026-10-15