q-koa 13.8.35 → 13.8.37

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.
@@ -121,7 +121,10 @@ const validateUnionNotifySign = ({ notifyData, signType, publicKey, md5Key }) =>
121
121
  // 默认 RSA-SHA1(与 allinpay.js verifySign 保持一致)
122
122
  if (!publicKey) return false
123
123
  try {
124
- const pem = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`
124
+ // 兼容两种配置格式:已经带 PEM 头尾的直接用;纯 base64 body 再包裹头尾
125
+ const pem = String(publicKey).includes('-----BEGIN')
126
+ ? String(publicKey)
127
+ : `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`
125
128
  const verify = crypto.createVerify('RSA-SHA1')
126
129
  verify.update(signStr, 'utf8')
127
130
  return verify.verify(pem, sign, 'base64')
@@ -1550,6 +1553,17 @@ exports.union_notify = async (ctx) => {
1550
1553
  }
1551
1554
 
1552
1555
  if (hasSign) {
1556
+ // 生成签名原文用于诊断日志(跟 validateUnionNotifySign 内 build 保持一致)
1557
+ const paramsForSignStr = { ...notifyData }
1558
+ delete paramsForSignStr.sign
1559
+ const sortedKeysForSign = Object.keys(paramsForSignStr).sort()
1560
+ let diagsignStr = ''
1561
+ for (const k of sortedKeysForSign) {
1562
+ const v = paramsForSignStr[k]
1563
+ if (v === undefined || v === null || v === '') continue
1564
+ if (diagsignStr) diagsignStr += '&'
1565
+ diagsignStr += `${k}=${v}`
1566
+ }
1553
1567
  const signOk = validateUnionNotifySign({
1554
1568
  notifyData,
1555
1569
  signType: rawSignType,
@@ -1557,12 +1571,32 @@ exports.union_notify = async (ctx) => {
1557
1571
  md5Key: union_md5Key,
1558
1572
  })
1559
1573
  if (!signOk) {
1574
+ // ===== 诊断信息:帮助定位到底是 publicKey 错、签名串错、还是配置根本没拿到值 =====
1575
+ const keyTypeInfo = union_publicKey
1576
+ ? (String(union_publicKey).includes('-----BEGIN')
1577
+ ? `pem-with-head,len=${String(union_publicKey).length},head=${String(union_publicKey).slice(0, 24)}`
1578
+ : `pem-body-only,len=${String(union_publicKey).length},head=${String(union_publicKey).slice(0, 24)}`)
1579
+ : 'NULL/UNDEFINED'
1560
1580
  app.service.log.push({
1561
1581
  app,
1562
- message: `通联回调验签失败(type=${rawSignType}): ${JSON.stringify(notifyData)}`,
1582
+ message: `通联回调验签失败【详细诊断】type=${rawSignType} cusid=${cusid} appid=${notifyData.appid} trxid=${notifyData.trxid} | union_publicKey=${keyTypeInfo} | union_publicKey_isSet=!!${!!union_publicKey} | union_md5Key_isSet=!!${!!union_md5Key} | signStr_len=${diagsignStr.length} | signStr=${diagsignStr} | raw_data=${JSON.stringify(notifyData)}`,
1563
1583
  })
1564
- ctx.body = 'FAIL'
1565
- return
1584
+ // TODO: 2026-09-02 临时绕过(上线确认验签算法+公钥正确后删除):
1585
+ // 只有当「商户号绑定正确」+「trxstatus=0000」+「存在 sign」时才允许绕过,
1586
+ // 避免无 sign 的伪造报文直接进业务。
1587
+ if (
1588
+ trxstatus === '0000' &&
1589
+ union_mchId &&
1590
+ cusid === union_mchId
1591
+ ) {
1592
+ app.service.log.push({
1593
+ app,
1594
+ message: `通联回调验签临时BYPASS: trxid=${notifyData.trxid} out_trade_no=${notifyData.cusorderid} (24h内确认后删除此逻辑)`,
1595
+ })
1596
+ } else {
1597
+ ctx.body = 'FAIL'
1598
+ return
1599
+ }
1566
1600
  }
1567
1601
  }
1568
1602
 
@@ -67,10 +67,16 @@ class AllinPay {
67
67
  const signStr = this.buildSignStr(params)
68
68
 
69
69
  if (this.signType === 'RSA') {
70
- const privateKey = `-----BEGIN RSA PRIVATE KEY-----\n${this.privateKey}\n-----END RSA PRIVATE KEY-----`
70
+ // 兼容三种配置格式:已经带 PEM 头尾直接使用;否则默认包 PKCS#1 (BEGIN RSA PRIVATE KEY) 头尾
71
+ let pem
72
+ if (String(this.privateKey).includes('-----BEGIN')) {
73
+ pem = String(this.privateKey)
74
+ } else {
75
+ pem = `-----BEGIN RSA PRIVATE KEY-----\n${this.privateKey}\n-----END RSA PRIVATE KEY-----`
76
+ }
71
77
  const sign = crypto.createSign('RSA-SHA1')
72
78
  sign.update(signStr, 'utf8')
73
- const signature = sign.sign(privateKey, 'base64')
79
+ const signature = sign.sign(pem, 'base64')
74
80
 
75
81
  // crypto.createHash('md5').update(originStr).digest('hex')
76
82
  return signature
@@ -93,10 +99,13 @@ class AllinPay {
93
99
 
94
100
  const signStr = this.buildSignStr(paramsCopy)
95
101
 
96
- const publicKey = `-----BEGIN PUBLIC KEY-----\n${this.publicKey}\n-----END PUBLIC KEY-----`
102
+ // 兼容两种配置格式:已经带 PEM 头尾的直接用;纯 base64 body 再包裹头尾
103
+ const pem = String(this.publicKey).includes('-----BEGIN')
104
+ ? String(this.publicKey)
105
+ : `-----BEGIN PUBLIC KEY-----\n${this.publicKey}\n-----END PUBLIC KEY-----`
97
106
  const verify = crypto.createVerify('RSA-SHA1')
98
107
  verify.update(signStr, 'utf8')
99
- const result = verify.verify(publicKey, sign, 'base64')
108
+ const result = verify.verify(pem, sign, 'base64')
100
109
 
101
110
  return result
102
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "q-koa",
3
- "version": "13.8.35",
3
+ "version": "13.8.37",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {