微信JSAPI支付是公众号内支付的主流方案,但不少开发者在调起支付时遇到过各种各样的报错,其中timeStamp参数格式问题是最高频的一个坑。JavaScript的Date.now()返回的是毫秒级时间戳,而微信支付接口要求timeStamp必须是秒级时间戳的字符串形式,一旦传错,轻则前端报参数格式错误,重则签名校验失败,而且错误提示往往不够直观,排查起来非常费时。本文将围绕这个问题展开,讲清楚timeStamp的正确格式、错误产生的典型场景以及完整的修复方案。
为什么timeStamp必须是秒级时间戳
微信支付统一下单接口以及前端调起支付的配置参数中,timeStamp字段在官方文档中明确定义为:当前时间戳,单位为秒,长度为10位数字,类型为字符串。这个规范沿用自早期的支付签名体系,属于微信支付协议层的约定,所有参与签名的字段都必须严格按照这个格式来。
问题出在JavaScript的语言特性上。Date.now()和new Date().getTime()返回的都是毫秒级时间戳,长度为13位数字。很多后端用Node.js开发,或者前端直接生成时间戳传给后端签名,就很容易把毫秒值直接塞进timeStamp字段。毫秒值传给微信统一下单接口时,服务端会直接返回类似invalid time_stamp或者参数错误:timeStamp格式不符合要求的错误;如果时间戳只在后端签名用了毫秒,而前端调起时又用了秒,两边不一致还会导致签名验证失败,这是最隐蔽的情况。
另外一个容易忽略的细节是类型问题。timeStamp要求是字符串而不是数字。微信内置浏览器调起支付时的JS接口WeixinJSBridge.invoke('getBrandWCPayRequest', ...)和wx.chooseWXPay都对类型敏感,如果传了数字类型,部分安卓机型上会静默失败,连报错信息都不弹出来,排查难度更大。所以正确做法是既转成秒级,又转成字符串。
错误的写法与正确的写法对比
先看典型的错误代码。下面这段Node.js代码是很多项目中常见的写法,直接用毫秒时间戳参与签名和返回给前端:
// 错误示例:使用毫秒级时间戳
const timeStamp = Date.now(); // 例如 1718900000000,13位,错误
const nonceStr = 'abcdefg123456';
const package = 'prepay_id=wx20170101abcdef';
// 签名时使用了错误的时间戳
const paySign = md5(`appId=wxAppId&nonceStr=${nonceStr}&package=${package}&signType=MD5&timeStamp=${timeStamp}&key=apiKey`);
res.json({ timeStamp, nonceStr, package, signType: 'MD5', paySign });
这段代码的问题有两个:一是Date.now()返回的是13位毫秒值,二是timeStamp是number类型直接JSON序列化后传给前端。微信支付后端在校验统一下单请求或者签名时会失败,前端调起时也可能直接提示支付失败。
正确的写法是除以1000后取整,并转成字符串:
// 正确示例:秒级时间戳 + 字符串类型
const timeStamp = String(Math.floor(Date.now() / 1000)); // 例如 "1718900000",10位字符串
const nonceStr = 'abcdefg123456';
const package = 'prepay_id=wx20170101abcdef';
const signType = 'RSA'; // V3接口使用RSA签名
// 参与签名的字段按规范拼接,timeStamp使用秒级字符串
const message = `appId=wxAppId\ntimeStamp=${timeStamp}\nnonceStr=${nonceStr}\npackage=${package}\n`;
// 使用商户私钥对报文进行SHA256 with RSA签名
const paySign = crypto.createSign('RSA-SHA256').update(message).sign(privateKey, 'base64');
res.json({ timeStamp, nonceStr, package, signType, paySign });
注意两点关键区别:第一,Math.floor(Date.now() / 1000)保证结果为10位秒级时间戳,再用String()转换类型;第二,签名时参与运算的timeStamp必须与返回给前端的timeStamp完全一致,包括值和类型。有些开发者签名时用秒、返回给前端时用毫秒,或者反过来,两边不一致必然导致调起支付返回签名验证错误。
前端调起支付时的注意事项
拿到后端返回的支付参数后,前端在微信内置浏览器中调起支付。推荐优先使用JSSDK的wx.chooseWXPay方式,需要先完成JSSDK配置,然后再调起支付:
// 后端接口返回的支付参数
fetch('/api/pay/create').then(res => res.json()).then(data => {
wx.chooseWXPay({
timestamp: data.timeStamp, // 注意字段名是timestamp,值为10位秒级字符串
nonceStr: data.nonceStr,
package: data.package,
signType: data.signType,
paySign: data.paySign,
success: function (res) {
// 支付成功后的回调,建议以后端查单结果为准
if (res.errMsg === 'chooseWXPay:ok') {
location.href = '/pay/result?orderId=' + orderId;
}
},
cancel: function (res) {
// 用户取消支付
},
fail: function (res) {
alert('调起支付失败:' + res.errMsg);
}
});
});
如果不引入JSSDK,也可以使用微信内置浏览器提供的WeixinJSBridge,这时字段名是驼峰的timeStamp:
function onBridgeReady(payParams) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
appId: payParams.appId,
timeStamp: payParams.timeStamp, // 10位秒级字符串
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign
}, function (res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 支付成功
}
});
}
if (typeof WeixinJSBridge === 'undefined') {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}
} else {
onBridgeReady(payParams);
}
这里有一个经典陷阱需要注意:JSSDK方式下字段名是小写开头的timestamp,而WeixinJSBridge方式下是timeStamp,两种方式字段名不同。如果字段名写错,支付会直接报chooseWXPay:fail,这也经常和timeStamp问题混淆在一起排查。
排查思路与常见错误信息汇总
当调起支付失败时,可以按照以下顺序排查:
- 检查timeStamp位数:正常应为10位数字,如果是13位说明传了毫秒值;如果是其他位数则说明生成逻辑有bug。
- 检查timeStamp类型:使用
typeof timeStamp确认是string而不是number。 - 检查签名一致性:后端签名时的timeStamp和返回前端的timeStamp必须完全相同,可以在前端打印出来逐一比对。
- 检查当前时间与微信服务器时间偏差:如果服务器时间不准,即使格式正确也可能报错,可通过NTP同步服务器时间。
- 检查签名算法:V3接口使用RSA-SHA256,V2接口使用MD5或HMAC-SHA256,签名类型和密钥不能混用。
常见错误信息与timeStamp相关的对应关系如下:
| 错误信息 | 可能原因 |
|---|---|
| chooseWXPay:fail | timeStamp为毫秒值、类型错误或字段名拼写不对 |
| 签名验证错误 | 签名参与字段与调起参数不一致,或timeStamp单位错误 |
| invalid time_stamp | 统一下单时timeStamp格式非法或服务器时间偏差过大 |
| get_brand_wcpay_request:fail | WeixinJSBridge调起参数中timeStamp格式不符 |
最后提醒一点,timeStamp的正确生成应该只发生在服务端,不要让前端生成时间戳再传给后端签名。前端时间可以随意被用户修改,而且参与签名的参数必须由可信方统一生成。养成String(Math.floor(Date.now() / 1000))这个固定写法的习惯,并且封装成独立的工具函数在项目中复用,就能从根源上避免这个问题的再次出现。
微信JSAPI支付timeStamp秒级时间戳WeixinJSBridge支付修改时间:2026-08-31 06:38:32