q-koa 13.8.32 → 13.8.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/app.js +0 -2
- package/core/file/plugins/weixin/controller.js +113 -2
- package/package.json +1 -1
package/core/app.js
CHANGED
|
@@ -231,7 +231,6 @@ class APP {
|
|
|
231
231
|
* 挂在jwt解密方法
|
|
232
232
|
*/
|
|
233
233
|
if (this.app[appName]) {
|
|
234
|
-
console.time('ss')
|
|
235
234
|
this.app[appName].sign = async (user) => {
|
|
236
235
|
const result = await jwt.sign(user, secret, {
|
|
237
236
|
expiresIn,
|
|
@@ -239,7 +238,6 @@ class APP {
|
|
|
239
238
|
})
|
|
240
239
|
return result
|
|
241
240
|
}
|
|
242
|
-
console.timeEnd('ss')
|
|
243
241
|
}
|
|
244
242
|
// eslint-disable-next-line no-nested-ternary
|
|
245
243
|
ctx.request.clientType = ctx.header['client-type']
|
|
@@ -82,6 +82,54 @@ const sendWxFail = (ctx, msg = 'FAIL') => {
|
|
|
82
82
|
)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* 通联(AllinPay)融合回调签名验证(RSA / MD5)
|
|
87
|
+
* @param {Object} options
|
|
88
|
+
* @param {Object} options.notifyData 回调 body(必须包含 sign 字段)
|
|
89
|
+
* @param {string} options.signType 'RSA' | 'MD5'(已大写)
|
|
90
|
+
* @param {string} options.publicKey 通联平台公钥(PEM body,无 header/footer)
|
|
91
|
+
* @param {string} options.md5Key MD5 签名密钥(signType=MD5 时使用)
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
const validateUnionNotifySign = ({ notifyData, signType, publicKey, md5Key }) => {
|
|
95
|
+
if (!notifyData || !notifyData.sign) return false
|
|
96
|
+
const params = { ...notifyData }
|
|
97
|
+
const sign = String(params.sign)
|
|
98
|
+
delete params.sign
|
|
99
|
+
delete params.signType // signType 本身不参与签名原文
|
|
100
|
+
|
|
101
|
+
// 通联 buildSignStr:按 ASCII 字典序拼接,跳过空/undefined/null
|
|
102
|
+
const sortedKeys = Object.keys(params).sort()
|
|
103
|
+
let signStr = ''
|
|
104
|
+
for (const k of sortedKeys) {
|
|
105
|
+
const v = params[k]
|
|
106
|
+
if (v === undefined || v === null || v === '') continue
|
|
107
|
+
if (signStr) signStr += '&'
|
|
108
|
+
signStr += `${k}=${v}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (signType === 'MD5') {
|
|
112
|
+
if (!md5Key) return false
|
|
113
|
+
const expected = crypto
|
|
114
|
+
.createHash('md5')
|
|
115
|
+
.update(signStr + `&key=${md5Key}`, 'utf8')
|
|
116
|
+
.digest('hex')
|
|
117
|
+
.toUpperCase()
|
|
118
|
+
return expected === sign.toUpperCase()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 默认 RSA-SHA1(与 allinpay.js verifySign 保持一致)
|
|
122
|
+
if (!publicKey) return false
|
|
123
|
+
try {
|
|
124
|
+
const pem = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`
|
|
125
|
+
const verify = crypto.createVerify('RSA-SHA1')
|
|
126
|
+
verify.update(signStr, 'utf8')
|
|
127
|
+
return verify.verify(pem, sign, 'base64')
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
85
133
|
const check = ({ timestamp, nonce, signature, token }) => {
|
|
86
134
|
const tmp = [token, timestamp, nonce].sort().join('')
|
|
87
135
|
const currSign = crypto.createHash('sha1').update(tmp).digest('hex')
|
|
@@ -1453,11 +1501,74 @@ exports.union_notify = async (ctx) => {
|
|
|
1453
1501
|
try {
|
|
1454
1502
|
const notifyData = ctx.request.body
|
|
1455
1503
|
|
|
1504
|
+
// TODO: 临时观察日志,上线后删除
|
|
1505
|
+
app.service.log.push({
|
|
1506
|
+
app,
|
|
1507
|
+
message: `通联融合回调union_notify报文: ${JSON.stringify(notifyData || {})}`,
|
|
1508
|
+
})
|
|
1509
|
+
|
|
1510
|
+
if (!notifyData || typeof notifyData !== 'object') {
|
|
1511
|
+
ctx.status = 400
|
|
1512
|
+
ctx.body = 'FAIL'
|
|
1513
|
+
return
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// ===== 通联(AllinPay)回调验签 + 商户号绑定 =====
|
|
1517
|
+
const pay_config = 'weixin_pay'
|
|
1518
|
+
const appConfig = getConfig(app)
|
|
1519
|
+
const {
|
|
1520
|
+
union_mchId,
|
|
1521
|
+
union_appid,
|
|
1522
|
+
union_publicKey,
|
|
1523
|
+
union_md5Key,
|
|
1524
|
+
union_signType,
|
|
1525
|
+
} = await appConfig.getObject(pay_config)
|
|
1526
|
+
const cusid = notifyData.cusid
|
|
1527
|
+
|
|
1528
|
+
if (cusid && union_mchId && cusid !== union_mchId) {
|
|
1529
|
+
app.service.log.push({
|
|
1530
|
+
app,
|
|
1531
|
+
message: `通联回调商户号不匹配: cfg=${union_mchId} got=${cusid}`,
|
|
1532
|
+
})
|
|
1533
|
+
ctx.body = 'FAIL'
|
|
1534
|
+
return
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// 优先按回调带的 signType 决定算法,没有则退到配置 union_signType,再退 RSA
|
|
1538
|
+
const rawSignType = (notifyData.signType || union_signType || 'RSA').toUpperCase()
|
|
1539
|
+
const hasSign = notifyData.sign !== undefined && notifyData.sign !== ''
|
|
1540
|
+
const trxstatus = notifyData.trxstatus
|
|
1541
|
+
|
|
1542
|
+
// 仅当成功状态(trxstatus==='0000')强制要求签名;失败状态没有 sign 时仍打日志但直接返回 success(不重放)
|
|
1543
|
+
if (trxstatus === '0000' && !hasSign) {
|
|
1544
|
+
app.service.log.push({
|
|
1545
|
+
app,
|
|
1546
|
+
message: `通联回调签名缺失: ${JSON.stringify(notifyData)}`,
|
|
1547
|
+
})
|
|
1548
|
+
ctx.body = 'FAIL'
|
|
1549
|
+
return
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (hasSign) {
|
|
1553
|
+
const signOk = validateUnionNotifySign({
|
|
1554
|
+
notifyData,
|
|
1555
|
+
signType: rawSignType,
|
|
1556
|
+
publicKey: union_publicKey,
|
|
1557
|
+
md5Key: union_md5Key,
|
|
1558
|
+
})
|
|
1559
|
+
if (!signOk) {
|
|
1560
|
+
app.service.log.push({
|
|
1561
|
+
app,
|
|
1562
|
+
message: `通联回调验签失败(type=${rawSignType}): ${JSON.stringify(notifyData)}`,
|
|
1563
|
+
})
|
|
1564
|
+
ctx.body = 'FAIL'
|
|
1565
|
+
return
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1456
1569
|
const {
|
|
1457
1570
|
trxcode,
|
|
1458
|
-
trxstatus,
|
|
1459
1571
|
cusorderid,
|
|
1460
|
-
cusid,
|
|
1461
1572
|
acct,
|
|
1462
1573
|
trxid,
|
|
1463
1574
|
trxamt,
|