1
0
mirror of synced 2025-11-06 04:20:53 +08:00

Compare commits

...

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
bcce7f73a4 Fix WeChat Pay V3 public key transfer signature verification failure
When using public key mode for transfer APIs, WeChat Pay may return a response with
a platform certificate serial number in the Wechatpay-Serial header, but the signature
is actually signed with the public key. The previous logic would fail to verify this.

Changes:
- Modified PublicCertificateVerifier.verify() to fallback to public key verification
  when certificate verification fails
- This ensures both platform certificate and public key signatures can be verified
- Fixes the issue where funds are locked but verification fails for transfer APIs

Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
2025-10-03 17:43:00 +00:00
copilot-swe-agent[bot]
942c431549 Initial plan 2025-10-03 17:34:37 +00:00
Mr.Robot
9fd12b2a09 🎨 WxOpenMessageRouter增加注解ConditionalOnMissingBean 2025-10-04 01:32:23 +08:00
xiaoyun461
10f71234e4 🆕 #3725【企业微信】 增加markdown_v2的消息类型支持 2025-10-04 01:31:24 +08:00
Copilot
9aa2781e68 🎨 #3634 【微信支付】尝试修复服务商模式分账动账通知非法请求、头部信息验证失败的问题 2025-09-27 17:11:32 +08:00
8 changed files with 102 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ public class WxOpenServiceAutoConfiguration {
}
@Bean
@ConditionalOnMissingBean
public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) {
return new WxOpenMessageRouter(wxOpenService);
}

View File

@@ -70,6 +70,23 @@ public interface WxCpGroupRobotService {
*/
void sendMarkdown(String webhookUrl, String content) throws WxErrorException;
/**
* 发送markdown_v2类型的消息
*
* @param content markdown内容最长不超过4096个字节必须是utf8编码
* @throws WxErrorException 异常
*/
void sendMarkdownV2(String content) throws WxErrorException;
/**
* 发送markdown_v2类型的消息
*
* @param webhookUrl webhook地址
* @param content markdown内容最长不超过4096个字节必须是utf8编码
* @throws WxErrorException 异常
*/
void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException;
/**
* 发送image类型的消息
*

View File

@@ -42,6 +42,11 @@ public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
this.sendMarkdown(this.getWebhookUrl(), content);
}
@Override
public void sendMarkdownV2(String content) throws WxErrorException {
this.sendMarkdownV2(this.getWebhookUrl(), content);
}
@Override
public void sendImage(String base64, String md5) throws WxErrorException {
this.sendImage(this.getWebhookUrl(), base64, md5);
@@ -70,6 +75,14 @@ public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
.toJson());
}
@Override
public void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException {
this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage()
.setMsgType(GroupRobotMsgType.MARKDOWN_V2)
.setContent(content)
.toJson());
}
@Override
public void sendImage(String webhookUrl, String base64, String md5) throws WxErrorException {
this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage()

View File

@@ -252,6 +252,12 @@ public class WxCpGroupRobotMessage implements Serializable {
messageJson.add("markdown", text);
break;
}
case MARKDOWN_V2: {
JsonObject text = new JsonObject();
text.addProperty("content", this.getContent());
messageJson.add("markdown_v2", text);
break;
}
case IMAGE: {
JsonObject text = new JsonObject();
text.addProperty("base64", this.getBase64());

View File

@@ -630,6 +630,11 @@ public class WxCpConsts {
*/
public static final String MARKDOWN = "markdown";
/**
* markdown_v2消息.
*/
public static final String MARKDOWN_V2 = "markdown_v2";
/**
* 图文消息(点击跳转到外链).
*/

View File

@@ -64,6 +64,51 @@ public class WxCpGroupRobotServiceImplTest {
robotService.sendMarkdown(content);
}
/**
* Test send mark down v2.
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testSendMarkDownV2() throws WxErrorException {
String content = "# 一、标题\n" +
"## 二级标题\n" +
"### 三级标题\n" +
"# 二、字体\n" +
"*斜体*\n" +
"\n" +
"**加粗**\n" +
"# 三、列表 \n" +
"- 无序列表 1 \n" +
"- 无序列表 2\n" +
" - 无序列表 2.1\n" +
" - 无序列表 2.2\n" +
"1. 有序列表 1\n" +
"2. 有序列表 2\n" +
"# 四、引用\n" +
"> 一级引用\n" +
">>二级引用\n" +
">>>三级引用\n" +
"# 五、链接\n" +
"[这是一个链接](https://work.weixin.qq.com/api/doc)\n" +
"![](https://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png)\n" +
"# 六、分割线\n" +
"\n" +
"---\n" +
"# 七、代码\n" +
"`这是行内代码`\n" +
"```\n" +
"这是独立代码块\n" +
"```\n" +
"\n" +
"# 八、表格\n" +
"| 姓名 | 文化衫尺寸 | 收货地址 |\n" +
"| :----- | :----: | -------: |\n" +
"| 张三 | S | 广州 |\n" +
"| 李四 | L | 深圳 |";
robotService.sendMarkdownV2(content);
}
/**
* Test send image.
*

View File

@@ -7,6 +7,7 @@ import com.github.binarywang.wxpay.bean.profitsharing.request.*;
import com.github.binarywang.wxpay.bean.profitsharing.result.*;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.exception.WxSignTestException;
import com.github.binarywang.wxpay.service.ProfitSharingService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.Verifier;
@@ -293,7 +294,11 @@ public class ProfitSharingServiceImpl implements ProfitSharingService {
* @return true:校验通过 false:校验不通过
*/
private boolean verifyNotifySign(SignatureHeader header, String data) throws WxPayException {
String beforeSign = String.format("%s%n%s%n%s%n", header.getTimeStamp(), header.getNonce(), data);
String wxPaySign = header.getSignature();
if (wxPaySign.startsWith("WECHATPAY/SIGNTEST/")) {
throw new WxSignTestException("微信支付签名探测流量");
}
String beforeSign = String.format("%s\n%s\n%s\n", header.getTimeStamp(), header.getNonce(), data);
Verifier verifier = this.payService.getConfig().getVerifier();
if (verifier == null) {
throw new WxPayException("证书检验对象为空");

View File

@@ -24,9 +24,17 @@ public class PublicCertificateVerifier implements Verifier{
@Override
public boolean verify(String serialNumber, byte[] message, String signature) {
// 如果序列号不包含"PUB_KEY_ID"且有证书验证器,先尝试证书验证
if (!serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) {
return this.certificateVerifier.verify(serialNumber, message, signature);
try {
if (this.certificateVerifier.verify(serialNumber, message, signature)) {
return true;
}
} catch (Exception e) {
// 证书验证失败,继续尝试公钥验证
}
}
// 使用公钥验证(兜底方案,适用于公钥转账等场景)
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(publicKey);