feat: 微信支付v3真实下单实现(商户证书就绪即通)+缺证书时明确503提示;certs目录说明
This commit is contained in:
@@ -28,3 +28,4 @@ client/src/.share-done
|
|||||||
admin/node_modules/
|
admin/node_modules/
|
||||||
admin/dist/
|
admin/dist/
|
||||||
server/data/
|
server/data/
|
||||||
|
!server/data/certs/README.md
|
||||||
|
|||||||
@@ -30,6 +30,25 @@ class WeChatService {
|
|||||||
this.tokenExpireTime = 0
|
this.tokenExpireTime = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付 v3 是否配置完整(商户号+序列号+商户私钥)
|
||||||
|
*/
|
||||||
|
isPayConfigured() {
|
||||||
|
return !!(this.mchId && this.serialNo && this.privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成微信支付 v3 请求头 Authorization(商户私钥签名)
|
||||||
|
*/
|
||||||
|
buildV3Auth(method, urlPath, timestamp, nonce, bodyStr) {
|
||||||
|
const message = `${method}\n${urlPath}\n${timestamp}\n${nonce}\n${bodyStr}\n`
|
||||||
|
const signer = crypto.createSign('RSA-SHA256')
|
||||||
|
signer.update(message)
|
||||||
|
signer.end()
|
||||||
|
const signature = signer.sign(this.privateKey, 'base64')
|
||||||
|
return `WECHATPAY2-SHA256-RFC4648 mchid="${this.mchId}",nonce_str="${nonce}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 Access Token
|
* 获取 Access Token
|
||||||
*/
|
*/
|
||||||
@@ -154,15 +173,40 @@ class WeChatService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isPayConfigured()) {
|
||||||
|
const err = new Error('支付功能暂未开通:缺少商户证书配置,请联系管理员在服务器放置 apiclient_key.pem 后重启服务')
|
||||||
|
err.code = 'PAY_NOT_CONFIGURED'
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 注意:实际生产环境需要处理网络请求和签名
|
// 1. 统一下单(真·微信支付 v3 JSAPI)
|
||||||
// const response = await axios.post(fullUrl, orderParams, { ... })
|
const bodyStr = JSON.stringify(orderParams)
|
||||||
// const prepayId = response.data.prepay_id
|
const auth = this.buildV3Auth('POST', url, timestamp, nonceStr, bodyStr)
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timer = setTimeout(() => controller.abort(), 15000)
|
||||||
|
let response
|
||||||
|
try {
|
||||||
|
response = await axios.post(fullUrl, bodyStr, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: auth,
|
||||||
|
'User-Agent': 'yuzhiran-ai-dimension/1.2'
|
||||||
|
},
|
||||||
|
timeout: 15000,
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
|
||||||
// 模拟获取 prepay_id
|
const prepayId = response.data && response.data.prepay_id
|
||||||
const prepayId = `wx_mock_${Date.now()}`
|
if (!prepayId) {
|
||||||
|
throw new Error('微信支付下单未返回 prepay_id')
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 生成前端调起支付的签名
|
// 2. 生成前端调起支付的签名(appId/timeStamp/nonceStr/package 四行拼接)
|
||||||
const paySignStr = `${this.appId}\n${timestamp}\n${nonceStr}\nprepay_id=${prepayId}\n`
|
const paySignStr = `${this.appId}\n${timestamp}\n${nonceStr}\nprepay_id=${prepayId}\n`
|
||||||
const paySign = crypto.createSign('RSA-SHA256')
|
const paySign = crypto.createSign('RSA-SHA256')
|
||||||
paySign.update(paySignStr)
|
paySign.update(paySignStr)
|
||||||
@@ -179,8 +223,10 @@ class WeChatService {
|
|||||||
orderNo
|
orderNo
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('微信支付下单失败:', error)
|
console.error('微信支付下单失败:', error.response ? error.response.data : error.message)
|
||||||
throw error
|
if (error.code === 'PAY_NOT_CONFIGURED') throw error
|
||||||
|
const wxMsg = error.response && error.response.data && error.response.data.message
|
||||||
|
throw new Error(wxMsg ? `微信支付下单失败:${wxMsg}` : '微信支付下单失败,请稍后再试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user