From 9aa2781e6825dd5b6a6f6f36e67f77ad4985acc4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Sep 2025 17:11:32 +0800 Subject: [PATCH 001/111] =?UTF-8?q?:art:=20#3634=20=E3=80=90=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=B0=9D=E8=AF=95=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=9C=8D=E5=8A=A1=E5=95=86=E6=A8=A1=E5=BC=8F=E5=88=86?= =?UTF-8?q?=E8=B4=A6=E5=8A=A8=E8=B4=A6=E9=80=9A=E7=9F=A5=E9=9D=9E=E6=B3=95?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E3=80=81=E5=A4=B4=E9=83=A8=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wxpay/service/impl/ProfitSharingServiceImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java index 6be5ffc8c1..afaa45440a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java @@ -7,6 +7,7 @@ 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 @@ private ProfitSharingNotifyV3Response parseNotifyData(String data, SignatureHead * @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("证书检验对象为空"); From 10f71234e41e93a1253474a4bff79ab334b58113 Mon Sep 17 00:00:00 2001 From: xiaoyun461 <35725210+xiaoyun461@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:31:24 +0800 Subject: [PATCH 002/111] =?UTF-8?q?:new:=20#3725=E3=80=90=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=20=E5=A2=9E=E5=8A=A0markdo?= =?UTF-8?q?wn=5Fv2=E7=9A=84=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weixin/cp/api/WxCpGroupRobotService.java | 17 +++++++ .../api/impl/WxCpGroupRobotServiceImpl.java | 13 ++++++ .../bean/message/WxCpGroupRobotMessage.java | 6 +++ .../weixin/cp/constant/WxCpConsts.java | 5 +++ .../impl/WxCpGroupRobotServiceImplTest.java | 45 +++++++++++++++++++ 5 files changed, 86 insertions(+) diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java index e396ed58ac..c1a8d56255 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java @@ -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类型的消息 * diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java index 21246d2415..8373c6c8ee 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java @@ -42,6 +42,11 @@ public void sendMarkdown(String content) throws WxErrorException { 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 void sendMarkdown(String webhookUrl, String content) throws WxErrorExcept .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() diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java index d115245e04..97beeec189 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java @@ -252,6 +252,12 @@ public String toJson() { 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()); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java index 3d51c9e2c9..ff3f8e0e6c 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java @@ -630,6 +630,11 @@ public static class GroupRobotMsgType { */ public static final String MARKDOWN = "markdown"; + /** + * markdown_v2消息. + */ + public static final String MARKDOWN_V2 = "markdown_v2"; + /** * 图文消息(点击跳转到外链). */ diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java index 8e0d87d82c..f66580cc94 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java @@ -64,6 +64,51 @@ public void testSendMarkDown() throws WxErrorException { 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" + + "\n" + + "# 六、分割线\n" + + "\n" + + "---\n" + + "# 七、代码\n" + + "`这是行内代码`\n" + + "```\n" + + "这是独立代码块\n" + + "```\n" + + "\n" + + "# 八、表格\n" + + "| 姓名 | 文化衫尺寸 | 收货地址 |\n" + + "| :----- | :----: | -------: |\n" + + "| 张三 | S | 广州 |\n" + + "| 李四 | L | 深圳 |"; + robotService.sendMarkdownV2(content); + } + /** * Test send image. * From 9fd12b2a09c13199d04818715fd46f00f3e83fbc Mon Sep 17 00:00:00 2001 From: "Mr.Robot" <49147552+SunnyBoyLJQ@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:32:23 +0800 Subject: [PATCH 003/111] =?UTF-8?q?:art:=20WxOpenMessageRouter=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E8=A7=A3ConditionalOnMissingBean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wxjava/open/config/WxOpenServiceAutoConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java index 22b0a6621d..e532f3c160 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java @@ -28,6 +28,7 @@ public WxOpenService wxOpenService(WxOpenConfigStorage wxOpenConfigStorage) { } @Bean + @ConditionalOnMissingBean public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) { return new WxOpenMessageRouter(wxOpenService); } From a6825a62bb4fa1d1e620969d81002fc77b271b56 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:39:35 +0800 Subject: [PATCH 004/111] =?UTF-8?q?:art:=20#3628=E3=80=90=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E4=BF=AE=E5=A4=8D=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=94=A8=E6=88=B7=E6=8E=A5=E5=8F=A3=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9B=B4=E5=B1=9E=E9=A2=86=E5=AF=BC=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cp/util/json/WxCpUserGsonAdapter.java | 7 ++++- .../cp/util/json/WxCpUserGsonAdapterTest.java | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java index 0da35ff7fb..1df32b8601 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java @@ -281,7 +281,12 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon } addProperty(o, MAIN_DEPARTMENT, user.getMainDepartment()); - addArrayProperty(o, DIRECT_LEADER, user.getDirectLeader()); + // Special handling for directLeader: include empty arrays to support WeChat Work API reset functionality + if (user.getDirectLeader() != null) { + JsonArray directLeaderArray = new JsonArray(); + Arrays.stream(user.getDirectLeader()).forEach(directLeaderArray::add); + o.add(DIRECT_LEADER, directLeaderArray); + } if (!user.getExtAttrs().isEmpty()) { JsonArray attrsJsonArray = new JsonArray(); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java index 9b62a8d580..66be5c66a2 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java @@ -180,4 +180,31 @@ public void testSerialize() { "{\"type\":2,\"name\":\"测试app\"," + "\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}"); } + + /** + * Test directLeader empty array serialization. + * This test verifies that empty directLeader arrays are included in JSON as "direct_leader":[] + * instead of being omitted, which is required for WeChat Work API to reset user direct leaders. + */ + @Test + public void testDirectLeaderEmptyArraySerialization() { + WxCpUser user = new WxCpUser(); + user.setUserId("testuser"); + user.setName("Test User"); + + // Test with empty array - should be serialized as "direct_leader":[] + user.setDirectLeader(new String[]{}); + String json = user.toJson(); + assertThat(json).contains("\"direct_leader\":[]"); + + // Test with null - should not include direct_leader field + user.setDirectLeader(null); + json = user.toJson(); + assertThat(json).doesNotContain("direct_leader"); + + // Test with non-empty array - should be serialized normally + user.setDirectLeader(new String[]{"leader1", "leader2"}); + json = user.toJson(); + assertThat(json).contains("\"direct_leader\":[\"leader1\",\"leader2\"]"); + } } From ca567ce310bc29312763a0af9899275e03c374ba Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:44:56 +0800 Subject: [PATCH 005/111] =?UTF-8?q?:new:=20#3618=20=E3=80=90=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=A2=9E=E5=8A=A0=E5=A2=83?= =?UTF-8?q?=E5=A4=96=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weixin-java-pay/OVERSEAS_PAY.md | 120 ++++++++++++++ .../WxPayUnifiedOrderV3GlobalRequest.java | 57 +++++++ .../result/enums/GlobalTradeTypeEnum.java | 36 +++++ .../wxpay/service/WxPayService.java | 22 +++ .../service/impl/BaseWxPayServiceImpl.java | 31 ++++ .../impl/BaseWxPayServiceGlobalImplTest.java | 89 ++++++++++ .../service/impl/OverseasWxPayExample.java | 153 ++++++++++++++++++ 7 files changed, 508 insertions(+) create mode 100644 weixin-java-pay/OVERSEAS_PAY.md create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java diff --git a/weixin-java-pay/OVERSEAS_PAY.md b/weixin-java-pay/OVERSEAS_PAY.md new file mode 100644 index 0000000000..b9da9814f9 --- /dev/null +++ b/weixin-java-pay/OVERSEAS_PAY.md @@ -0,0 +1,120 @@ +# 境外微信支付(Overseas WeChat Pay)支持 + +本次更新添加了境外微信支付的支持,解决了 [Issue #3618](https://github.com/binarywang/WxJava/issues/3618) 中提到的问题。 + +## 问题背景 + +境外微信支付需要使用新的API接口地址和额外的参数: +- 使用不同的基础URL: `https://apihk.mch.weixin.qq.com` +- 需要额外的参数: `trade_type` 和 `merchant_category_code` +- 使用不同的API端点: `/global/v3/transactions/*` + +## 新增功能 + +### 1. GlobalTradeTypeEnum +新的枚举类,定义了境外支付的交易类型和对应的API端点: +- `APP`: `/global/v3/transactions/app` +- `JSAPI`: `/global/v3/transactions/jsapi` +- `NATIVE`: `/global/v3/transactions/native` +- `H5`: `/global/v3/transactions/h5` + +### 2. WxPayUnifiedOrderV3GlobalRequest +扩展的请求类,包含境外支付必需的额外字段: +- `trade_type`: 交易类型 (JSAPI, APP, NATIVE, H5) +- `merchant_category_code`: 商户类目代码(境外商户必填) + +### 3. 新的服务方法 +- `createOrderV3Global()`: 创建境外支付订单 +- `unifiedOrderV3Global()`: 境外统一下单接口 + +## 使用示例 + +### JSAPI支付示例 +```java +// 创建境外支付请求 +WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); +request.setOutTradeNo(RandomUtils.getRandomStr()); +request.setDescription("境外商品购买"); +request.setNotifyUrl("https://your-domain.com/notify"); + +// 设置金额 +WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount(); +amount.setCurrency(WxPayConstants.CurrencyType.CNY); +amount.setTotal(100); // 1元,单位为分 +request.setAmount(amount); + +// 设置支付者 +WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer(); +payer.setOpenid("用户的openid"); +request.setPayer(payer); + +// 设置境外支付必需的参数 +request.setTradeType("JSAPI"); +request.setMerchantCategoryCode("5812"); // 商户类目代码 + +// 调用境外支付接口 +WxPayUnifiedOrderV3Result.JsapiResult result = payService.createOrderV3Global( + GlobalTradeTypeEnum.JSAPI, + request +); +``` + +### APP支付示例 +```java +WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); +// ... 设置基础信息 ... + +request.setTradeType("APP"); +request.setMerchantCategoryCode("5812"); +request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); // APP支付不需要openid + +WxPayUnifiedOrderV3Result.AppResult result = payService.createOrderV3Global( + GlobalTradeTypeEnum.APP, + request +); +``` + +### NATIVE支付示例 +```java +WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); +// ... 设置基础信息 ... + +request.setTradeType("NATIVE"); +request.setMerchantCategoryCode("5812"); +request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); + +String codeUrl = payService.createOrderV3Global( + GlobalTradeTypeEnum.NATIVE, + request +); +``` + +## 配置说明 + +境外支付使用相同的 `WxPayConfig` 配置,无需特殊设置: + +```java +WxPayConfig config = new WxPayConfig(); +config.setAppId("你的AppId"); +config.setMchId("你的境外商户号"); +config.setMchKey("你的商户密钥"); +config.setNotifyUrl("https://your-domain.com/notify"); + +// V3相关配置 +config.setPrivateKeyPath("你的私钥文件路径"); +config.setCertSerialNo("你的商户证书序列号"); +config.setApiV3Key("你的APIv3密钥"); +``` + +**注意**: 境外支付会自动使用 `https://apihk.mch.weixin.qq.com` 作为基础URL,无需手动设置。 + +## 兼容性 + +- 完全向后兼容,不影响现有的国内支付功能 +- 使用相同的配置类和结果类 +- 遵循现有的代码风格和架构模式 + +## 参考文档 + +- [境外微信支付文档](https://pay.weixin.qq.com/doc/global/v3/zh/4013014223) +- [原始Issue #3618](https://github.com/binarywang/WxJava/issues/3618) \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java new file mode 100644 index 0000000000..296d3a8646 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java @@ -0,0 +1,57 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + *
+ * 境外微信支付统一下单请求参数对象. + * 参考文档:https://pay.weixin.qq.com/doc/global/v3/zh/4013014223 + *+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class WxPayUnifiedOrderV3GlobalRequest extends WxPayUnifiedOrderV3Request implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+ * 字段名:交易类型 + * 变量名:trade_type + * 是否必填:是 + * 类型:string[1,16] + * 描述: + * 交易类型,取值如下: + * JSAPI--JSAPI支付 + * NATIVE--Native支付 + * APP--APP支付 + * H5--H5支付 + * 示例值:JSAPI + *+ */ + @SerializedName(value = "trade_type") + private String tradeType; + + /** + *
+ * 字段名:商户类目 + * 变量名:merchant_category_code + * 是否必填:是 + * 类型:string[1,32] + * 描述: + * 商户类目,境外商户必填 + * 示例值:5812 + *+ */ + @SerializedName(value = "merchant_category_code") + private String merchantCategoryCode; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java new file mode 100644 index 0000000000..fd33b240f1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java @@ -0,0 +1,36 @@ +package com.github.binarywang.wxpay.bean.result.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 境外微信支付方式 + * Overseas WeChat Pay trade types with global endpoints + * + * @author Binary Wang + */ +@Getter +@AllArgsConstructor +public enum GlobalTradeTypeEnum { + /** + * APP + */ + APP("/global/v3/transactions/app"), + /** + * JSAPI 或 小程序 + */ + JSAPI("/global/v3/transactions/jsapi"), + /** + * NATIVE + */ + NATIVE("/global/v3/transactions/native"), + /** + * H5 + */ + H5("/global/v3/transactions/h5"); + + /** + * 境外下单url + */ + private final String url; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 8ceac2b6ba..c73fb843e8 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -6,6 +6,7 @@ import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.constant.WxPayConstants; @@ -640,6 +641,17 @@ public interface WxPayService { */
* 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 0df3530a31..f32083a632 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -11,6 +11,7 @@
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.config.WxPayConfigHolder;
@@ -746,6 +747,14 @@ public T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOr
return result.getPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey());
}
+ @Override
+ public T createOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException {
+ WxPayUnifiedOrderV3Result result = this.unifiedOrderV3Global(tradeType, request);
+ // Convert GlobalTradeTypeEnum to TradeTypeEnum for getPayInfo method
+ TradeTypeEnum domesticTradeType = TradeTypeEnum.valueOf(tradeType.name());
+ return result.getPayInfo(domesticTradeType, request.getAppid(), request.getMchid(), this.getConfig().getPrivateKey());
+ }
+
@Override
public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
if (StringUtils.isBlank(request.getSpAppid())) {
@@ -790,6 +799,28 @@ public WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUn
return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
}
+ @Override
+ public WxPayUnifiedOrderV3Result unifiedOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException {
+ if (StringUtils.isBlank(request.getAppid())) {
+ request.setAppid(this.getConfig().getAppId());
+ }
+ if (StringUtils.isBlank(request.getMchid())) {
+ request.setMchid(this.getConfig().getMchId());
+ }
+ if (StringUtils.isBlank(request.getNotifyUrl())) {
+ request.setNotifyUrl(this.getConfig().getNotifyUrl());
+ }
+ if (StringUtils.isBlank(request.getTradeType())) {
+ request.setTradeType(tradeType.name());
+ }
+
+ // Use global WeChat Pay base URL for overseas payments
+ String globalBaseUrl = "https://apihk.mch.weixin.qq.com";
+ String url = globalBaseUrl + tradeType.getUrl();
+ String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
+ }
+
@Override
public CombineTransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException {
if (StringUtils.isBlank(request.getCombineAppid())) {
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java
new file mode 100644
index 0000000000..c648c8a171
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java
@@ -0,0 +1,89 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3GlobalRequest;
+import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import me.chanjar.weixin.common.util.RandomUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * 境外微信支付测试类
+ *
+ * @author Binary Wang
+ */
+public class BaseWxPayServiceGlobalImplTest {
+
+ private static final Gson GSON = new GsonBuilder().create();
+
+ @Test
+ public void testWxPayUnifiedOrderV3GlobalRequest() {
+ // Test that the new request class has the required fields
+ WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+
+ // Set basic order information
+ String outTradeNo = RandomUtils.getRandomStr();
+ request.setOutTradeNo(outTradeNo);
+ request.setDescription("Test overseas payment");
+ request.setNotifyUrl("https://api.example.com/notify");
+
+ // Set amount
+ WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+ amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+ amount.setTotal(100); // 1 yuan in cents
+ request.setAmount(amount);
+
+ // Set payer
+ WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer();
+ payer.setOpenid("test_openid");
+ request.setPayer(payer);
+
+ // Set the new required fields for global payments
+ request.setTradeType("JSAPI");
+ request.setMerchantCategoryCode("5812"); // Example category code
+
+ // Assert that all fields are properly set
+ assertNotNull(request.getTradeType());
+ assertNotNull(request.getMerchantCategoryCode());
+ assertEquals("JSAPI", request.getTradeType());
+ assertEquals("5812", request.getMerchantCategoryCode());
+ assertEquals(outTradeNo, request.getOutTradeNo());
+ assertEquals("Test overseas payment", request.getDescription());
+ assertEquals(100, request.getAmount().getTotal());
+ assertEquals("test_openid", request.getPayer().getOpenid());
+
+ // Test JSON serialization contains the new fields
+ String json = GSON.toJson(request);
+ assertTrue(json.contains("trade_type"));
+ assertTrue(json.contains("merchant_category_code"));
+ assertTrue(json.contains("JSAPI"));
+ assertTrue(json.contains("5812"));
+ }
+
+ @Test
+ public void testGlobalTradeTypeEnum() {
+ // Test that all trade types have the correct global endpoints
+ assertEquals("/global/v3/transactions/app", GlobalTradeTypeEnum.APP.getUrl());
+ assertEquals("/global/v3/transactions/jsapi", GlobalTradeTypeEnum.JSAPI.getUrl());
+ assertEquals("/global/v3/transactions/native", GlobalTradeTypeEnum.NATIVE.getUrl());
+ assertEquals("/global/v3/transactions/h5", GlobalTradeTypeEnum.H5.getUrl());
+ }
+
+ @Test
+ public void testGlobalTradeTypeEnumValues() {
+ // Test that we have all the main trade types
+ GlobalTradeTypeEnum[] tradeTypes = GlobalTradeTypeEnum.values();
+ assertEquals(4, tradeTypes.length);
+
+ // Test that we can convert between enum name and TradeTypeEnum
+ for (GlobalTradeTypeEnum globalType : tradeTypes) {
+ // This tests that the enum names match between Global and regular TradeTypeEnum
+ String name = globalType.name();
+ assertNotNull(name);
+ assertTrue(name.equals("APP") || name.equals("JSAPI") || name.equals("NATIVE") || name.equals("H5"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java
new file mode 100644
index 0000000000..ccccf9c803
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java
@@ -0,0 +1,153 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3GlobalRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import me.chanjar.weixin.common.util.RandomUtils;
+
+/**
+ * 境外微信支付使用示例
+ * Example usage for overseas WeChat Pay
+ *
+ * @author Binary Wang
+ */
+public class OverseasWxPayExample {
+
+ /**
+ * 境外微信支付JSAPI下单示例
+ * Example for overseas WeChat Pay JSAPI order creation
+ */
+ public void createOverseasJsapiOrder(WxPayService payService) throws WxPayException {
+ // 创建境外支付请求对象
+ WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+
+ // 设置基础订单信息
+ request.setOutTradeNo(RandomUtils.getRandomStr()); // 商户订单号
+ request.setDescription("境外商品购买"); // 商品描述
+ request.setNotifyUrl("https://your-domain.com/notify"); // 支付通知地址
+
+ // 设置金额信息
+ WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+ amount.setCurrency(WxPayConstants.CurrencyType.CNY); // 币种
+ amount.setTotal(100); // 金额,单位为分
+ request.setAmount(amount);
+
+ // 设置支付者信息
+ WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer();
+ payer.setOpenid("用户的openid"); // 用户openid
+ request.setPayer(payer);
+
+ // 设置境外支付必需的参数
+ request.setTradeType("JSAPI"); // 交易类型
+ request.setMerchantCategoryCode("5812"); // 商户类目代码,境外商户必填
+
+ // 可选:设置场景信息
+ WxPayUnifiedOrderV3GlobalRequest.SceneInfo sceneInfo = new WxPayUnifiedOrderV3GlobalRequest.SceneInfo();
+ sceneInfo.setPayerClientIp("用户IP地址");
+ request.setSceneInfo(sceneInfo);
+
+ // 调用境外支付接口
+ WxPayUnifiedOrderV3Result.JsapiResult result = payService.createOrderV3Global(
+ GlobalTradeTypeEnum.JSAPI,
+ request
+ );
+
+ // 返回的result包含前端需要的支付参数
+ System.out.println("支付参数:" + result);
+ }
+
+ /**
+ * 境外微信支付APP下单示例
+ * Example for overseas WeChat Pay APP order creation
+ */
+ public void createOverseasAppOrder(WxPayService payService) throws WxPayException {
+ WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+
+ // 设置基础信息
+ request.setOutTradeNo(RandomUtils.getRandomStr());
+ request.setDescription("境外APP商品购买");
+ request.setNotifyUrl("https://your-domain.com/notify");
+
+ // 设置金额
+ WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+ amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+ amount.setTotal(200); // 2元
+ request.setAmount(amount);
+
+ // APP支付不需要设置payer.openid,但需要设置空的payer对象
+ request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer());
+
+ // 境外支付必需参数
+ request.setTradeType("APP");
+ request.setMerchantCategoryCode("5812");
+
+ // 调用境外APP支付接口
+ WxPayUnifiedOrderV3Result.AppResult result = payService.createOrderV3Global(
+ GlobalTradeTypeEnum.APP,
+ request
+ );
+
+ System.out.println("APP支付参数:" + result);
+ }
+
+ /**
+ * 境外微信支付NATIVE下单示例
+ * Example for overseas WeChat Pay NATIVE order creation
+ */
+ public void createOverseasNativeOrder(WxPayService payService) throws WxPayException {
+ WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+
+ request.setOutTradeNo(RandomUtils.getRandomStr());
+ request.setDescription("境外扫码支付");
+ request.setNotifyUrl("https://your-domain.com/notify");
+
+ // 设置金额
+ WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+ amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+ amount.setTotal(300); // 3元
+ request.setAmount(amount);
+
+ // NATIVE支付不需要设置payer.openid
+ request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer());
+
+ // 境外支付必需参数
+ request.setTradeType("NATIVE");
+ request.setMerchantCategoryCode("5812");
+
+ // 调用境外NATIVE支付接口
+ String result = payService.createOrderV3Global(
+ GlobalTradeTypeEnum.NATIVE,
+ request
+ );
+
+ System.out.println("NATIVE支付二维码链接:" + result);
+ }
+
+ /**
+ * 配置示例
+ * Configuration example
+ */
+ public WxPayConfig createOverseasConfig() {
+ WxPayConfig config = new WxPayConfig();
+
+ // 基础配置
+ config.setAppId("你的AppId");
+ config.setMchId("你的境外商户号");
+ config.setMchKey("你的商户密钥");
+ config.setNotifyUrl("https://your-domain.com/notify");
+
+ // 境外支付使用的是全球API,在代码中会自动使用 https://apihk.mch.weixin.qq.com 作为基础URL
+ // 无需额外设置payBaseUrl,方法内部会自动处理
+
+ // V3相关配置(境外支付也使用V3接口)
+ config.setPrivateKeyPath("你的私钥文件路径");
+ config.setCertSerialNo("你的商户证书序列号");
+ config.setApiV3Key("你的APIv3密钥");
+
+ return config;
+ }
+}
\ No newline at end of file
From db85c0ab8a48799eeddd78ee52544e7c889fd8db Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 4 Oct 2025 01:47:50 +0800
Subject: [PATCH 006/111] =?UTF-8?q?:art:=20=E5=AE=8C=E5=96=84=E5=85=B3?=
=?UTF-8?q?=E4=BA=8E=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0=E5=8A=9F=E8=83=BD?=
=?UTF-8?q?=E7=9A=84=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 7 +++++++
weixin-java-open/README.md | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 42 insertions(+)
diff --git a/README.md b/README.md
index e8c981bbae..12b516c1b7 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,13 @@
- 企业微信:`weixin-java-cp`
- 微信视频号/微信小店:`weixin-java-channel`
+**注意**:
+- **移动应用开发**:如果你的移动应用(iOS/Android App)需要接入微信登录、分享等功能:
+ - 微信登录(网页授权):使用 `weixin-java-open` 模块,在服务端处理 OAuth 授权
+ - 微信支付:使用 `weixin-java-pay` 模块
+ - 客户端集成:需使用微信官方提供的移动端SDK(iOS/Android),本项目为服务端SDK
+- **微信开放平台**(`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理
+
---------------------------------
### 版本说明
diff --git a/weixin-java-open/README.md b/weixin-java-open/README.md
index dd69161849..6ca65dfef3 100644
--- a/weixin-java-open/README.md
+++ b/weixin-java-open/README.md
@@ -1,3 +1,38 @@
+# 微信开放平台模块 (weixin-java-open)
+
+## 模块说明
+
+本模块主要用于**微信第三方平台**的开发,适用于以下场景:
+
+### 适用场景
+1. **第三方平台开发**:作为第三方平台,代替多个公众号或小程序进行管理和开发
+2. **代公众号实现业务**:通过授权代替公众号进行消息管理、素材管理等操作
+3. **代小程序实现业务**:通过授权代替小程序进行代码管理、基本信息设置等操作
+
+### 移动应用开发说明
+
+**如果您要开发移动应用(iOS/Android App)并接入微信功能,请注意:**
+
+- **微信登录**:
+ - 移动应用的微信登录(网页授权)需要在**微信开放平台**(open.weixin.qq.com)创建移动应用
+ - 服务端处理 OAuth 授权时使用本模块 `weixin-java-open`
+ - 移动端需集成微信官方SDK(iOS/Android),本项目仅提供服务端SDK
+
+- **微信支付**:
+ - 使用 `weixin-java-pay` 模块,参考 [微信支付文档](../weixin-java-pay/)
+ - 移动应用支付使用 APP 支付类型(TradeType.APP)
+
+- **微信分享**:
+ - 需集成微信官方移动端SDK,本项目不涉及客户端功能
+
+**参考资料**:
+- [微信开放平台官方文档](https://open.weixin.qq.com/)
+- [移动应用接入指南](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html)
+
+---
+
+## 代码示例
+
消息机制未实现,下面为通知回调中设置的代码部分
以下代码可通过腾讯全网发布测试用例
From 98329b11daca17e48433c7bb7247938f285375f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=8F=E4=BB=98=E5=90=8C=E5=AD=A6?=
<50438539+a810439322@users.noreply.github.com>
Date: Thu, 23 Oct 2025 10:12:23 +0800
Subject: [PATCH 007/111] =?UTF-8?q?:bug:=20#3640=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E4=BD=BF?=
=?UTF-8?q?=E7=94=A8=E8=BF=9E=E6=8E=A5=E6=B1=A0=E5=90=8ESSL=E5=AE=A2?=
=?UTF-8?q?=E6=88=B7=E7=AB=AF=E8=AF=81=E4=B9=A6=E6=9C=AA=E6=AD=A3=E7=A1=AE?=
=?UTF-8?q?=E5=8F=91=E9=80=81=E5=AF=BC=E8=87=B4=E9=80=80=E6=AC=BE=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/config/WxPayConfig.java | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index ee44780590..43da17f048 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -18,6 +18,10 @@
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@@ -579,7 +583,20 @@ public CloseableHttpClient initSslHttpClient() throws WxPayException {
}
// 创建支持SSL的连接池管理器
- PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
+ SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
+ sslContext,
+ new DefaultHostnameVerifier()
+ );
+
+ Registry socketFactoryRegistry = RegistryBuilder
+ .create()
+ .register("https", sslsf)
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .build();
+ PoolingHttpClientConnectionManager connectionManager =
+ new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+
+ // PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(this.maxConnTotal);
connectionManager.setDefaultMaxPerRoute(this.maxConnPerRoute);
From b9b4f004387a644d665d33f2366c1b2b1982a3c3 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Thu, 23 Oct 2025 11:00:01 +0800
Subject: [PATCH 008/111] :memo: Revise development instructions with Chinese
translations
---
.github/copilot-instructions.md | 258 ++++++++++++++++----------------
1 file changed, 131 insertions(+), 127 deletions(-)
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index cec0d76c6b..cad29d96d9 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,198 +1,202 @@
-# WxJava - WeChat Java SDK Development Instructions
+# Copilot Instruction
+请始终使用中文生成 Pull Request 的标题、描述和提交信息
-WxJava is a comprehensive WeChat Java SDK supporting multiple WeChat platforms including Official Accounts (公众号), Mini Programs (小程序), WeChat Pay (微信支付), Enterprise WeChat (企业微信), Open Platform (开放平台), and Channel/Video (视频号). This is a Maven multi-module project with Spring Boot and Solon framework integrations.
-**ALWAYS reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the information here.**
+# WxJava - 微信 Java SDK 开发说明
-## Working Effectively
+WxJava 是一个支持多种微信平台的完整 Java SDK,包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。
-### Prerequisites and Environment Setup
-- **Java Requirements**: JDK 8+ required (project uses Java 8 as minimum target)
-- **Maven**: Maven 3.6+ recommended (Maven 3.9.11 validated)
-- **IDE**: IntelliJ IDEA recommended (project optimized for IDEA)
+**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。**
-### Bootstrap, Build, and Validate
-Execute these commands in sequence after cloning:
+## 高效开发指南
+
+### 前置条件与环境准备
+- **Java 要求**:JDK 8+(项目最低目标为 Java 8)
+- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11)
+- **IDE**:推荐使用 IntelliJ IDEA(项目针对 IDEA 优化)
+
+### 引导、构建与校验
+克隆仓库后按顺序执行以下命令:
```bash
-# 1. Basic compilation (NEVER CANCEL - takes 4-5 minutes)
+# 1. 基础编译(请勿中断 - 约需 4-5 分钟)
mvn clean compile -DskipTests=true --no-transfer-progress
-# Timeout: Set 8+ minutes. Actual time: ~4 minutes
+# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟
-# 2. Full package build (NEVER CANCEL - takes 2-3 minutes)
+# 2. 完整打包(请勿中断 - 约需 2-3 分钟)
mvn clean package -DskipTests=true --no-transfer-progress
-# Timeout: Set 5+ minutes. Actual time: ~2 minutes
+# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟
-# 3. Code quality validation (NEVER CANCEL - takes 45-60 seconds)
+# 3. 代码质量校验(请勿中断 - 约需 45-60 秒)
mvn checkstyle:check --no-transfer-progress
-# Timeout: Set 3+ minutes. Actual time: ~50 seconds
+# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒
```
-**CRITICAL TIMING NOTES:**
-- **NEVER CANCEL** any Maven build command
-- Compilation phase takes longest (~4 minutes) due to 34 modules
-- Full builds are faster on subsequent runs due to incremental compilation
-- Always use `--no-transfer-progress` to reduce log noise
-
-### Testing Structure
-- **Test Framework**: TestNG (not JUnit)
-- **Test Files**: 298 test files across all modules
-- **Default Behavior**: Tests are DISABLED by default in pom.xml (`true `)
-- **Test Configuration**: Tests require external WeChat API credentials via test-config.xml files
-- **DO NOT** attempt to run tests without proper WeChat API credentials as they will fail
-
-## Project Structure and Navigation
-
-### Core SDK Modules (Main Development Areas)
-- `weixin-java-common/` - Common utilities and base classes (most important)
-- `weixin-java-mp/` - WeChat Official Account SDK (公众号)
-- `weixin-java-pay/` - WeChat Pay SDK (微信支付)
-- `weixin-java-miniapp/` - Mini Program SDK (小程序)
-- `weixin-java-cp/` - Enterprise WeChat SDK (企业微信)
-- `weixin-java-open/` - Open Platform SDK (开放平台)
-- `weixin-java-channel/` - Channel/Video SDK (视频号)
-- `weixin-java-qidian/` - Qidian SDK (企点)
-
-### Framework Integration Modules
-- `spring-boot-starters/` - Spring Boot auto-configuration starters
-- `solon-plugins/` - Solon framework plugins
-- `weixin-graal/` - GraalVM native image support
-
-### Configuration and Quality
-- `quality-checks/google_checks.xml` - Checkstyle configuration
-- `.editorconfig` - Code formatting rules (2 spaces = 1 tab)
-- `pom.xml` - Root Maven configuration
-
-## Development Workflow
-
-### Making Code Changes
-1. **Always build first** to establish clean baseline:
+重要时间说明:
+- 绝对不要中断任意 Maven 构建命令
+- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块
+- 后续构建会更快,因为存在增量编译
+- 始终使用 `--no-transfer-progress` 以减少日志噪音
+
+### 测试结构
+- **测试框架**:TestNG(非 JUnit)
+- **测试文件**:共有 298 个测试文件
+- **默认行为**:pom.xml 中默认禁用测试(`true `)
+- **测试配置**:测试需要通过 test-config.xml 提供真实的微信 API 凭据
+- **注意**:没有真实微信 API 凭据请不要尝试运行测试,测试将会失败
+
+## 项目结构与导航
+
+### 核心 SDK 模块(主要开发区)
+- `weixin-java-common/` - 通用工具与基础类(最重要)
+- `weixin-java-mp/` - 公众号 SDK
+- `weixin-java-pay/` - 微信支付 SDK
+- `weixin-java-miniapp/` - 小程序 SDK
+- `weixin-java-cp/` - 企业微信 SDK
+- `weixin-java-open/` - 开放平台 SDK
+- `weixin-java-channel/` - 视频号 / Channel SDK
+- `weixin-java-qidian/` - 企点 SDK
+
+### 框架集成模块
+- `spring-boot-starters/` - Spring Boot 自动配置 starter
+- `solon-plugins/` - Solon 框架插件
+- `weixin-graal/` - GraalVM 本地镜像支持
+
+### 配置与质量控制
+- `quality-checks/google_checks.xml` - Checkstyle 配置
+- `.editorconfig` - 代码格式规则(2 个空格等于 1 个制表)
+- `pom.xml` - 根级 Maven 配置
+
+## 开发工作流
+
+### 修改代码的流程
+1. 修改前务必先构建以建立干净基线:
```bash
mvn clean compile --no-transfer-progress
```
-2. **Follow code style** (enforced by checkstyle):
- - Use 2 spaces for indentation (not tabs)
- - Follow Google Java Style Guide
- - Install EditorConfig plugin in your IDE
+2. 遵循代码风格(由 checkstyle 强制):
+ - 缩进使用 2 个空格(不要用制表符)
+ - 遵循 Google Java 风格指南
+ - 在 IDE 中安装 EditorConfig 插件
-3. **Validate changes incrementally**:
+3. 增量验证修改:
```bash
- # After each change:
+ # 每次修改后运行:
mvn compile --no-transfer-progress
- mvn checkstyle:check --no-transfer-progress
+ mvn checkstyle:check --no-transfer-progress
```
-### Before Submitting Changes
-**ALWAYS run these validation steps in sequence:**
+### 提交修改前的必须校验
+请务必按顺序完成以下校验步骤:
-1. **Code Style Validation**:
+1. 代码风格校验:
```bash
mvn checkstyle:check --no-transfer-progress
- # Must pass - takes ~50 seconds
+ # 必须通过 - 约需 50 秒
```
-2. **Full Clean Build**:
+2. 完整清理构建:
```bash
mvn clean package -DskipTests=true --no-transfer-progress
- # Must succeed - takes ~2 minutes
+ # 必须成功 - 约需 2 分钟
```
-3. **Documentation**: Update javadoc for public methods and classes
-4. **Contribution Guidelines**: Follow `CONTRIBUTING.md` - PRs must target `develop` branch
+3. 文档:为公共方法和类补充或更新 javadoc
+4. 贡献规范:遵循 `CONTRIBUTING.md`,Pull Request 必须以 `develop` 分支为目标
-## Module Dependencies and Build Order
+## 模块依赖与构建顺序
-### Core Module Dependencies (Build Order)
-1. `weixin-graal` (GraalVM support)
-2. `weixin-java-common` (foundation for all other modules)
-3. Core SDK modules (mp, pay, miniapp, cp, open, channel, qidian)
-4. Framework integrations (spring-boot-starters, solon-plugins)
+### 核心模块依赖(构建顺序)
+1. `weixin-graal`(GraalVM 支持)
+2. `weixin-java-common`(所有模块的基础)
+3. 核心 SDK 模块(mp、pay、miniapp、cp、open、channel、qidian)
+4. 框架集成(spring-boot-starters、solon-plugins)
-### Key Relationship Patterns
-- All SDK modules depend on `weixin-java-common`
-- Spring Boot starters depend on corresponding SDK modules
-- Solon plugins follow same pattern as Spring Boot starters
-- Each module has both single and multi-account configurations
+### 主要关系模式
+- 所有 SDK 模块都依赖于 `weixin-java-common`
+- Spring Boot starters 依赖对应的 SDK 模块
+- Solon 插件遵循与 Spring Boot starters 相同的依赖模式
+- 每个模块都有单账号与多账号配置支持
-## Common Tasks and Commands
+## 常见任务与命令
-### Validate Specific Module
+### 验证指定模块
```bash
-# Build single module (replace 'weixin-java-mp' with target module):
+# 构建单个模块(将 'weixin-java-mp' 替换为目标模块):
cd weixin-java-mp
mvn clean compile --no-transfer-progress
```
-### Check Dependencies
+### 检查依赖
```bash
-# Analyze dependencies:
+# 分析依赖树:
mvn dependency:tree --no-transfer-progress
-# Check for dependency updates:
+# 检查依赖更新:
./others/check-dependency-updates.sh
```
-### Release and Publishing
+### 发布与发布准备
```bash
-# Version check:
+# 版本检查:
mvn versions:display-property-updates --no-transfer-progress
-# Deploy (requires credentials):
+# 部署(需要凭据):
mvn clean deploy -P release --no-transfer-progress
```
-## Important Files and Locations
+## 重要文件与位置
-### Configuration Files
-- `pom.xml` - Root Maven configuration with dependency management
-- `quality-checks/google_checks.xml` - Checkstyle rules
-- `.editorconfig` - IDE formatting configuration
-- `.github/workflows/maven-publish.yml` - CI/CD pipeline
+### 配置文件
+- `pom.xml` - 根级 Maven 配置与依赖管理
+- `quality-checks/google_checks.xml` - Checkstyle 规则
+- `.editorconfig` - IDE 格式化配置
+- `.github/workflows/maven-publish.yml` - CI/CD 工作流
-### Documentation
-- `README.md` - Project overview and usage (Chinese)
-- `CONTRIBUTING.md` - Development contribution guidelines
-- `demo.md` - Links to demo projects and examples
-- Each module has dedicated documentation and examples
+### 文档
+- `README.md` - 项目概览与使用说明(中文)
+- `CONTRIBUTING.md` - 贡献指南
+- `demo.md` - 示例项目与演示链接
+- 每个模块均有单独的文档与示例
-### Test Resources
-- `*/src/test/resources/test-config.sample.xml` - Template for test configuration
-- Tests require real WeChat API credentials to run
+### 测试资源
+- `*/src/test/resources/test-config.sample.xml` - 测试配置模板
+- 测试运行需要真实的微信 API 凭据
-## SDK Usage Patterns
+## SDK 使用模式
-### Maven Dependency Usage
+### Maven 依赖示例
```xml
com.github.binarywang
- weixin-java-mp
+ weixin-java-mp
4.7.0
```
-### Common Development Areas
-- **API Client Implementation**: Located in `*/service/impl/` directories
-- **Model Classes**: Located in `*/bean/` directories
-- **Configuration**: Located in `*/config/` directories
-- **Utilities**: Located in `*/util/` directories in weixin-java-common
+### 常见开发区域
+- **API 客户端实现**:位于 `*/service/impl/` 目录
+- **模型类**:位于 `*/bean/` 目录
+- **配置**:位于 `*/config/` 目录
+- **工具类**:位于 `weixin-java-common` 的 `*/util/` 目录
-## Troubleshooting
+## 故障排查
-### Build Issues
-- **OutOfMemoryError**: Increase Maven memory: `export MAVEN_OPTS="-Xmx2g"`
-- **Compilation Failures**: Usually dependency issues - run `mvn clean` first
-- **Checkstyle Failures**: Check `.editorconfig` settings in IDE
+### 构建问题
+- **OutOfMemoryError**:增加 Maven 内存:`export MAVEN_OPTS="-Xmx2g"`
+- **编译失败**:通常为依赖问题 - 先执行 `mvn clean`
+- **Checkstyle 失败**:检查 IDE 的 `.editorconfig` 设置
-### Common Gotchas
-- **Tests Always Skip**: This is normal - tests require WeChat API credentials
-- **Multi-Module Changes**: Always build from root, not individual modules
-- **Branch Target**: PRs must target `develop` branch, not `master`/`release`
+### 常见陷阱
+- **测试默认跳过**:这是正常现象 — 测试需要微信 API 凭据
+- **多模块变更**:总是在仓库根目录构建,而不是单独模块
+- **分支目标**:Pull Request 必须以 `develop` 分支为目标,而不是 `master` 或 `release`
-## Performance Notes
-- **First Build**: Takes 4-5 minutes due to dependency downloads
-- **Incremental Builds**: Much faster (~30-60 seconds)
-- **Checkstyle**: Runs quickly (~50 seconds) and should be run frequently
-- **IDE Performance**: Project uses Lombok - ensure annotation processing is enabled
+## 性能说明
+- **首次构建**:由于依赖下载,耗时 4-5 分钟
+- **增量构建**:通常更快(约 30-60 秒)
+- **Checkstyle**:运行迅速(约 50 秒),应当经常运行
+- **IDE 性能**:项目使用 Lombok,请确保启用注解处理
-Remember: This is a SDK library project, not a runnable application. Changes should focus on API functionality, not application behavior.
\ No newline at end of file
+注意:本项目为 SDK 库项目,而非可运行应用。修改应以 API 功能为主,不要改动应用级行为。
From b26c7867979ee1c9d3eebabd5c6babae6796d499 Mon Sep 17 00:00:00 2001
From: accept mediocrity <165350377+AcceptMediocrity@users.noreply.github.com>
Date: Tue, 28 Oct 2025 11:51:26 +0800
Subject: [PATCH 009/111] =?UTF-8?q?:art:=20#3745=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E8=AF=B7?=
=?UTF-8?q?=E6=B1=82=E5=BE=AE=E4=BF=A1=E4=BB=BF=E7=9C=9F=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=97=B6=E9=AA=8C=E7=AD=BE=E5=AF=86=E9=92=A5?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=9A=84=20Content-Type=20=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/service/WxPayService.java | 14 +++++
.../service/impl/BaseWxPayServiceImpl.java | 3 +-
.../impl/WxPayServiceApacheHttpImpl.java | 47 ++++++++++++++++-
.../impl/WxPayServiceHttpComponentsImpl.java | 44 ++++++++++++++++
.../impl/WxPayServiceJoddHttpImpl.java | 52 +++++++++++++++++++
5 files changed, 157 insertions(+), 3 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index c73fb843e8..4ee5226d3d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -109,6 +109,19 @@ public interface WxPayService {
*/
String post(String url, String requestStr, boolean useKey) throws WxPayException;
+
+ /**
+ * 发送post请求,得到响应字符串.
+ *
+ * @param url 请求地址
+ * @param requestStr 请求信息
+ * @param useKey 是否使用证书
+ * @param mimeType Content-Type请求头
+ * @return 返回请求结果字符串 string
+ * @throws WxPayException the wx pay exception
+ */
+ String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException;
+
/**
* 发送post请求,得到响应字符串.
*
@@ -1457,6 +1470,7 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
* 是否需要证书: 否
* 请求方式: POST
* 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1
+ * 注意: 微信暂不支持api v3
*
*
* @return the sandbox sign key
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index f32083a632..3884881b8d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -35,6 +35,7 @@
import me.chanjar.weixin.common.error.WxRuntimeException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
+import org.apache.http.entity.ContentType;
import java.io.File;
import java.io.IOException;
@@ -1262,7 +1263,7 @@ public String getSandboxSignKey() throws WxPayException {
request.checkAndSign(this.getConfig());
String url = "https://api.mch.weixin.qq.com/xdc/apiv2getsignkey/sign/getsignkey";
- String responseContent = this.post(url, request.toXML(), false);
+ String responseContent = this.post(url, request.toXML(), false, ContentType.APPLICATION_XML.getMimeType());
WxPaySandboxSignKeyResult result = BaseWxPayResult.fromXML(responseContent, WxPaySandboxSignKeyResult.class);
result.checkResult(this, request.getSignType(), true);
return result.getSandboxSignKey();
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
index 977a2856fe..96454e5c08 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
@@ -54,7 +54,7 @@ public byte[] postForBytes(String url, String requestStr, boolean useKey) throws
try {
HttpPost httpPost = this.createHttpPost(url, requestStr);
CloseableHttpClient httpClient = this.createHttpClient(useKey);
-
+
// 使用连接池的客户端,不需要手动关闭
final byte[] bytes = httpClient.execute(httpPost, ByteArrayResponseHandler.INSTANCE);
final String responseData = Base64.getEncoder().encodeToString(bytes);
@@ -73,7 +73,33 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx
try {
HttpPost httpPost = this.createHttpPost(url, requestStr);
CloseableHttpClient httpClient = this.createHttpClient(useKey);
-
+
+ // 使用连接池的客户端,不需要手动关闭
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+ this.logRequestAndResponse(url, requestStr, responseString);
+ if (this.getConfig().isIfSaveApiData()) {
+ wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
+ }
+ return responseString;
+ } finally {
+ httpPost.releaseConnection();
+ }
+ } catch (Exception e) {
+ this.logError(url, requestStr, e);
+ if (this.getConfig().isIfSaveApiData()) {
+ wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
+ }
+ throw new WxPayException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException {
+ try {
+ HttpPost httpPost = this.createHttpPost(url, requestStr, mimeType);
+ CloseableHttpClient httpClient = this.createHttpClient(useKey);
+
// 使用连接池的客户端,不需要手动关闭
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
@@ -306,6 +332,10 @@ private static StringEntity createEntry(String requestStr) {
//return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
}
+ private static StringEntity createEntry(String requestStr, String mimeType) {
+ return new StringEntity(requestStr, ContentType.create(mimeType, StandardCharsets.UTF_8));
+ //return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
+ }
private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (useKey) {
@@ -348,6 +378,19 @@ private HttpPost createHttpPost(String url, String requestStr) {
return httpPost;
}
+ private HttpPost createHttpPost(String url, String requestStr, String mimeType) throws WxPayException {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(createEntry(requestStr, mimeType));
+
+ httpPost.setConfig(RequestConfig.custom()
+ .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
+ .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
+ .setSocketTimeout(this.getConfig().getHttpTimeout())
+ .build());
+
+ return httpPost;
+ }
+
private void initSSLContext(HttpClientBuilder httpClientBuilder) throws WxPayException {
SSLContext sslContext = this.getConfig().getSslContext();
if (null == sslContext) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
index 1c558f711b..5c21a06a8e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
@@ -91,6 +91,32 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx
}
}
+ @Override
+ public String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException {
+ try {
+ HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey);
+ HttpPost httpPost = this.createHttpPost(url, requestStr, mimeType);
+ try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+ this.logRequestAndResponse(url, requestStr, responseString);
+ if (this.getConfig().isIfSaveApiData()) {
+ wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
+ }
+ return responseString;
+ }
+ } finally {
+ httpPost.releaseConnection();
+ }
+ } catch (Exception e) {
+ this.logError(url, requestStr, e);
+ if (this.getConfig().isIfSaveApiData()) {
+ wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
+ }
+ throw new WxPayException(e.getMessage(), e);
+ }
+ }
+
@Override
public String postV3(String url, String requestStr) throws WxPayException {
HttpPost httpPost = this.createHttpPost(url, requestStr);
@@ -283,6 +309,11 @@ private static StringEntity createEntry(String requestStr) {
//return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
}
+ private static StringEntity createEntry(String requestStr, String mimeType) {
+ return new StringEntity(requestStr, ContentType.create(mimeType, StandardCharsets.UTF_8));
+ //return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
+ }
+
private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (useKey) {
@@ -325,6 +356,19 @@ private HttpPost createHttpPost(String url, String requestStr) {
return httpPost;
}
+ private HttpPost createHttpPost(String url, String requestStr, String mimeType) throws WxPayException {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(createEntry(requestStr, mimeType));
+
+ httpPost.setConfig(RequestConfig.custom()
+ .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
+ .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
+ .setSocketTimeout(this.getConfig().getHttpTimeout())
+ .build());
+
+ return httpPost;
+ }
+
private void initSSLContext(HttpClientBuilder httpClientBuilder) throws WxPayException {
SSLContext sslContext = this.getConfig().getSslContext();
if (null == sslContext) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
index 7c2f1e82c0..6c8bcec899 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
@@ -63,6 +63,24 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx
}
}
+ @Override
+ public String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException {
+ try {
+ HttpRequest request = this.buildHttpRequest(url, requestStr, useKey, mimeType);
+ String responseString = this.getResponseString(request.send());
+
+ log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
+ if (this.getConfig().isIfSaveApiData()) {
+ wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
+ }
+ return responseString;
+ } catch (Exception e) {
+ log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+ wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
+ throw new WxPayException(e.getMessage(), e);
+ }
+ }
+
@Override
public String postV3(String url, String requestStr) throws WxPayException {
return null;
@@ -146,6 +164,40 @@ private HttpRequest buildHttpRequest(String url, String requestStr, boolean useK
return request;
}
+ private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException {
+ HttpRequest request = HttpRequest
+ .post(url)
+ .contentType(mimeType)
+ .timeout(this.getConfig().getHttpTimeout())
+ .connectionTimeout(this.getConfig().getHttpConnectionTimeout())
+ .bodyText(requestStr);
+
+ if (useKey) {
+ SSLContext sslContext = this.getConfig().getSslContext();
+ if (null == sslContext) {
+ sslContext = this.getConfig().initSSLContext();
+ }
+ final SSLSocketHttpConnectionProvider provider = new SSLSocketHttpConnectionProvider(sslContext);
+ request.withConnectionProvider(provider);
+ }
+
+ if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) {
+ if (StringUtils.isEmpty(this.getConfig().getHttpProxyUsername())) {
+ this.getConfig().setHttpProxyUsername("whatever");
+ }
+
+ ProxyInfo httpProxy = new ProxyInfo(ProxyType.HTTP, this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort(),
+ this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword());
+ HttpConnectionProvider provider = request.connectionProvider();
+ if (null == provider) {
+ provider = new SocketHttpConnectionProvider();
+ }
+ provider.useProxy(httpProxy);
+ request.withConnectionProvider(provider);
+ }
+ return request;
+ }
+
private String getResponseString(HttpResponse response) throws WxPayException {
try {
log.debug("【微信服务器响应头信息】:\n{}", response.toString(false));
From de4848b2f767a98ab8e70686c760de8c5613bf18 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Tue, 28 Oct 2025 11:54:06 +0800
Subject: [PATCH 010/111] =?UTF-8?q?:art:=20=E8=A7=84=E8=8C=83mp=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97=E7=9A=84javadoc=E6=8E=A5=E5=8F=A3=E6=B3=A8=E9=87=8A?=
=?UTF-8?q?=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/mp/api/WxMpAiOpenService.java | 162 +++---
.../weixin/mp/api/WxMpCardService.java | 497 ++++++++++++++----
.../weixin/mp/api/WxMpKefuService.java | 116 ++--
.../weixin/mp/api/WxMpMassMessageService.java | 206 ++++++--
.../weixin/mp/api/WxMpMaterialService.java | 77 ++-
.../weixin/mp/api/WxMpMenuService.java | 32 +-
.../weixin/mp/api/WxMpQrcodeService.java | 33 +-
.../me/chanjar/weixin/mp/api/WxMpService.java | 401 +++++++-------
.../weixin/mp/api/WxMpTemplateMsgService.java | 253 +++++----
.../weixin/mp/api/WxMpUserService.java | 48 +-
.../weixin/mp/api/WxMpUserTagService.java | 183 +++++--
.../mp/api/impl/WxMpAiOpenServiceImpl.java | 76 ++-
.../mp/api/impl/WxMpUserTagServiceImpl.java | 8 +-
13 files changed, 1360 insertions(+), 732 deletions(-)
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java
index 07bc1e52e1..ad995bba2d 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java
@@ -6,74 +6,112 @@
import me.chanjar.weixin.mp.enums.AiLangType;
/**
- * - * 微信AI开放接口(语音识别,微信翻译). - * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712282KzWVE - * Created by BinaryWang on 2018/6/9. - *+ * 微信AI开放接口(语音识别,微信翻译) + *
+ * 提供微信AI相关的功能,包括语音识别、微信翻译等。 + * 支持上传语音文件进行语音识别,以及文本翻译功能。 + *
+ *+ * 详情请见:微信AI开放接口 + *
+ * Created by BinaryWang on 2018/6/9. * * @author Binary Wang */ public interface WxMpAiOpenService { - /** - *- * 提交语音. - * http请求方式: POST - * http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN - *- * - * @param voiceId 语音唯一标识 - * @param lang 语言,zh_CN 或 en_US,默认中文 - * @param voiceFile 语音文件 - * @throws WxErrorException the wx error exception - */ - void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; + /** + *
+ * 提交语音 + *+ * + * @param voiceId 语音唯一标识 + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceFile 语音文件 + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 获取语音识别结果. - * 接口调用请求说明 - * - * http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN - * 请注意,添加完文件之后10s内调用这个接口 - * - *- * - * @param voiceId 语音唯一标识 - * @param lang 语言,zh_CN 或 en_US,默认中文 - * @return the string - * @throws WxErrorException the wx error exception - */ - String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException; + /** + *
+ * 获取语音识别结果 + * 请注意,添加完文件之后10s内调用这个接口 + *+ * + * @param voiceId 语音唯一标识 + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @return 语音识别结果文本 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 识别指定语音文件内容 + * 此方法揉合了前两两个方法:uploadVoice 和 queryRecognitionResult + *+ * + * @param voiceId 语音唯一标识 + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceFile 语音文件 + * @return 语音识别结果文本 + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 微信翻译. - * 接口调用请求说明 - * - * http请求方式: POST - * http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx<o=xxx - * - *- * - * @param langFrom 源语言,zh_CN 或 en_US - * @param langTo 目标语言,zh_CN 或 en_US - * @param content 要翻译的文本内容 - * @return the string - * @throws WxErrorException the wx error exception - */ - String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; + /** + *
+ * 微信翻译 + *+ * + * @param langFrom 源语言,zh_CN 或 en_US + * @param langTo 目标语言,zh_CN 或 en_US + * @param content 要翻译的文本内容 + * @return 翻译结果文本 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 提供微信卡券的创建、查询、核销、管理等功能。 + * 支持卡券API签名生成、卡券Code解码、卡券核销、库存管理等功能。 + *
+ *+ * 详情请见:卡券开发文档 + *
* * @author YuJian(mgcnrx11 @ hotmail.com) on 01/11/2016 * @author yuanqixun 2018-08-29 */ public interface WxMpCardService { /** - * 得到WxMpService. + *+ * 获取WxMpService实例 + ** - * @return WxMpService wx mp service + * @return WxMpService实例 */ WxMpService getWxMpService(); /** - * 获得卡券api_ticket,不强制刷新卡券api_ticket. + *
+ * 获得卡券api_ticket,不强制刷新卡券api_ticket + ** - * @return 卡券api_ticket card api ticket - * @throws WxErrorException 异常 - * @see #getCardApiTicket(boolean) #getCardApiTicket(boolean)#getCardApiTicket(boolean) + * @return 卡券api_ticket + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 获得卡券api_ticket.
+ * 获得卡券api_ticket
* 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
- *
- * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94.9F.E6.88.90.E7.AE.97.E6.B3.95
*
*
- * @param forceRefresh 强制刷新
- * @return 卡券api_ticket card api ticket
- * @throws WxErrorException 异常
+ * @param forceRefresh 强制刷新,如果为true则强制刷新api_ticket
+ * @return 卡券api_ticket
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 创建调用卡券api时所需要的签名.
- *
- * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
- * .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
- * .9F.E6.88.90.E7.AE.97.E6.B3.95
+ * 创建调用卡券api时所需要的签名
*
*
- * @param optionalSignParam 参与签名的参数数组。可以为下列字段:app_id, card_id, card_type, code, openid, location_id 注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空
- * @return 卡券Api签名对象 wx card api signature
- * @throws WxErrorException 异常
+ * @param optionalSignParam 参与签名的参数数组。可以为下列字段:app_id, card_id, card_type, code, openid, location_id
+ * 注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空
+ * @return 卡券Api签名对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ * + * 卡券Code解码 + ** * @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得 - * @return 解密后的Code string - * @throws WxErrorException 异常 + * @return 解密后的Code + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 卡券Code查询 + ** * @param cardId 卡券ID代表一类卡券 * @param code 单张卡券的唯一标准 * @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 - * @return WxMpCardResult对象 wx mp card result - * @throws WxErrorException 异常 + * @return WxMpCardResult对象,包含卡券查询结果信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
* 卡券Code核销。核销失败会抛出异常
+ *
*
* @param code 单张卡券的唯一标准
- * @return 调用返回的JSON字符串 。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
- * @throws WxErrorException 异常
+ * @return 调用返回的JSON字符串,可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ * + * 卡券Code核销。核销失败会抛出异常 + ** * @param code 单张卡券的唯一标准 * @param cardId 当自定义Code卡券时需要传入card_id - * @return 调用返回的JSON字符串 。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 - * @throws WxErrorException 异常 + * @return 调用返回的JSON字符串,可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 卡券Mark接口
* 开发者在帮助消费者核销卡券之前,必须帮助先将此code(卡券串码)与一个openid绑定(即mark住),
* 才能进一步调用核销接口,否则报错。
+ *
*
* @param code 卡券的code码
* @param cardId 卡券的ID
* @param openId 用券用户的openid
* @param isMark 是否要mark(占用)这个code,填写true或者false,表示占用或解除占用
- * @throws WxErrorException 异常
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ * + * 查看卡券详情接口 + ** * @param cardId 卡券的ID - * @return 返回的卡券详情JSON字符串
+ * 添加测试白名单 + ** * @param openid 用户的openid - * @return string string - * @throws WxErrorException 异常 + * @return 操作结果字符串 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 创建卡券 + ** - * @param cardCreateMessage 请求 - * @return result wx mp card create result - * @throws WxErrorException 异常 + * @param cardCreateRequest 卡券创建请求对象 + * @return 卡券创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 创建卡券二维码 + ** * @param cardId 卡券编号 * @param outerStr 二维码标识 - * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result - * @throws WxErrorException 异常 + * @return 卡券二维码创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 创建卡券二维码 + ** * @param cardId 卡券编号 - * @param outerStr 二维码标识 + * @param outerStr 用户首次领卡时,会通过 领取事件推送 给商户; 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源 * @param expiresIn 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效 - * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result - * @throws WxErrorException 异常 + * @return 卡券二维码创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 创建卡券二维码 + ** * @param cardId 卡券编号 * @param outerStr 用户首次领卡时,会通过 领取事件推送 给商户; 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源 * @param expiresIn 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效 - * @param openid 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写。 - * @param code 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。 - * @param isUniqueCode 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,卡券须通过审核且库存不为0。 - * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result - * @throws WxErrorException 异常 + * @param openid 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写 + * @param code 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写 + * @param isUniqueCode 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,卡券须通过审核且库存不为0 + * @return 卡券二维码创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 创建卡券货架 + ** * @param createRequest 货架创建参数 - * @return WxMpCardLandingPageCreateResult wx mp card landing page create result - * @throws WxErrorException 异常 + * @return 货架创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 将用户的卡券设置为失效状态 + ** * @param cardId 卡券编号 * @param code 用户会员卡号 * @param reason 设置为失效的原因 - * @return result string - * @throws WxErrorException 异常 + * @return 操作结果字符串 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 删除卡券接口 + ** * @param cardId 卡券id - * @return 删除结果 wx mp card delete result - * @throws WxErrorException 异常 + * @return 删除结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
* 导入自定义code(仅对自定义code商户)
+ *
*
* @param cardId 卡券id
- * @param codeList 需导入微信卡券后台的自定义code,上限为100个。
- * @return the wx mp card code deposit result
- * @throws WxErrorException the wx error exception
+ * @param codeList 需导入微信卡券后台的自定义code,上限为100个
+ * @return 导入结果对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 查询导入code数目接口
+ *
*
* @param cardId 卡券id
- * @return the wx mp card code deposit count result
- * @throws WxErrorException the wx error exception
+ * @return 查询结果对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 核查code接口
+ *
*
* @param cardId 卡券id
* @param codeList 已经微信卡券后台的自定义code,上限为100个
- * @return the wx mp card code checkcode result
- * @throws WxErrorException the wx error exception
+ * @return 核查结果对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 图文消息群发卡券获取内嵌html
+ *
*
* @param cardId 卡券id
- * @return the wx mp card mpnews gethtml result
- * @throws WxErrorException the wx error exception
+ * @return HTML获取结果对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 修改库存接口
- * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Managing_Coupons_Vouchers_and_Cards.html#5
+ *
*
* @param cardId 卡券ID
* @param changeValue 库存变更值,负值为减少库存
- * @throws WxErrorException the wx error exception
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 更改Code接口
- * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Managing_Coupons_Vouchers_and_Cards.html#6
+ *
*
* @param cardId 卡券ID
* @param oldCode 需变更的Code码
* @param newCode 变更后的有效Code码
- * @throws WxErrorException the wx error exception
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 设置买单接口
- * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Create_a_Coupon_Voucher_or_Card.html#12
+ *
*
* @param cardId 卡券ID
* @param isOpen 是否开启买单功能,填true/false
- * @throws WxErrorException the wx error exception
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 设置自助核销
- * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Create_a_Coupon_Voucher_or_Card.html#14
+ *
*
* @param cardId 卡券ID
* @param isOpen 是否开启自助核销功能
* @param needVerifyCod 用户核销时是否需要输入验证码, 填true/false, 默认为false
* @param needRemarkAmount 用户核销时是否需要备注核销金额, 填true/false, 默认为false
- * @throws WxErrorException the wx error exception
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 获取用户已领取卡券接口
- * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Managing_Coupons_Vouchers_and_Cards.html#1
+ *
*
* @param openId 需要查询的用户openid
* @param cardId 卡券ID。不填写时默认查询当前appid下的卡券
- * @return user card list
- * @throws WxErrorException the wx error exception
+ * @return 用户卡券列表结果对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 设置客服信息(即更新客服信息)
+ * 邀请绑定客服账号
* 详情请见:客服管理
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN
*
*
- * @param request the request
- * @return the boolean
- * @throws WxErrorException 异常
+ * @param request 客服账号请求对象,包含客服账号、邀请者微信号等信息
+ * @return 邀请是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean kfAccountInviteWorker(WxMpKfAccountRequest request) throws WxErrorException;
@@ -120,10 +120,10 @@ public interface WxMpKefuService {
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
*
*
- * @param kfAccount the kf account
- * @param imgFile the img file
- * @return the boolean
- * @throws WxErrorException 异常
+ * @param kfAccount 客服账号,格式为:账号前缀@微信号
+ * @param imgFile 头像图片文件,支持JPG、PNG格式,大小不超过2MB
+ * @return 上传是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException;
@@ -134,9 +134,9 @@ public interface WxMpKefuService {
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
*
*
- * @param kfAccount the kf account
- * @return the boolean
- * @throws WxErrorException 异常
+ * @param kfAccount 客服账号,格式为:账号前缀@微信号
+ * @return 删除是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean kfAccountDel(String kfAccount) throws WxErrorException;
@@ -150,10 +150,10 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN
*
*
- * @param openid the openid
- * @param kfAccount the kf account
- * @return the boolean
- * @throws WxErrorException 异常
+ * @param openid 用户的openid,标识具体的用户
+ * @param kfAccount 客服账号,格式为:账号前缀@微信号
+ * @return 创建是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException;
@@ -165,10 +165,10 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN
*
*
- * @param openid the openid
- * @param kfAccount the kf account
- * @return the boolean
- * @throws WxErrorException 异常
+ * @param openid 用户的openid,标识具体的用户
+ * @param kfAccount 客服账号,格式为:账号前缀@微信号
+ * @return 关闭是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException;
@@ -180,9 +180,9 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID
*
*
- * @param openid the openid
- * @return the wx mp kf session get result
- * @throws WxErrorException 异常
+ * @param openid 用户的openid,标识具体的用户
+ * @return 客户会话状态信息,包含客服账号、会话状态等
+ * @throws WxErrorException 微信API调用异常
*/
WxMpKfSessionGetResult kfSessionGet(String openid) throws WxErrorException;
@@ -194,9 +194,9 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
*
*
- * @param kfAccount the kf account
- * @return the wx mp kf session list
- * @throws WxErrorException 异常
+ * @param kfAccount 客服账号,格式为:账号前缀@微信号
+ * @return 客服会话列表,包含正在接待的会话信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpKfSessionList kfSessionList(String kfAccount) throws WxErrorException;
@@ -208,8 +208,8 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getwaitcase?access_token=ACCESS_TOKEN
*
*
- * @return the wx mp kf session wait case list
- * @throws WxErrorException 异常
+ * @return 未接入会话列表,包含等待接入的会话信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpKfSessionWaitCaseList kfSessionGetWaitCase() throws WxErrorException;
@@ -223,12 +223,12 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/msgrecord/getmsglist?access_token=ACCESS_TOKEN
*
*
- * @param startTime 起始时间
- * @param endTime 结束时间
- * @param msgId 消息id顺序从小到大,从1开始
- * @param number 每次获取条数,最多10000条
- * @return 聊天记录对象 wx mp kf msg list
- * @throws WxErrorException 异常
+ * @param startTime 起始时间,用于筛选聊天记录的时间范围
+ * @param endTime 结束时间,用于筛选聊天记录的时间范围
+ * @param msgId 消息id顺序从小到大,从1开始,用于分页获取
+ * @param number 每次获取条数,最多10000条,用于分页控制
+ * @return 聊天记录对象,包含客服和用户的聊天消息列表
+ * @throws WxErrorException 微信API调用异常
*/
WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer number) throws WxErrorException;
@@ -240,17 +240,17 @@ public interface WxMpKefuService {
* 接口url格式: https://api.weixin.qq.com/customservice/msgrecord/getmsglist?access_token=ACCESS_TOKEN
*
*
- * @param startTime 起始时间
- * @param endTime 结束时间
- * @return 聊天记录对象 wx mp kf msg list
- * @throws WxErrorException 异常
+ * @param startTime 起始时间,用于筛选聊天记录的时间范围
+ * @param endTime 结束时间,用于筛选聊天记录的时间范围
+ * @return 聊天记录对象,包含客服和用户的聊天消息列表
+ * @throws WxErrorException 微信API调用异常
*/
WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorException;
/**
*
* 客服输入状态
- * 开发者可通过调用“客服输入状态”接口,返回客服当前输入状态给用户。
+ * 开发者可通过调用"客服输入状态"接口,返回客服当前输入状态给用户。
* 此接口需要客服消息接口权限。
* 如果不满足发送客服消息的触发条件,则无法下发输入状态。
* 下发输入状态,需要客服之前30秒内跟用户有过消息交互。
@@ -261,10 +261,10 @@ public interface WxMpKefuService {
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN
*
*
- * @param openid 用户id
- * @param command "Typing":对用户下发“正在输入"状态 "CancelTyping":取消对用户的”正在输入"状态
- * @return the boolean
- * @throws WxErrorException 异常
+ * @param openid 用户的openid,标识具体的用户
+ * @param command 输入状态命令,可选值:"Typing":对用户下发"正在输入"状态;"CancelTyping":取消对用户的"正在输入"状态
+ * @return 发送是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean sendKfTypingState(String openid, String command) throws WxErrorException;
}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java
index 823c2c6343..5ad3098c2a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java
@@ -8,138 +8,234 @@
import me.chanjar.weixin.mp.bean.result.WxMpMassUploadResult;
/**
- * - * 群发消息服务类. + * 群发消息服务类 + ** * @author Binary Wang */ public interface WxMpMassMessageService { /** *+ * 提供微信公众号群发消息的功能,包括图文消息、视频消息的群发, + * 支持按分组群发、按openid列表群发、消息预览、群发状态查询等功能。 + *
+ *+ * 详情请见:群发消息开发文档 + *
* Created by Binary Wang on 2017-8-16. - *
- * 上传群发用的图文消息,上传后才能群发图文消息.
- *
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
+ * 上传群发用的图文消息,上传后才能群发图文消息
*
*
- * @param news the news
- * @return the wx mp mass upload result
- * @throws WxErrorException the wx error exception
- * @see #massGroupMessageSend(WxMpMassTagMessage) #massGroupMessageSend(WxMpMassTagMessage)
- * @see #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) #massOpenIdsMessageSend(WxMpMassOpenIdsMessage)
+ * @param news 图文消息对象
+ * @return 上传结果对象,包含media_id等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 上传群发用的视频,上传后才能群发视频消息.
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
+ * 上传群发用的视频,上传后才能群发视频消息
*
*
- * @param video the video
- * @return the wx mp mass upload result
- * @throws WxErrorException the wx error exception
- * @see #massGroupMessageSend(WxMpMassTagMessage) #massGroupMessageSend(WxMpMassTagMessage)
- * @see #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) #massOpenIdsMessageSend(WxMpMassOpenIdsMessage)
+ * @param video 视频消息对象
+ * @return 上传结果对象,包含media_id等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 分组群发消息.
+ * 分组群发消息
* 如果发送图文消息,必须先使用 {@link #massNewsUpload(WxMpMassNews)} 获得media_id,然后再发送
* 如果发送视频消息,必须先使用 {@link #massVideoUpload(WxMpMassVideo)} 获得media_id,然后再发送
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
*
*
- * @param message the message
- * @return the wx mp mass send result
- * @throws WxErrorException the wx error exception
+ * @param message 分组群发消息对象
+ * @return 群发结果对象,包含msg_id等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 按openId列表群发消息.
+ * 按openId列表群发消息
* 如果发送图文消息,必须先使用 {@link #massNewsUpload(WxMpMassNews)} 获得media_id,然后再发送
* 如果发送视频消息,必须先使用 {@link #massVideoUpload(WxMpMassVideo)} 获得media_id,然后再发送
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
*
*
- * @param message the message
- * @return the wx mp mass send result
- * @throws WxErrorException the wx error exception
+ * @param message 按openid列表群发消息对象
+ * @return 群发结果对象,包含msg_id等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 群发消息预览接口.
+ * 群发消息预览接口
* 开发者可通过该接口发送消息给指定用户,在手机端查看消息的样式和排版。为了满足第三方平台开发者的需求,
* 在保留对openID预览能力的同时,增加了对指定微信号发送预览的能力,但该能力每日调用次数有限制(100次),请勿滥用。
- * 接口调用请求说明
- * http请求方式: POST
- * https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=ACCESS_TOKEN
- * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
*
*
- * @param wxMpMassPreviewMessage the wx mp mass preview message
- * @return wxMpMassSendResult wx mp mass send result
- * @throws WxErrorException the wx error exception
+ * @param wxMpMassPreviewMessage 预览消息对象
+ * @return 群发结果对象,包含msg_id等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * 删除群发.
+ * 删除群发
* 群发之后,随时可以通过该接口删除群发。
* 请注意:
* 1、只有已经发送成功的消息才能删除
* 2、删除消息是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片。
* 3、删除群发消息只能删除图文消息和视频消息,其他类型的消息一经发送,无法删除。
* 4、如果多次群发发送的是一个图文消息,那么删除其中一次群发,就会删除掉这个图文消息也,导致所有群发都失效
- * 接口调用请求说明:
- * http请求方式: POST
- * https://api.weixin.qq.com/cgi-bin/message/mass/delete?access_token=ACCESS_TOKEN
- * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1481187827_i0l21
*
*
* @param msgId 发送出去的消息ID
* @param articleIndex 要删除的文章在图文消息中的位置,第一篇编号为1,该字段不填或填0会删除全部文章
- * @throws WxErrorException the wx error exception
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 获取群发速度
- * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#9
+ *
*
- * @return the wx mp mass speed get result
- * @throws WxErrorException the wx error exception
+ * @return 群发速度获取结果对象
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 设置群发速度
- * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#9
+ *
*
- * @param speed 群发速度的级别,是一个0到4的整数,数字越大表示群发速度越慢。 speed realspeed 0 80w/分钟 1 60w/分钟 2 45w/分钟 3 30w/分钟 4 10w/分钟
- * @throws WxErrorException the wx error exception
+ * @param speed 群发速度的级别,是一个0到4的整数,数字越大表示群发速度越慢。
+ * speed realspeed
+ * 0 80w/分钟
+ * 1 60w/分钟
+ * 2 45w/分钟
+ * 3 30w/分钟
+ * 4 10w/分钟
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 查询群发消息发送状态【订阅号与服务号认证后均可用】
- * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#%E6%9F%A5%E8%AF%A2%E7%BE%A4%E5%8F%91%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E7%8A%B6%E6%80%81%E3%80%90%E8%AE%A2%E9%98%85%E5%8F%B7%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%8F%B7%E8%AE%A4%E8%AF%81%E5%90%8E%E5%9D%87%E5%8F%AF%E7%94%A8%E3%80%91
+ *
*
* @param msgId 群发消息后返回的消息id
- * @return 消息发送后的状态 ,SEND_SUCCESS表示发送成功,SENDING表示发送中,SEND_FAIL表示发送失败,DELETE表示已删除
- * @throws WxErrorException the wx error exception
+ * @return 消息发送后的状态,SEND_SUCCESS表示发送成功,SENDING表示发送中,SEND_FAIL表示发送失败,DELETE表示已删除
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
- * Created by Binary Wang on 2016/7/21.
* 素材管理的相关接口,包括媒体管理的接口,
* 即以https://api.weixin.qq.com/cgi-bin/material
* 和 https://api.weixin.qq.com/cgi-bin/media开头的接口
@@ -36,7 +35,7 @@ public interface WxMpMaterialService {
* 2、media_id是可复用的。
* 3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/amr格式
* 4、需使用https调用本接口。
- * 本接口即为原“上传多媒体文件”接口。
+ * 本接口即为原"上传多媒体文件"接口。
* 注意事项:
* 上传的临时多媒体文件有格式和大小限制,如下:
* 图片(image): 2M,支持PNG\JPEG\JPG\GIF格式
@@ -49,17 +48,17 @@ public interface WxMpMaterialService {
*
*
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
- * @param file 文件对象
- * @return the wx media upload result
- * @throws WxErrorException the wx error exception
- * @see #mediaUpload(String, String, InputStream) #mediaUpload(String, String, InputStream)#mediaUpload(String, String, InputStream)
+ * @param file 文件对象,需要上传的临时素材文件
+ * @return 上传结果,包含media_id等信息
+ * @throws WxErrorException 微信API调用异常
+ * @see #mediaUpload(String, String, InputStream) 使用输入流上传临时素材
*/
WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException;
/**
*
* 新增临时素材
- * 本接口即为原“上传多媒体文件”接口。
+ * 本接口即为原"上传多媒体文件"接口。
*
* 详情请见: 新增临时素材
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
@@ -67,10 +66,10 @@ public interface WxMpMaterialService {
*
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
* @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts}
- * @param inputStream 输入流
- * @return the wx media upload result
- * @throws WxErrorException the wx error exception
- * @see #mediaUpload(java.lang.String, java.io.File) #mediaUpload(java.lang.String, java.io.File)#mediaUpload(java.lang.String, java.io.File)
+ * @param inputStream 输入流,包含要上传的临时素材内容
+ * @return 上传结果,包含media_id等信息
+ * @throws WxErrorException 微信API调用异常
+ * @see #mediaUpload(String, File) 使用文件对象上传临时素材
*/
WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException;
@@ -78,15 +77,15 @@ public interface WxMpMaterialService {
*
* 获取临时素材
* 公众号可以使用本接口获取临时素材(即下载临时的多媒体文件)。请注意,视频文件不支持https下载,调用该接口需http协议。
- * 本接口即为原“下载多媒体文件”接口。
+ * 本接口即为原"下载多媒体文件"接口。
* 根据微信文档,视频文件下载不了,会返回null
* 详情请见: 获取临时素材
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
*
*
- * @param mediaId 媒体文件Id
- * @return 保存到本地的临时文件 file
- * @throws WxErrorException the wx error exception
+ * @param mediaId 媒体文件Id,通过上传临时素材接口获取
+ * @return 保存到本地的临时文件,如果下载失败则返回null
+ * @throws WxErrorException 微信API调用异常
*/
File mediaDownload(String mediaId) throws WxErrorException;
@@ -100,9 +99,9 @@ public interface WxMpMaterialService {
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
*
*
- * @param mediaId 媒体文件Id
- * @return 保存到本地的临时文件 file
- * @throws WxErrorException the wx error exception
+ * @param mediaId 媒体文件Id,通过JSSDK上传语音素材获取
+ * @return 保存到本地的临时文件,高清语音素材
+ * @throws WxErrorException 微信API调用异常
*/
File jssdkMediaDownload(String mediaId) throws WxErrorException;
@@ -114,9 +113,9 @@ public interface WxMpMaterialService {
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN
*
*
- * @param file 上传的文件对象
- * @return WxMediaImgUploadResult 返回图片url
- * @throws WxErrorException the wx error exception
+ * @param file 上传的文件对象,图片素材,支持jpg/png格式,大小不超过1MB
+ * @return 图片上传结果,包含图片URL,可用于图文消息中
+ * @throws WxErrorException 微信API调用异常
*/
WxMediaImgUploadResult mediaImgUpload(File file) throws WxErrorException;
@@ -141,8 +140,8 @@ public interface WxMpMaterialService {
*
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
* @param material 上传的素材, 请看{@link WxMpMaterial}
- * @return the wx mp material upload result
- * @throws WxErrorException the wx error exception
+ * @return 上传结果,包含media_id等信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMaterialUploadResult materialFileUpload(String mediaType, WxMpMaterial material) throws WxErrorException;
@@ -155,8 +154,8 @@ public interface WxMpMaterialService {
*
*
* @param mediaId 永久素材的id
- * @return the input stream
- * @throws WxErrorException the wx error exception
+ * @return 素材内容的输入流,可用于读取图片或语音文件
+ * @throws WxErrorException 微信API调用异常
*/
InputStream materialImageOrVoiceDownload(String mediaId) throws WxErrorException;
@@ -169,8 +168,8 @@ public interface WxMpMaterialService {
*
*
* @param mediaId 永久素材的id
- * @return the wx mp material video info result
- * @throws WxErrorException the wx error exception
+ * @return 视频素材信息,包含标题、描述和下载地址
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMaterialVideoInfoResult materialVideoInfo(String mediaId) throws WxErrorException;
@@ -183,8 +182,8 @@ public interface WxMpMaterialService {
*
*
* @param mediaId 永久素材的id
- * @return the wx mp material news
- * @throws WxErrorException the wx error exception
+ * @return 图文素材信息,包含文章列表和标题等
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMaterialNews materialNewsInfo(String mediaId) throws WxErrorException;
@@ -201,8 +200,8 @@ public interface WxMpMaterialService {
*
*
* @param mediaId 永久素材的id
- * @return the boolean
- * @throws WxErrorException the wx error exception
+ * @return 删除是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常
*/
boolean materialDelete(String mediaId) throws WxErrorException;
@@ -219,8 +218,8 @@ public interface WxMpMaterialService {
* 接口url格式:https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token=ACCESS_TOKEN
*
*
- * @return the wx mp material count result
- * @throws WxErrorException the wx error exception
+ * @return 素材统计结果,包含各类素材的数量
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMaterialCountResult materialCount() throws WxErrorException;
@@ -232,10 +231,10 @@ public interface WxMpMaterialService {
* 接口url格式:https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN
*
*
- * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回
+ * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材返回
* @param count 返回素材的数量,取值在1到20之间
- * @return the wx mp material news batch get result
- * @throws WxErrorException the wx error exception
+ * @return 图文素材列表,包含文章列表和标题等信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMaterialNewsBatchGetResult materialNewsBatchGet(int offset, int count) throws WxErrorException;
@@ -248,10 +247,10 @@ public interface WxMpMaterialService {
*
*
* @param type 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
- * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回
+ * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材返回
* @param count 返回素材的数量,取值在1到20之间
- * @return the wx mp material file batch get result
- * @throws WxErrorException the wx error exception
+ * @return 其他媒体素材列表,包含图片、语音、视频等素材信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMaterialFileBatchGetResult materialFileBatchGet(String type, int offset, int count) throws WxErrorException;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java
index 3e78893005..ad1813ee85 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java
@@ -19,9 +19,9 @@ public interface WxMpMenuService {
* 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
*
*
- * @param menu the menu
- * @return 如果是个性化菜单 ,则返回menuid,否则返回null
- * @throws WxErrorException the wx error exception
+ * @param menu 菜单对象,包含菜单结构和配置信息
+ * @return 如果是个性化菜单,则返回menuid,否则返回null
+ * @throws WxErrorException 微信API调用异常
*/
String menuCreate(WxMenu menu) throws WxErrorException;
@@ -33,9 +33,9 @@ public interface WxMpMenuService {
* 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
*
*
- * @param json the json
- * @return 如果是个性化菜单 ,则返回menuid,否则返回null
- * @throws WxErrorException the wx error exception
+ * @param json 菜单配置的JSON字符串,包含菜单结构和配置信息
+ * @return 如果是个性化菜单,则返回menuid,否则返回null
+ * @throws WxErrorException 微信API调用异常
*/
String menuCreate(String json) throws WxErrorException;
@@ -45,7 +45,7 @@ public interface WxMpMenuService {
* 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN
*
*
- * @throws WxErrorException the wx error exception
+ * @throws WxErrorException 微信API调用异常
*/
void menuDelete() throws WxErrorException;
@@ -55,8 +55,8 @@ public interface WxMpMenuService {
* 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
*
*
- * @param menuId 个性化菜单的menuid
- * @throws WxErrorException the wx error exception
+ * @param menuId 个性化菜单的menuid,通过创建个性化菜单时返回
+ * @throws WxErrorException 微信API调用异常
*/
void menuDelete(String menuId) throws WxErrorException;
@@ -66,8 +66,8 @@ public interface WxMpMenuService {
* 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN
*
*
- * @return the wx mp menu
- * @throws WxErrorException the wx error exception
+ * @return 当前公众号的自定义菜单配置
+ * @throws WxErrorException 微信API调用异常
*/
WxMpMenu menuGet() throws WxErrorException;
@@ -77,9 +77,9 @@ public interface WxMpMenuService {
* 详情请见: http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html
*
*
- * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号。
- * @return the wx menu
- * @throws WxErrorException the wx error exception
+ * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号
+ * @return 匹配到的菜单配置
+ * @throws WxErrorException 微信API调用异常
*/
WxMenu menuTryMatch(String userid) throws WxErrorException;
@@ -98,8 +98,8 @@ public interface WxMpMenuService {
* https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=ACCESS_TOKEN
*
*
- * @return the self menu info
- * @throws WxErrorException the wx error exception
+ * @return 自定义菜单配置信息,包含菜单结构和配置详情
+ * @throws WxErrorException 微信API调用异常
*/
WxMpGetSelfMenuInfoResult getSelfMenuInfo() throws WxErrorException;
}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java
index ed8c5e6a79..df923512c9 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java
@@ -22,8 +22,8 @@ public interface WxMpQrcodeService {
*
* @param sceneId 场景值ID,临时二维码时为32位非0整型
* @param expireSeconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。
- * @return the wx mp qr code ticket
- * @throws WxErrorException the wx error exception
+ * @return 二维码ticket,可用于获取二维码图片
+ * @throws WxErrorException 微信API调用异常
*/
WxMpQrCodeTicket qrCodeCreateTmpTicket(int sceneId, Integer expireSeconds) throws WxErrorException;
@@ -36,8 +36,8 @@ public interface WxMpQrcodeService {
*
* @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64
* @param expireSeconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。
- * @return the wx mp qr code ticket
- * @throws WxErrorException the wx error exception
+ * @return 二维码ticket,可用于获取二维码图片
+ * @throws WxErrorException 微信API调用异常
*/
WxMpQrCodeTicket qrCodeCreateTmpTicket(String sceneStr, Integer expireSeconds) throws WxErrorException;
@@ -48,8 +48,8 @@ public interface WxMpQrcodeService {
*
*
* @param sceneId 场景值ID,最大值为100000(目前参数只支持1--100000)
- * @return the wx mp qr code ticket
- * @throws WxErrorException the wx error exception
+ * @return 二维码ticket,可用于获取二维码图片
+ * @throws WxErrorException 微信API调用异常
*/
WxMpQrCodeTicket qrCodeCreateLastTicket(int sceneId) throws WxErrorException;
@@ -60,8 +60,8 @@ public interface WxMpQrcodeService {
*
*
* @param sceneStr 参数。字符串类型长度现在为1到64
- * @return the wx mp qr code ticket
- * @throws WxErrorException the wx error exception
+ * @return 二维码ticket,可用于获取二维码图片
+ * @throws WxErrorException 微信API调用异常
*/
WxMpQrCodeTicket qrCodeCreateLastTicket(String sceneStr) throws WxErrorException;
@@ -71,9 +71,9 @@ public interface WxMpQrcodeService {
* 详情请见: 生成带参数的二维码
*
*
- * @param ticket 二维码ticket
- * @return the file
- * @throws WxErrorException the wx error exception
+ * @param ticket 二维码ticket,通过创建二维码接口获取
+ * @return 二维码图片文件,jpg格式
+ * @throws WxErrorException 微信API调用异常
*/
File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException;
@@ -85,8 +85,9 @@ public interface WxMpQrcodeService {
*
* @param ticket 二维码ticket
* @param needShortUrl 是否需要压缩的二维码地址
- * @return the string
- * @throws WxErrorException the wx error exception
+ * @return 二维码图片的URL地址
+ * @throws WxErrorException 微信API调用异常
+ * @deprecated 请使用 {@link #qrCodePictureUrl(String)} 方法
*/
@Deprecated
String qrCodePictureUrl(String ticket, boolean needShortUrl) throws WxErrorException;
@@ -97,9 +98,9 @@ public interface WxMpQrcodeService {
* 详情请见: 生成带参数的二维码
*
*
- * @param ticket 二维码ticket
- * @return the string
- * @throws WxErrorException the wx error exception
+ * @param ticket 二维码ticket,通过创建二维码接口获取
+ * @return 二维码图片的URL地址
+ * @throws WxErrorException 微信API调用异常
*/
String qrCodePictureUrl(String ticket) throws WxErrorException;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index 47a24b7931..468dced138 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -37,7 +37,7 @@ public interface WxMpService extends WxService {
* @param longData 需要转换的长信息,不超过4KB
* @param expireSeconds 短key有效期(单位秒),最大值为2592000(即30天),默认为2592000(30天)
* @return shortKey 短key,15字节,base62编码(0-9/a-z/A-Z)
- * @throws WxErrorException .
+ * @throws WxErrorException 微信API调用异常
*/
String genShorten(String longData, Integer expireSeconds) throws WxErrorException;
@@ -47,9 +47,9 @@ public interface WxMpService extends WxService {
* 详情请见: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/KEY_Shortener.html
*
*
- * @param shortKey 短key
- * @return WxMpShortKeyResult 解析结果
- * @throws WxErrorException .
+ * @param shortKey 短key,15字节,base62编码(0-9/a-z/A-Z)
+ * @return WxMpShortKeyResult 解析结果,包含原始长信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpShortKeyResult fetchShorten(String shortKey) throws WxErrorException;
@@ -59,19 +59,19 @@ public interface WxMpService extends WxService {
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
*
*
- * @param timestamp 时间戳
- * @param nonce 随机串
- * @param signature 签名
- * @return 是否验证通过 boolean
+ * @param timestamp 时间戳,字符串格式
+ * @param nonce 随机串,字符串格式
+ * @param signature 签名,字符串格式
+ * @return 是否验证通过,true表示验证通过,false表示验证失败
*/
boolean checkSignature(String timestamp, String nonce, String signature);
/**
* 获取access_token, 不强制刷新access_token.
*
- * @return token access token
- * @throws WxErrorException .
- * @see #getAccessToken(boolean) #getAccessToken(boolean)#getAccessToken(boolean)#getAccessToken(boolean)
+ * @return token access token,字符串格式
+ * @throws WxErrorException 微信API调用异常
+ * @see #getAccessToken(boolean) 获取access_token,可选择是否强制刷新
*/
String getAccessToken() throws WxErrorException;
@@ -87,21 +87,21 @@ public interface WxMpService extends WxService {
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
*
*
- * @param forceRefresh 是否强制刷新
- * @return token access token
- * @throws WxErrorException .
+ * @param forceRefresh 是否强制刷新,true表示强制刷新,false表示使用缓存
+ * @return token access token,字符串格式
+ * @throws WxErrorException 微信API调用异常
*/
String getAccessToken(boolean forceRefresh) throws WxErrorException;
/**
* 获得ticket,不强制刷新ticket.
*
- * @param type ticket 类型
- * @return ticket ticket
- * @throws WxErrorException .
- * @see #getTicket(TicketType, boolean) #getTicket(TicketType, boolean)#getTicket(TicketType, boolean)#getTicket(TicketType, boolean)
+ * @param ticketType ticket 类型,通过TicketType枚举指定
+ * @return ticket ticket,字符串格式
+ * @throws WxErrorException 微信API调用异常
+ * @see #getTicket(TicketType, boolean) 获得ticket,可选择是否强制刷新
*/
- String getTicket(TicketType type) throws WxErrorException;
+ String getTicket(TicketType ticketType) throws WxErrorException;
/**
*
@@ -109,19 +109,19 @@ public interface WxMpService extends WxService {
* 获得时会检查 Token是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
*
- * @param type ticket类型
- * @param forceRefresh 强制刷新
- * @return ticket ticket
- * @throws WxErrorException .
+ * @param ticketType ticket类型,通过TicketType枚举指定
+ * @param forceRefresh 强制刷新,true表示强制刷新,false表示使用缓存
+ * @return ticket ticket,字符串格式
+ * @throws WxErrorException 微信API调用异常
*/
- String getTicket(TicketType type, boolean forceRefresh) throws WxErrorException;
+ String getTicket(TicketType ticketType, boolean forceRefresh) throws WxErrorException;
/**
* 获得jsapi_ticket,不强制刷新jsapi_ticket.
*
- * @return jsapi ticket
- * @throws WxErrorException .
- * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean)#getJsapiTicket(boolean)#getJsapiTicket(boolean)
+ * @return jsapi ticket,字符串格式
+ * @throws WxErrorException 微信API调用异常
+ * @see #getJsapiTicket(boolean) 获得jsapi_ticket,可选择是否强制刷新
*/
String getJsapiTicket() throws WxErrorException;
@@ -133,9 +133,9 @@ public interface WxMpService extends WxService {
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
*
*
- * @param forceRefresh 强制刷新
- * @return jsapi ticket
- * @throws WxErrorException .
+ * @param forceRefresh 强制刷新,true表示强制刷新,false表示使用缓存
+ * @return jsapi ticket,字符串格式
+ * @throws WxErrorException 微信API调用异常
*/
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;
@@ -146,9 +146,9 @@ public interface WxMpService extends WxService {
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
*
*
- * @param url 地址
- * @return 生成的签名对象 wx jsapi signature
- * @throws WxErrorException .
+ * @param url 当前网页的URL,不包括#及其后面部分
+ * @return 生成的签名对象,包含签名、时间戳、随机串等信息
+ * @throws WxErrorException 微信API调用异常
*/
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;
@@ -158,9 +158,10 @@ public interface WxMpService extends WxService {
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口
*
*
- * @param longUrl 长url
- * @return 生成的短地址 string
- * @throws WxErrorException .
+ * @param longUrl 长url,需要转换的原始URL
+ * @return 生成的短地址,字符串格式
+ * @throws WxErrorException 微信API调用异常
+ * @deprecated 请使用 {@link #genShorten(String, Integer)} 方法
*/
@Deprecated
String shortUrl(String longUrl) throws WxErrorException;
@@ -171,9 +172,9 @@ public interface WxMpService extends WxService {
* 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解
*
*
- * @param semanticQuery 查询条件
- * @return 查询结果 wx mp semantic query result
- * @throws WxErrorException .
+ * @param semanticQuery 查询条件,包含查询内容、类型等信息
+ * @return 查询结果,包含语义理解的结果和建议回复
+ * @throws WxErrorException 微信API调用异常
*/
WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException;
@@ -187,7 +188,7 @@ public interface WxMpService extends WxService {
* @param redirectUri 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
* @param state 非必填,用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
- * @return url string
+ * @return url 构造好的授权登录URL,字符串格式
*/
String buildQrConnectUrl(String redirectUri, String scope, String state);
@@ -197,8 +198,8 @@ public interface WxMpService extends WxService {
* http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html
*
*
- * @return 微信服务器ip地址数组 string [ ]
- * @throws WxErrorException .
+ * @return 微信服务器ip地址数组,包含所有微信服务器IP地址
+ * @throws WxErrorException 微信API调用异常
*/
String[] getCallbackIP() throws WxErrorException;
@@ -209,10 +210,10 @@ public interface WxMpService extends WxService {
* 为了帮助开发者排查回调连接失败的问题,提供这个网络检测的API。它可以对开发者URL做域名解析,然后对所有IP进行一次ping操作,得到丢包率和耗时。
*
*
- * @param action 执行的检测动作
- * @param operator 指定平台从某个运营商进行检测
- * @return 检测结果 wx net check result
- * @throws WxErrorException .
+ * @param action 执行的检测动作,可选值:all(全部检测)、dns(仅域名解析)、ping(仅网络连通性检测)
+ * @param operator 指定平台从某个运营商进行检测,可选值:CHINANET(中国电信)、UNICOM(中国联通)、CAP(中国联通)、CUCC(中国联通)
+ * @return 检测结果,包含丢包率和耗时等信息
+ * @throws WxErrorException 微信API调用异常
*/
WxNetCheckResult netCheck(String action, String operator) throws WxErrorException;
@@ -232,8 +233,8 @@ public interface WxMpService extends WxService {
* https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info?access_token=ACCESS_TOKEN
*
*
- * @return 公众号的自动回复规则 current auto reply info
- * @throws WxErrorException .
+ * @return 公众号的自动回复规则,包含关注后自动回复、消息自动回复、关键词自动回复等信息
+ * @throws WxErrorException 微信API调用异常
*/
WxMpCurrentAutoReplyInfo getCurrentAutoReplyInfo() throws WxErrorException;
@@ -245,8 +246,8 @@ public interface WxMpService extends WxService {
*
*
*
- * @param appid 公众号的APPID
- * @throws WxErrorException the wx error exception
+ * @param appid 公众号的APPID,需要清零调用的公众号的appid
+ * @throws WxErrorException 微信API调用异常
*/
void clearQuota(String appid) throws WxErrorException;
@@ -257,53 +258,53 @@ public interface WxMpService extends WxService {
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法
*
*
- * @param * 模板消息接口 - * http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN + *+ * @author Binary Wang */ public interface WxMpTemplateMsgService { - /** - *+ * 提供微信模板消息的发送、行业设置、模板管理等功能。 + * 模板消息用于在用户触发特定事件后,向用户发送重要的服务通知。 + *
+ *+ * 详情请见:模板消息开发文档 + *
* Created by Binary Wang on 2016-10-14. + * * @author miller.lin - * @author Binary Wang
- * 设置所属行业 - * 官方文档中暂未告知响应内容 - * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - *- * - * @param wxMpIndustry 行业信息 - * @return 是否成功 industry - * @throws WxErrorException . - */ - boolean setIndustry(WxMpTemplateIndustry wxMpIndustry) throws WxErrorException; + /** + *
+ * 设置所属行业 + * 官方文档中暂未告知响应内容 + *+ * + * @param wxMpIndustry 行业信息 + * @return 是否成功设置行业 + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 获取设置的行业信息 - * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - *- * - * @return wxMpIndustry industry - * @throws WxErrorException . - */ - WxMpTemplateIndustry getIndustry() throws WxErrorException; + /** + *
+ * 获取设置的行业信息 + *+ * + * @return 行业信息对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 发送模板消息 - * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - *- * - * @param templateMessage 模板消息 - * @return 消息Id string - * @throws WxErrorException . - */ - String sendTemplateMsg(WxMpTemplateMessage templateMessage) throws WxErrorException; + /** + *
+ * 发送模板消息 + *+ * + * @param templateMessage 模板消息对象 + * @return 消息ID,可用于查询模板消息发送状态 + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 获得模板ID - * 从行业模板库选择模板到账号后台,获得模板ID的过程可在MP中完成 - * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=ACCESS_TOKEN - *- * - * @param shortTemplateId 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式 - * @return templateId 模板Id - * @throws WxErrorException . - * @deprecated 请使用 addTemplate(java.lang.String, java.util.List) - */ - @Deprecated - String addTemplate(String shortTemplateId) throws WxErrorException; + /** + *
+ * 获得模板ID + * 从行业模板库选择模板到账号后台,获得模板ID的过程可在MP中完成 + *+ * + * @param shortTemplateId 模板库中模板的编号,有"TM**"和"OPENTMTM**"等形式 + * @return 模板ID + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 获得模板ID - * 从类目模板库选择模板到账号后台,获得模板ID的过程可在MP中完成 - * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=ACCESS_TOKEN - *- * - * @param shortTemplateId 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式,对于类目模板,为纯数字ID - * @param keywordNameList 选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码 - * @return templateId 模板Id - * @throws WxErrorException . - */ - String addTemplate(String shortTemplateId, List
+ * 获得模板ID + * 从类目模板库选择模板到账号后台,获得模板ID的过程可在MP中完成 + *+ * + * @param shortTemplateId 模板库中模板的编号,有"TM**"和"OPENTMTM**"等形式,对于类目模板,为纯数字ID + * @param keywordNameList 选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码 + * @return 模板ID + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 获取模板列表 - * 获取已添加至账号下所有模板列表,可在MP中查看模板列表信息,为方便第三方开发者,提供通过接口调用的方式来获取账号下所有模板信息 - * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=ACCESS_TOKEN - *- * - * @return templateId 模板Id - * @throws WxErrorException . - */ - List
+ * 获取模板列表 + * 获取已添加至账号下所有模板列表,可在MP中查看模板列表信息,为方便第三方开发者,提供通过接口调用的方式来获取账号下所有模板信息 + *+ * + * @return 模板列表,包含所有已添加的模板信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
- * 删除模板 - * 删除模板可在MP中完成,为方便第三方开发者,提供通过接口调用的方式来删除某账号下的模板 - * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN - * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/del_private_template?access_token=ACCESS_TOKEN - *- * - * @param templateId 模板Id - * @return . boolean - * @throws WxErrorException . - */ - boolean delPrivateTemplate(String templateId) throws WxErrorException; + /** + *
+ * 删除模板 + * 删除模板可在MP中完成,为方便第三方开发者,提供通过接口调用的方式来删除某账号下的模板 + *+ * + * @param templateId 模板ID + * @return 是否成功删除 + * @throws WxErrorException 微信API调用异常,可能包括: + *
+ * 提供微信公众号用户标签的创建、查询、更新、删除等功能。 + * 通过标签可以方便地对用户进行分组管理和精准营销。 + * 一个公众号最多可以创建100个标签。 + *
+ *+ * 详情请见:用户标签管理 + *
* Created by Binary Wang on 2016/9/2. * * @author Binary Wang @@ -17,110 +25,193 @@ public interface WxMpUserTagService { *
* 创建标签
* 一个公众号,最多可以创建100个标签。
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/create?access_token=ACCESS_TOKEN
*
*
* @param name 标签名字(30个字符以内)
- * @return the wx user tag
- * @throws WxErrorException the wx error exception
+ * @return 创建的标签对象,包含标签ID等信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 获取公众号已创建的标签
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/get?access_token=ACCESS_TOKEN
*
*
- * @return the list
- * @throws WxErrorException the wx error exception
+ * @return 标签列表,包含所有已创建的标签信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 编辑标签
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/update?access_token=ACCESS_TOKEN
+ * 可以修改标签的名称,但不能修改标签ID。
*
*
- * @param tagId the tag id
- * @param name the name
- * @return the boolean
- * @throws WxErrorException the wx error exception
+ * @param tagId 标签ID,不能为null
+ * @param name 新的标签名字(30个字符以内)
+ * @return 操作是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 删除标签
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=ACCESS_TOKEN
+ * 删除标签后,该标签下的所有用户将被取消标签。
*
*
- * @param tagId the tag id
- * @return the boolean
- * @throws WxErrorException the wx error exception
+ * @param tagId 标签ID,不能为null
+ * @return 操作是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 获取标签下粉丝列表
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=ACCESS_TOKEN
+ * 可用于获取某个标签下的所有用户信息,支持分页查询。
*
*
- * @param tagId the tag id
- * @param nextOpenid the next openid
- * @return the wx tag list user
- * @throws WxErrorException the wx error exception
+ * @param tagId 标签ID,不能为null
+ * @param nextOpenid 第一个拉取用户的openid,不填从头开始拉取
+ * @return 标签下粉丝列表对象,包含用户信息和分页信息
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 批量为用户打标签
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=ACCESS_TOKEN
+ * 可以为多个用户同时打上同一个标签。
*
*
- * @param tagId the tag id
- * @param openids the openids
- * @return the boolean
- * @throws WxErrorException the wx error exception
+ * @param tagId 标签ID,不能为null
+ * @param openids 用户openid数组,不能为null或空数组
+ * @return 操作是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 批量为用户取消标签
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=ACCESS_TOKEN
+ * 可以为多个用户同时取消同一个标签。
*
*
- * @param tagId the tag id
- * @param openids the openids
- * @return the boolean
- * @throws WxErrorException the wx error exception
+ * @param tagId 标签ID,不能为null
+ * @param openids 用户openid数组,不能为null或空数组
+ * @return 操作是否成功,true表示成功,false表示失败
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ *
* 获取用户身上的标签列表
- * 详情请见:用户标签管理
- * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/getidlist?access_token=ACCESS_TOKEN
+ * 可查询某个用户被打上的所有标签ID。
*
*
- * @param openid the openid
- * @return 标签Id的列表 list
- * @throws WxErrorException the wx error exception
+ * @param openid 用户的openid,不能为null或空字符串
+ * @return 标签ID的列表,表示该用户被打上的所有标签
+ * @throws WxErrorException 微信API调用异常,可能包括:
+ * - * Created by BinaryWang on 2018/6/9. - *+ * Created by BinaryWang on 2018/6/9. * * @author Binary Wang */ @RequiredArgsConstructor public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { - private final WxMpService wxMpService; + private final WxMpService wxMpService; - @Override - public void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { - if (lang == null) { - lang = AiLangType.zh_CN; + @Override + public void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { + if (lang == null) { + lang = AiLangType.zh_CN; + } + + this.wxMpService.execute(VoiceUploadRequestExecutor.create(this.wxMpService.getRequestHttp()), + String.format(VOICE_UPLOAD_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), "mp3", voiceId, lang.getCode()), + voiceFile); } - this.wxMpService.execute(VoiceUploadRequestExecutor.create(this.wxMpService.getRequestHttp()), - String.format(VOICE_UPLOAD_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), "mp3", voiceId, lang.getCode()), - voiceFile); - } + @Override + public String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException { + if (lang == null) { + lang = AiLangType.zh_CN; + } - @Override - public String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { - this.uploadVoice(voiceId, lang, voiceFile); - return this.queryRecognitionResult(voiceId, lang); - } + final String response = this.wxMpService.get(VOICE_QUERY_RESULT_URL, + String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); + WxError error = WxError.fromJson(response, WxType.MP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } - @Override - public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { - String response = this.wxMpService.post(String.format(TRANSLATE_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), - langFrom.getCode(), langTo.getCode()), content); + return GsonParser.parse(response).get("result").getAsString(); + } - WxError error = WxError.fromJson(response, WxType.MP); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); + @Override + public String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { + this.uploadVoice(voiceId, lang, voiceFile); + return this.queryRecognitionResult(voiceId, lang); } - return GsonParser.parse(response).get("to_content").getAsString(); - } + @Override + public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { + String response = this.wxMpService.post(String.format(TRANSLATE_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), + langFrom.getCode(), langTo.getCode()), content); - @Override - public String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException { - if (lang == null) { - lang = AiLangType.zh_CN; - } + WxError error = WxError.fromJson(response, WxType.MP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } - final String response = this.wxMpService.get(VOICE_QUERY_RESULT_URL, - String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); - WxError error = WxError.fromJson(response, WxType.MP); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); + return GsonParser.parse(response).get("to_content").getAsString(); } - - return GsonParser.parse(response).get("result").getAsString(); - } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java index 007942f096..63a3208e4b 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java @@ -46,10 +46,10 @@ public List
@@ -44,6 +49,11 @@ public interface WxOpenMaService extends WxMaService {
*/
String API_SET_WEBVIEW_DOMAIN = "https://api.weixin.qq.com/wxa/setwebviewdomain";
+ /**
+ * 快速配置小程序业务域名
+ */
+ String API_SET_WEBVIEW_DOMAIN_DIRECTLY = "https://api.weixin.qq.com/wxa/setwebviewdomain_directly";
+
/**
* 获取业务域名校验文件(仅供第三方代小程序调用)
*/
@@ -310,6 +320,24 @@ WxOpenMaDomainResult modifyDomain(String action, List requestDomains, Li
List uploadDomains, List downloadDomains,
List udpDomains, List tcpDomains) throws WxErrorException;
+ /**
+ * 快速配置小程序服务器域名
+ * 文档地址
+ *
+ * @param action add添加, delete删除, set覆盖, get获取
+ * @param requestDomains request 合法域名;当 action 是 get 时不需要此字段
+ * @param wsRequestDomains socket 合法域名;当 action 是 get 时不需要此字段
+ * @param uploadDomains uploadFile 合法域名;当 action 是 get 时不需要此字段
+ * @param downloadDomains downloadFile 合法域名;当 action 是 get 时不需要此字段
+ * @param tcpDomains tcp 合法域名;当 action 是 get 时不需要此字段
+ * @param udpDomains udp 合法域名;当 action 是 get 时不需要此字段
+ * @return the wx open ma domain result
+ * @throws WxErrorException the wx error exception
+ */
+ WxOpenMaDomainResult modifyDomainDirectly(String action, List requestDomains, List wsRequestDomains,
+ List uploadDomains, List downloadDomains,
+ List udpDomains, List tcpDomains) throws WxErrorException;
+
/**
* 获取小程序的业务域名
*
@@ -346,6 +374,28 @@ WxOpenMaDomainResult modifyDomain(String action, List requestDomains, Li
*/
WxOpenMaWebDomainResult setWebViewDomainInfo(String action, List domainList) throws WxErrorException;
+ /**
+ * 快速配置小程序业务域名
+ * 文档地址
+ *
+ * @param action add添加, delete删除, set覆盖, get获取
+ * @param domainList the domain list
+ * @return 直接返回字符串
+ * @throws WxErrorException the wx error exception
+ */
+ String setWebViewDomainDirectly(String action, List domainList) throws WxErrorException;
+
+ /**
+ * 快速配置小程序业务域名
+ * 文档地址
+ *
+ * @param action add添加, delete删除, set覆盖, get获取
+ * @param domainList the domain list
+ * @return web view domain info
+ * @throws WxErrorException the wx error exception
+ */
+ WxOpenMaWebDomainResult setWebViewDomainDirectlyInfo(String action, List domainList) throws WxErrorException;
+
/**
* 获取业务域名校验文件
*
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
index 80da912bef..18940c4d20 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
@@ -1307,4 +1307,55 @@ public WxOpenResult applySetOrderPathInfo(WxOpenMaApplyOrderPathInfo info) throw
String response = post(OPEN_APPLY_SET_ORDER_PATH_INFO, gson.toJson(info));
return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
}
+
+ @Override
+ public WxOpenMaDomainResult modifyWxaServerDomain(String action, List requestDomains, List wsRequestDomains,
+ List uploadDomains, List downloadDomains,
+ List udpDomains, List tcpDomains) throws WxErrorException {
+ JsonObject requestJson = new JsonObject();
+ requestJson.addProperty("action", action);
+ if (!"get".equals(action)) {
+ requestJson.add("requestdomain", toJsonArray(requestDomains));
+ requestJson.add("wsrequestdomain", toJsonArray(wsRequestDomains));
+ requestJson.add("uploaddomain", toJsonArray(uploadDomains));
+ requestJson.add("downloaddomain", toJsonArray(downloadDomains));
+ requestJson.add("udpdomain", toJsonArray(udpDomains));
+ requestJson.add("tcpdomain", toJsonArray(tcpDomains));
+ }
+
+ String response = post(API_MODIFY_WXA_SERVER_DOMAIN, requestJson.toString());
+ return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class);
+ }
+
+ @Override
+ public WxOpenMaDomainConfirmFileResult getDomainConfirmFile() throws WxErrorException {
+ String responseContent = post(API_GET_DOMAIN_CONFIRM_FILE, "{}");
+ return WxOpenMaDomainConfirmFileResult.fromJson(responseContent);
+ }
+
+ @Override
+ public String modifyWxaJumpDomain(String action, List domainList) throws WxErrorException {
+ JsonObject requestJson = new JsonObject();
+ requestJson.addProperty("action", action);
+ if (!"get".equals(action)) {
+ requestJson.add("webviewdomain", toJsonArray(domainList));
+ }
+ return post(API_MODIFY_WXA_JUMP_DOMAIN, requestJson.toString());
+ }
+
+ @Override
+ public WxOpenMaWebDomainResult modifyWxaJumpDomainInfo(String action, List domainList) throws WxErrorException {
+ String response = this.modifyWxaJumpDomain(action, domainList);
+ return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class);
+ }
+
+ private JsonArray toJsonArray(List list) {
+ JsonArray jsonArray = new JsonArray();
+ if (list != null) {
+ for (String item : list) {
+ jsonArray.add(item);
+ }
+ }
+ return jsonArray;
+ }
}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
index 85ce41fd0c..da9f910eb2 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
@@ -113,6 +113,25 @@ public WxOpenMaDomainResult modifyDomain(String action, List requestDoma
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class);
}
+ @Override
+ public WxOpenMaDomainResult modifyDomainDirectly(String action, List requestDomains, List wsRequestDomains,
+ List uploadDomains, List downloadDomains,
+ List udpDomains, List tcpDomains) throws WxErrorException {
+ JsonObject requestJson = new JsonObject();
+ requestJson.addProperty(ACTION, action);
+ if (!ACTION_GET.equals(action)) {
+ requestJson.add("requestdomain", toJsonArray(requestDomains));
+ requestJson.add("wsrequestdomain", toJsonArray(wsRequestDomains));
+ requestJson.add("uploaddomain", toJsonArray(uploadDomains));
+ requestJson.add("downloaddomain", toJsonArray(downloadDomains));
+ requestJson.add("udpdomain", toJsonArray(udpDomains));
+ requestJson.add("tcpdomain", toJsonArray(tcpDomains));
+ }
+
+ String response = post(API_MODIFY_DOMAIN_DIRECTLY, GSON.toJson(requestJson));
+ return WxMaGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class);
+ }
+
@Override
public String getWebViewDomain() throws WxErrorException {
return setWebViewDomain(ACTION_GET, null);
@@ -140,6 +159,22 @@ public WxOpenMaWebDomainResult setWebViewDomainInfo(String action, List
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class);
}
+ @Override
+ public String setWebViewDomainDirectly(String action, List domainList) throws WxErrorException {
+ JsonObject requestJson = new JsonObject();
+ requestJson.addProperty(ACTION, action);
+ if (!ACTION_GET.equals(action)) {
+ requestJson.add("webviewdomain", toJsonArray(domainList));
+ }
+ return post(API_SET_WEBVIEW_DOMAIN_DIRECTLY, GSON.toJson(requestJson));
+ }
+
+ @Override
+ public WxOpenMaWebDomainResult setWebViewDomainDirectlyInfo(String action, List domainList) throws WxErrorException {
+ String response = this.setWebViewDomainDirectly(action, domainList);
+ return WxMaGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class);
+ }
+
@Override
public WxOpenMaDomainConfirmFileResult getWebviewDomainConfirmFile() throws WxErrorException {
String responseContent = post(API_GET_WEBVIEW_DOMAIN_CONFIRM_FILE, "{}");
From e9bc5d01098764779f89d6622e7dbc207b051b45 Mon Sep 17 00:00:00 2001
From: troubleMTT <14077153+troublemtt@user.noreply.gitee.com>
Date: Thu, 30 Oct 2025 11:43:02 +0000
Subject: [PATCH 012/111] =?UTF-8?q?:art:=20#3746=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E8=8E=B7=E5=8F=96=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=B7=B2=E9=85=8D=E7=BD=AE=E7=9A=84=E3=80=8C=E8=81=94?=
=?UTF-8?q?=E7=B3=BB=E6=88=91=E3=80=8D=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3?=
=?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=80=BC=E5=A2=9E=E5=8A=A0next=5Fcursor?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../chanjar/weixin/cp/bean/external/WxCpContactWayList.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java
index 04918f64e4..aeaeeea439 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java
@@ -26,6 +26,12 @@ public class WxCpContactWayList extends WxCpBaseResp implements Serializable {
@SerializedName("contact_way")
private List contactWay;
+ /**
+ * 分页参数,用于查询下一个分页的数据,为空时表示没有更多的分页
+ */
+ @SerializedName("next_cursor")
+ private String nextCursor;
+
/**
* The type Contact way.
*/
From bb573afcafa4b29f96e68845c0606614fad4a72e Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 16:30:13 +0800
Subject: [PATCH 013/111] :art: #3732 Add Quarkus/GraalVM Native Image support
- Fix Random instance initialization issues
---
QUARKUS_SUPPORT.md | 112 ++++++++++++++++++
README.md | 15 +--
.../weixin/common/util/RandomUtils.java | 16 ++-
.../common/util/crypto/WxCryptUtil.java | 17 ++-
.../native-image.properties | 4 +
.../weixin-java-common/reflect-config.json | 14 +++
.../binarywang/wxpay/v3/util/SignUtils.java | 17 ++-
7 files changed, 182 insertions(+), 13 deletions(-)
create mode 100644 QUARKUS_SUPPORT.md
create mode 100644 weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties
create mode 100644 weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json
diff --git a/QUARKUS_SUPPORT.md b/QUARKUS_SUPPORT.md
new file mode 100644
index 0000000000..c20fb2c28b
--- /dev/null
+++ b/QUARKUS_SUPPORT.md
@@ -0,0 +1,112 @@
+# WxJava Quarkus/GraalVM Native Image Support
+
+## 概述
+
+从 4.7.8.B 版本开始,WxJava 提供了对 Quarkus 和 GraalVM Native Image 的支持。这允许您将使用 WxJava 的应用程序编译为原生可执行文件,从而获得更快的启动速度和更低的内存占用。
+
+## 问题背景
+
+在之前的版本中,使用 Quarkus 构建 Native Image 时会遇到以下错误:
+
+```
+Error: Unsupported features in 3 methods
+Detailed message:
+Error: Detected an instance of Random/SplittableRandom class in the image heap.
+Instances created during image generation have cached seed values and don't behave as expected.
+The culprit object has been instantiated by the 'org.apache.http.impl.auth.NTLMEngineImpl' class initializer
+```
+
+## 解决方案
+
+为了解决这个问题,WxJava 进行了以下改进:
+
+### 1. Random 实例的延迟初始化
+
+所有 `java.util.Random` 实例都已改为延迟初始化,避免在类加载时创建:
+
+- `RandomUtils` - 使用双重检查锁定模式延迟初始化
+- `SignUtils` - 使用双重检查锁定模式延迟初始化
+- `WxCryptUtil` - 使用双重检查锁定模式延迟初始化
+
+### 2. Native Image 配置
+
+在 `weixin-java-common` 模块中添加了 GraalVM Native Image 配置文件:
+
+- `META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties`
+ - 配置 Apache HttpClient 相关类在运行时初始化,避免在构建时创建 SecureRandom 实例
+
+- `META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json`
+ - 配置反射访问的类和方法
+
+## 使用方式
+
+### Quarkus 项目配置
+
+在您的 Quarkus 项目中使用 WxJava,只需正常引入依赖即可:
+
+```xml
+
+ com.github.binarywang
+ weixin-java-miniapp
+ 4.7.8.B
+
+```
+
+### 构建 Native Image
+
+使用 Quarkus 构建原生可执行文件:
+
+```bash
+./mvnw package -Pnative
+```
+
+或使用容器构建:
+
+```bash
+./mvnw package -Pnative -Dquarkus.native.container-build=true
+```
+
+### GraalVM Native Image
+
+如果直接使用 GraalVM Native Image 工具:
+
+```bash
+native-image --no-fallback \
+ -H:+ReportExceptionStackTraces \
+ -jar your-application.jar
+```
+
+WxJava 的配置文件会自动被 Native Image 工具识别和应用。
+
+## 测试验证
+
+建议在构建 Native Image 后进行以下测试:
+
+1. 验证应用程序可以正常启动
+2. 验证微信 API 调用功能正常
+3. 验证随机字符串生成功能正常
+4. 验证加密/解密功能正常
+
+## 已知限制
+
+- 本配置主要针对 Quarkus 3.x 和 GraalVM 22.x+ 版本进行测试
+- 如果使用其他 Native Image 构建工具(如 Spring Native),可能需要额外配置
+- 部分反射使用可能需要根据实际使用的 WxJava 功能进行调整
+
+## 问题反馈
+
+如果在使用 Quarkus/GraalVM Native Image 时遇到问题,请通过以下方式反馈:
+
+1. 在 [GitHub Issues](https://github.com/binarywang/WxJava/issues) 提交问题
+2. 提供详细的错误信息和 Native Image 构建日志
+3. 说明使用的 Quarkus 版本和 GraalVM 版本
+
+## 参考资料
+
+- [Quarkus 官方文档](https://quarkus.io/)
+- [GraalVM Native Image 文档](https://www.graalvm.org/latest/reference-manual/native-image/)
+- [Quarkus Tips for Writing Native Applications](https://quarkus.io/guides/writing-native-applications-tips)
+
+## 贡献
+
+欢迎提交 PR 完善 Quarkus/GraalVM 支持!如果您发现了新的兼容性问题或有改进建议,请参考 [代码贡献指南](CONTRIBUTING.md)。
diff --git a/README.md b/README.md
index 12b516c1b7..0b86319b66 100644
--- a/README.md
+++ b/README.md
@@ -65,13 +65,14 @@
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。
2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。
3. **2024-12-30 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**!
-4. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
-5. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
-6. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
-7. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识;
-8. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。
-9. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间;
-10. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com
+4. **从 4.7.8.B 版本开始支持 Quarkus/GraalVM Native Image,详见 [【Quarkus 支持文档】](QUARKUS_SUPPORT.md)**。
+5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
+6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
+7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
+8. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识;
+9. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。
+10. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间;
+11. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com
--------------------------------
### 其他说明
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java
index bbb11992bc..a9017c0d16 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java
@@ -4,12 +4,24 @@ public class RandomUtils {
private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- private static final java.util.Random RANDOM = new java.util.Random();
+ private static volatile java.util.Random random;
+
+ private static java.util.Random getRandom() {
+ if (random == null) {
+ synchronized (RandomUtils.class) {
+ if (random == null) {
+ random = new java.util.Random();
+ }
+ }
+ }
+ return random;
+ }
public static String getRandomStr() {
StringBuilder sb = new StringBuilder();
+ java.util.Random r = getRandom();
for (int i = 0; i < 16; i++) {
- sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length())));
+ sb.append(RANDOM_STR.charAt(r.nextInt(RANDOM_STR.length())));
}
return sb.toString();
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
index 0a40d0e93c..4039580cf1 100755
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
@@ -37,6 +37,19 @@ public class WxCryptUtil {
private static final Base64 BASE64 = new Base64();
private static final Charset CHARSET = StandardCharsets.UTF_8;
+ private static volatile Random random;
+
+ private static Random getRandom() {
+ if (random == null) {
+ synchronized (WxCryptUtil.class) {
+ if (random == null) {
+ random = new Random();
+ }
+ }
+ }
+ return random;
+ }
+
private static final ThreadLocal BUILDER_LOCAL = ThreadLocal.withInitial(() -> {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -109,10 +122,10 @@ private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
*/
private static String genRandomStr() {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- Random random = new Random();
+ Random r = getRandom();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
- int number = random.nextInt(base.length());
+ int number = r.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
diff --git a/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties
new file mode 100644
index 0000000000..e1e601713f
--- /dev/null
+++ b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties
@@ -0,0 +1,4 @@
+Args = --initialize-at-run-time=org.apache.http.impl.auth.NTLMEngineImpl \
+ --initialize-at-run-time=org.apache.http.impl.auth.NTLMEngine \
+ --initialize-at-run-time=org.apache.http.impl.auth.KerberosScheme \
+ --initialize-at-run-time=org.apache.http.impl.auth.SPNegoScheme
diff --git a/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json
new file mode 100644
index 0000000000..3bf76c8dab
--- /dev/null
+++ b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json
@@ -0,0 +1,14 @@
+[
+ {
+ "name": "me.chanjar.weixin.common.util.RandomUtils",
+ "methods": [
+ {"name": "getRandomStr", "parameterTypes": []}
+ ]
+ },
+ {
+ "name": "me.chanjar.weixin.common.util.crypto.WxCryptUtil",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ }
+]
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java
index 7065d06383..37a1ef2efd 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java
@@ -33,6 +33,19 @@ public static String genRandomStr() {
return genRandomStr(32);
}
+ private static volatile Random random;
+
+ private static Random getRandom() {
+ if (random == null) {
+ synchronized (SignUtils.class) {
+ if (random == null) {
+ random = new Random();
+ }
+ }
+ }
+ return random;
+ }
+
/**
* 生成随机字符串
*
@@ -41,10 +54,10 @@ public static String genRandomStr() {
*/
public static String genRandomStr(int length) {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- Random random = new Random();
+ Random r = getRandom();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
- int number = random.nextInt(base.length());
+ int number = r.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
From d4186c49c1b0edccb32de79ae917f2abb73ccabc Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 5 Nov 2025 10:45:50 +0800
Subject: [PATCH 014/111] =?UTF-8?q?:new:=20#3494=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=B9=B3=E5=8F=B0=E6=94=B6?=
=?UTF-8?q?=E4=BB=98=E9=80=9A=E6=8F=90=E7=8E=B0=E6=8E=A5=E5=8F=A3=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=E5=9B=9E=E8=B0=83=E5=8F=82=E6=95=B0=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E5=8F=8A=E8=A1=A5=E5=85=85=E6=97=A5=E7=BB=88=E4=BD=99=E9=A2=9D?=
=?UTF-8?q?=E6=8F=90=E7=8E=B0API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../bean/ecommerce/SpWithdrawRequest.java | 17 +-
.../SubDayEndBalanceWithdrawRequest.java | 125 ++++++++
.../SubDayEndBalanceWithdrawResult.java | 99 +++++++
.../SubDayEndBalanceWithdrawStatusResult.java | 261 +++++++++++++++++
.../bean/ecommerce/SubWithdrawRequest.java | 17 +-
.../bean/ecommerce/WithdrawNotifyResult.java | 266 ++++++++++++++++++
.../wxpay/service/EcommerceService.java | 54 +++-
.../service/impl/EcommerceServiceImpl.java | 42 +++
8 files changed, 877 insertions(+), 4 deletions(-)
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java
index 0b836366d4..a3d70557f0 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java
@@ -9,7 +9,7 @@
/**
* 电商平台提现
*
- * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012476670
*
*/
@Data
@@ -88,4 +88,19 @@ public class SpWithdrawRequest implements Serializable {
@SerializedName(value = "account_type")
private String accountType;
+ /**
+ *
+ * 字段名:回调通知地址
+ * 变量名:notify_url
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+ * 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+ * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+ *
+ */
+ @SerializedName(value = "notify_url")
+ private String notifyUrl;
+
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java
new file mode 100644
index 0000000000..11749ef117
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java
@@ -0,0 +1,125 @@
+package com.github.binarywang.wxpay.bean.ecommerce;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 二级商户按日终余额预约提现
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013328143
+ *
+ *
+ * @author copilot
+ * created on 2024/12/24
+ */
+@Data
+@NoArgsConstructor
+public class SubDayEndBalanceWithdrawRequest implements Serializable {
+
+ private static final long serialVersionUID = -8745123456789012345L;
+
+ /**
+ *
+ * 字段名:二级商户号
+ * 变量名:sub_mchid
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 电商平台二级商户号,由微信支付生成并下发。
+ * 示例值:1900000109
+ *
+ */
+ @SerializedName(value = "sub_mchid")
+ private String subMchid;
+
+ /**
+ *
+ * 字段名:商户提现单号
+ * 变量名:out_request_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 必须是字母数字
+ * 示例值:20190611222222222200000000012122
+ *
+ */
+ @SerializedName(value = "out_request_no")
+ private String outRequestNo;
+
+ /**
+ *
+ * 字段名:提现金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:int64
+ * 描述:
+ * 提现金额(单位:分)
+ * 示例值:100
+ *
+ */
+ @SerializedName(value = "amount")
+ private Integer amount;
+
+ /**
+ *
+ * 字段名:备注
+ * 变量名:remark
+ * 是否必填:否
+ * 类型:string(56)
+ * 描述:
+ * 商户对提现单的备注
+ * 示例值:交易提现
+ *
+ */
+ @SerializedName(value = "remark")
+ private String remark;
+
+ /**
+ *
+ * 字段名:银行附言
+ * 变量名:bank_memo
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 展示在收款银行系统中的附言,数字、字母最长32个汉字(能否成功展示依赖银行系统支持)。
+ * 示例值:微信支付提现
+ *
+ */
+ @SerializedName(value = "bank_memo")
+ private String bankMemo;
+
+ /**
+ *
+ * 字段名:出款账户类型
+ * 变量名:account_type
+ * 是否必填:是
+ * 类型:string(16)
+ * 描述:
+ * 枚举值:
+ * BASIC:基本户
+ * OPERATION:运营账户
+ * FEES:手续费账户
+ * 示例值:BASIC
+ *
+ */
+ @SerializedName(value = "account_type")
+ private String accountType;
+
+ /**
+ *
+ * 字段名:回调通知地址
+ * 变量名:notify_url
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+ * 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+ * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+ *
+ */
+ @SerializedName(value = "notify_url")
+ private String notifyUrl;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java
new file mode 100644
index 0000000000..f64eacb7b2
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java
@@ -0,0 +1,99 @@
+package com.github.binarywang.wxpay.bean.ecommerce;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 二级商户按日终余额预约提现结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013328143
+ *
+ *
+ * @author copilot
+ * created on 2024/12/24
+ */
+@Data
+@NoArgsConstructor
+public class SubDayEndBalanceWithdrawResult implements Serializable {
+
+ private static final long serialVersionUID = -8745123456789012346L;
+
+ /**
+ *
+ * 字段名:二级商户号
+ * 变量名:sub_mchid
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 电商平台二级商户号,由微信支付生成并下发。
+ * 示例值:1900000109
+ *
+ */
+ @SerializedName(value = "sub_mchid")
+ private String subMchid;
+
+ /**
+ *
+ * 字段名:电商平台商户号
+ * 变量名:sp_mchid
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 电商平台商户号
+ * 示例值:1800000123
+ *
+ */
+ @SerializedName(value = "sp_mchid")
+ private String spMchid;
+
+ /**
+ *
+ * 字段名:商户提现单号
+ * 变量名:out_request_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户提现单号,由商户自定义生成。
+ * 示例值:20190611222222222200000000012122
+ *
+ */
+ @SerializedName(value = "out_request_no")
+ private String outRequestNo;
+
+ /**
+ *
+ * 字段名:微信支付提现单号
+ * 变量名:withdraw_id
+ * 是否必填:是
+ * 类型:string(128)
+ * 描述:
+ * 电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+ * 示例值:12321937198237912739132791732912793127931279317929791239112123
+ *
+ */
+ @SerializedName(value = "withdraw_id")
+ private String withdrawId;
+
+ /**
+ *
+ * 字段名:提现单状态
+ * 变量名:status
+ * 是否必填:是
+ * 类型:string(16)
+ * 描述:
+ * 枚举值:
+ * CREATE_SUCCESS:受理成功
+ * SUCCESS:提现成功
+ * FAIL:提现失败
+ * REFUND:提现退票
+ * CLOSE:关单
+ * INIT:业务单已创建
+ * 示例值:CREATE_SUCCESS
+ *
+ */
+ @SerializedName(value = "status")
+ private String status;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java
new file mode 100644
index 0000000000..c642ab15d2
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java
@@ -0,0 +1,261 @@
+package com.github.binarywang.wxpay.bean.ecommerce;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 查询二级商户按日终余额预约提现状态
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013328163
+ *
+ *
+ * @author copilot
+ * created on 2024/12/24
+ */
+@Data
+@NoArgsConstructor
+public class SubDayEndBalanceWithdrawStatusResult implements Serializable {
+
+ private static final long serialVersionUID = -8745123456789012347L;
+
+ /**
+ *
+ * 字段名:二级商户号
+ * 变量名:sub_mchid
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 电商平台二级商户号,由微信支付生成并下发。
+ * 示例值:1900000109
+ *
+ */
+ @SerializedName(value = "sub_mchid")
+ private String subMchid;
+
+ /**
+ *
+ * 字段名:电商平台商户号
+ * 变量名:sp_mchid
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 电商平台商户号
+ * 示例值:1800000123
+ *
+ */
+ @SerializedName(value = "sp_mchid")
+ private String spMchid;
+
+ /**
+ *
+ * 字段名:提现单状态
+ * 变量名:status
+ * 是否必填:是
+ * 类型:string(16)
+ * 描述:
+ * 枚举值:
+ * CREATE_SUCCESS:受理成功
+ * SUCCESS:提现成功
+ * FAIL:提现失败
+ * REFUND:提现退票
+ * CLOSE:关单
+ * INIT:业务单已创建
+ * 示例值:CREATE_SUCCESS
+ *
+ */
+ @SerializedName(value = "status")
+ private String status;
+
+ /**
+ *
+ * 字段名:微信支付提现单号
+ * 变量名:withdraw_id
+ * 是否必填:是
+ * 类型:string(128)
+ * 描述:
+ * 电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+ * 示例值:12321937198237912739132791732912793127931279317929791239112123
+ *
+ */
+ @SerializedName(value = "withdraw_id")
+ private String withdrawId;
+
+ /**
+ *
+ * 字段名:商户提现单号
+ * 变量名:out_request_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户提现单号,由商户自定义生成。
+ * 示例值:20190611222222222200000000012122
+ *
+ */
+ @SerializedName(value = "out_request_no")
+ private String outRequestNo;
+
+ /**
+ *
+ * 字段名:提现金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:int64
+ * 描述:
+ * 单位:分
+ * 示例值:1
+ *
+ */
+ @SerializedName(value = "amount")
+ private Integer amount;
+
+ /**
+ *
+ * 字段名:发起提现时间
+ * 变量名:create_time
+ * 是否必填:是
+ * 类型:string(29)
+ * 描述:
+ * 遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+ * YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+ * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+ * 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+ * 示例值:2015-05-20T13:29:35.120+08:00
+ *
+ */
+ @SerializedName(value = "create_time")
+ private String createTime;
+
+ /**
+ *
+ * 字段名:提现状态更新时间
+ * 变量名:update_time
+ * 是否必填:是
+ * 类型:string(29)
+ * 描述:
+ * 遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+ * YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+ * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+ * 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+ * 示例值:2015-05-20T13:29:35.120+08:00
+ *
+ */
+ @SerializedName(value = "update_time")
+ private String updateTime;
+
+ /**
+ *
+ * 字段名:失败原因
+ * 变量名:reason
+ * 是否必填:否
+ * 类型:string(255)
+ * 描述:
+ * 仅在提现失败、退票、关单时有值
+ * 示例值:卡号错误
+ *
+ */
+ @SerializedName(value = "reason")
+ private String reason;
+
+ /**
+ *
+ * 字段名:提现备注
+ * 变量名:remark
+ * 是否必填:否
+ * 类型:string(56)
+ * 描述:
+ * 商户对提现单的备注,若发起提现时未传入相应值或输入不合法,则该值为空
+ * 示例值:交易提现
+ *
+ */
+ @SerializedName(value = "remark")
+ private String remark;
+
+ /**
+ *
+ * 字段名:银行附言
+ * 变量名:bank_memo
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)。若发起提现时未传入相应值或输入不合法,则该值为空
+ * 示例值:微信提现
+ *
+ */
+ @SerializedName(value = "bank_memo")
+ private String bankMemo;
+
+ /**
+ *
+ * 字段名:出款账户类型
+ * 变量名:account_type
+ * 是否必填:是
+ * 类型:string(16)
+ * 描述:
+ * BASIC:基本户
+ * OPERATION:运营账户
+ * FEES:手续费账户
+ * 示例值:BASIC
+ *
+ */
+ @SerializedName(value = "account_type")
+ private String accountType;
+
+ /**
+ *
+ * 字段名:提现失败解决方案
+ * 变量名:solution
+ * 是否必填:否
+ * 类型:string(255)
+ * 描述:
+ * 仅在提现失败、退票、关单时有值
+ * 示例值:请修改结算银行卡信息
+ *
+ */
+ @SerializedName(value = "solution")
+ private String solution;
+
+ /**
+ *
+ * 字段名:出款户名
+ * 变量名:account_name
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 出款户名(加密)
+ * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+ *
+ */
+ @SerializedName(value = "account_name")
+ private String accountName;
+
+ /**
+ *
+ * 字段名:出款账号
+ * 变量名:account_number
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 出款账号(加密)
+ * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+ *
+ */
+ @SerializedName(value = "account_number")
+ private String accountNumber;
+
+ /**
+ *
+ * 字段名:出款银行全称(含支行)
+ * 变量名:bank_name
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 出款银行全称(含支行)(加密)
+ * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+ *
+ */
+ @SerializedName(value = "bank_name")
+ private String bankName;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java
index 3c74db24c9..541d779a30 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java
@@ -9,7 +9,7 @@
/**
* 二级商户账户余额提现
*
- * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012476652
*
*/
@Data
@@ -86,4 +86,19 @@ public class SubWithdrawRequest implements Serializable {
@SerializedName(value = "bank_memo")
private String bankMemo;
+ /**
+ *
+ * 字段名:回调通知地址
+ * 变量名:notify_url
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+ * 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+ * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+ *
+ */
+ @SerializedName(value = "notify_url")
+ private String notifyUrl;
+
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java
new file mode 100644
index 0000000000..c5223dd123
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java
@@ -0,0 +1,266 @@
+package com.github.binarywang.wxpay.bean.ecommerce;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 提现状态变更通知结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013049135
+ *
+ *
+ * @author copilot
+ * created on 2024/12/24
+ */
+@Data
+@NoArgsConstructor
+public class WithdrawNotifyResult implements Serializable {
+
+ private static final long serialVersionUID = -7451351849088368701L;
+
+ /**
+ * 源数据
+ */
+ private NotifyResponse rawData;
+
+ /**
+ *
+ * 字段名:电商平台商户号
+ * 变量名:sp_mchid
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 微信支付分配给电商平台的商户号
+ * 示例值:1900000100
+ *
+ */
+ @SerializedName(value = "sp_mchid")
+ private String spMchid;
+
+ /**
+ *
+ * 字段名:二级商户号
+ * 变量名:sub_mchid
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 微信支付分配给二级商户的商户号,仅二级商户提现时返回
+ * 示例值:1900000109
+ *
+ */
+ @SerializedName(value = "sub_mchid")
+ private String subMchid;
+
+ /**
+ *
+ * 字段名:商户提现单号
+ * 变量名:out_request_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户提现单号,由商户自定义生成
+ * 示例值:20190611222222222200000000012122
+ *
+ */
+ @SerializedName(value = "out_request_no")
+ private String outRequestNo;
+
+ /**
+ *
+ * 字段名:微信支付提现单号
+ * 变量名:withdraw_id
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 微信支付提现单号
+ * 示例值:12321002198704230011101200
+ *
+ */
+ @SerializedName(value = "withdraw_id")
+ private String withdrawId;
+
+ /**
+ *
+ * 字段名:提现状态
+ * 变量名:status
+ * 是否必填:是
+ * 类型:string(16)
+ * 描述:
+ * 提现状态:
+ * CREATE_SUCCESS:受理成功
+ * SUCCESS:提现成功
+ * FAILED:提现失败
+ * REFUND:提现退票
+ * CLOSE:关单
+ * 示例值:SUCCESS
+ *
+ */
+ @SerializedName(value = "status")
+ private String status;
+
+ /**
+ *
+ * 字段名:提现金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:int64
+ * 描述:
+ * 提现金额,单位:分(人民币)
+ * 示例值:100
+ *
+ */
+ @SerializedName(value = "amount")
+ private Integer amount;
+
+ /**
+ *
+ * 字段名:提现发起时间
+ * 变量名:create_time
+ * 是否必填:是
+ * 类型:string(29)
+ * 描述:
+ * 提现发起时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+ * YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+ * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+ * 例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日13点29分35秒
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName(value = "create_time")
+ private String createTime;
+
+ /**
+ *
+ * 字段名:提现更新时间
+ * 变量名:update_time
+ * 是否必填:是
+ * 类型:string(29)
+ * 描述:
+ * 提现更新时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+ * YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+ * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+ * 例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日13点29分35秒
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName(value = "update_time")
+ private String updateTime;
+
+ /**
+ *
+ * 字段名:失败原因
+ * 变量名:reason
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 提现失败原因,仅在提现失败、退票时有值
+ * 示例值:账户余额不足
+ *
+ */
+ @SerializedName(value = "reason")
+ private String reason;
+
+ /**
+ *
+ * 字段名:备注
+ * 变量名:remark
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 商户对提现单的备注,若提现申请时未传递,则无此字段
+ * 示例值:交易提现
+ *
+ */
+ @SerializedName(value = "remark")
+ private String remark;
+
+ /**
+ *
+ * 字段名:银行附言
+ * 变量名:bank_memo
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 展示在收款银行系统中的附言,若提现申请时未传递,则无此字段
+ * 示例值:微信支付提现
+ *
+ */
+ @SerializedName(value = "bank_memo")
+ private String bankMemo;
+
+ /**
+ *
+ * 字段名:账户类型
+ * 变量名:account_type
+ * 是否必填:否
+ * 类型:string(16)
+ * 描述:
+ * 提现账户类型,仅电商平台提现时返回:
+ * BASIC:基本账户
+ * OPERATION:运营账户
+ * FEES:手续费账户
+ * 示例值:BASIC
+ *
+ */
+ @SerializedName(value = "account_type")
+ private String accountType;
+
+ /**
+ *
+ * 字段名:提现失败解决方案
+ * 变量名:solution
+ * 是否必填:否
+ * 类型:string(255)
+ * 描述:
+ * 仅在提现失败、退票、关单时有值
+ * 示例值:请修改结算银行卡信息
+ *
+ */
+ @SerializedName(value = "solution")
+ private String solution;
+
+ /**
+ *
+ * 字段名:出款户名
+ * 变量名:account_name
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 出款户名(加密)
+ * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+ *
+ */
+ @SerializedName(value = "account_name")
+ private String accountName;
+
+ /**
+ *
+ * 字段名:出款账号
+ * 变量名:account_number
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 出款账号(加密)
+ * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+ *
+ */
+ @SerializedName(value = "account_number")
+ private String accountNumber;
+
+ /**
+ *
+ * 字段名:出款银行全称(含支行)
+ * 变量名:bank_name
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 出款银行全称(含支行)(加密)
+ * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+ *
+ */
+ @SerializedName(value = "bank_name")
+ private String bankName;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java
index 2dbb2906c3..b630ce1785 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java
@@ -417,10 +417,23 @@ public interface EcommerceService {
*/
RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+ /**
+ *
+ * 提现状态变更通知回调数据处理
+ * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013049135
+ *
+ *
+ * @param notifyData 通知数据
+ * @param header 通知头部数据,不传则表示不校验头
+ * @return 解密后通知数据 withdraw notify result
+ * @throws WxPayException the wx pay exception
+ */
+ WithdrawNotifyResult parseWithdrawNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+
/**
*
* 二级商户账户余额提现API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_2.shtml
+ * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476652
*
*
* @param request 提现请求
@@ -432,7 +445,7 @@ public interface EcommerceService {
/**
*
* 电商平台提现API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+ * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476670
*
*
* @param request 提现请求
@@ -466,6 +479,43 @@ public interface EcommerceService {
*/
SpWithdrawStatusResult querySpWithdrawByOutRequestNo(String outRequestNo) throws WxPayException;
+ /**
+ *
+ * 平台查询预约提现状态(根据微信支付预约提现单号查询)
+ * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476674
+ *
+ *
+ * @param withdrawId 微信支付提现单号
+ * @return 返回数据 return sp withdraw status result
+ * @throws WxPayException the wx pay exception
+ */
+ SpWithdrawStatusResult querySpWithdrawByWithdrawId(String withdrawId) throws WxPayException;
+
+ /**
+ *
+ * 二级商户按日终余额预约提现
+ * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013328143
+ *
+ *
+ * @param request 提现请求
+ * @return 返回数据 return day-end balance withdraw result
+ * @throws WxPayException the wx pay exception
+ */
+ SubDayEndBalanceWithdrawResult subDayEndBalanceWithdraw(SubDayEndBalanceWithdrawRequest request) throws WxPayException;
+
+ /**
+ *
+ * 查询二级商户按日终余额预约提现状态
+ * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013328163
+ *
+ *
+ * @param subMchid 二级商户号
+ * @param outRequestNo 商户提现单号
+ * @return 返回数据 return day-end balance withdraw status result
+ * @throws WxPayException the wx pay exception
+ */
+ SubDayEndBalanceWithdrawStatusResult querySubDayEndBalanceWithdraw(String subMchid, String outRequestNo) throws WxPayException;
+
/**
*
* 修改结算账号API
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
index 479520d7f7..171535c992 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
@@ -337,6 +337,27 @@ public RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHe
}
}
+ @Override
+ public WithdrawNotifyResult parseWithdrawNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+ if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) {
+ throw new WxPayException("非法请求,头部信息验证失败");
+ }
+ NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class);
+ NotifyResponse.Resource resource = response.getResource();
+ String cipherText = resource.getCiphertext();
+ String associatedData = resource.getAssociatedData();
+ String nonce = resource.getNonce();
+ String apiV3Key = this.payService.getConfig().getApiV3Key();
+ try {
+ String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
+ WithdrawNotifyResult notifyResult = GSON.fromJson(result, WithdrawNotifyResult.class);
+ notifyResult.setRawData(response);
+ return notifyResult;
+ } catch (GeneralSecurityException | IOException e) {
+ throw new WxPayException("解析报文异常!", e);
+ }
+ }
+
@Override
public SubWithdrawResult subWithdraw(SubWithdrawRequest request) throws WxPayException {
String url = String.format("%s/v3/ecommerce/fund/withdraw", this.payService.getPayBaseUrl());
@@ -365,6 +386,27 @@ public SpWithdrawStatusResult querySpWithdrawByOutRequestNo(String outRequestNo)
return GSON.fromJson(response, SpWithdrawStatusResult.class);
}
+ @Override
+ public SpWithdrawStatusResult querySpWithdrawByWithdrawId(String withdrawId) throws WxPayException {
+ String url = String.format("%s/v3/merchant/fund/withdraw/withdraw-id/%s", this.payService.getPayBaseUrl(), withdrawId);
+ String response = this.payService.getV3(url);
+ return GSON.fromJson(response, SpWithdrawStatusResult.class);
+ }
+
+ @Override
+ public SubDayEndBalanceWithdrawResult subDayEndBalanceWithdraw(SubDayEndBalanceWithdrawRequest request) throws WxPayException {
+ String url = String.format("%s/v3/ecommerce/fund/balance-withdraw", this.payService.getPayBaseUrl());
+ String response = this.payService.postV3(url, GSON.toJson(request));
+ return GSON.fromJson(response, SubDayEndBalanceWithdrawResult.class);
+ }
+
+ @Override
+ public SubDayEndBalanceWithdrawStatusResult querySubDayEndBalanceWithdraw(String subMchid, String outRequestNo) throws WxPayException {
+ String url = String.format("%s/v3/ecommerce/fund/balance-withdraw/out-request-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), outRequestNo, subMchid);
+ String response = this.payService.getV3(url);
+ return GSON.fromJson(response, SubDayEndBalanceWithdrawStatusResult.class);
+ }
+
@Override
public void modifySettlement(String subMchid, SettlementRequest request) throws WxPayException {
String url = String.format("%s/v3/apply4sub/sub_merchants/%s/modify-settlement", this.payService.getPayBaseUrl(), subMchid);
From b94106fd57a779f2a6a0b9f81f68a68d3410aca4 Mon Sep 17 00:00:00 2001
From: kongkong
Date: Wed, 12 Nov 2025 13:41:52 +0800
Subject: [PATCH 015/111] =?UTF-8?q?:art:=20#3757=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E5=BD=93?=
=?UTF-8?q?=E5=8F=AA=E8=AE=BE=E7=BD=AE=20privateCertString=20=E6=88=96=20P?=
=?UTF-8?q?rivateCertContent=20=E6=97=B6=20certSerialNo=20=E6=B2=A1?=
=?UTF-8?q?=E6=9C=89=E8=A2=AB=E6=AD=A3=E7=A1=AE=E7=94=9F=E6=88=90=E7=9A=84?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/config/WxPayConfig.java | 4 +-
.../impl/BaseWxPayServiceImplTest.java | 41 +++++++++++++++++++
2 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index 43da17f048..7e27744002 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -341,7 +341,7 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
certificate = (X509Certificate) objects[1];
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
- if (certificate == null && StringUtils.isBlank(this.getCertSerialNo()) && StringUtils.isNotBlank(this.getPrivateCertPath())) {
+ if (certificate == null && StringUtils.isBlank(this.getCertSerialNo()) && (StringUtils.isNotBlank(this.getPrivateCertPath()) || StringUtils.isNotBlank(this.getPrivateCertString())) || this.getPrivateCertContent() != null) {
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
this.privateCertContent, "privateCertPath")) {
certificate = PemUtils.loadCertificate(certInputStream);
@@ -349,7 +349,7 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
- if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) {
+ if (StringUtils.isNotBlank(this.getPublicKeyString()) || StringUtils.isNotBlank(this.getPublicKeyPath()) || this.publicKeyContent != null) {
if (StringUtils.isBlank(this.getPublicKeyId())) {
throw new WxPayException("请确保和publicKeyId配套使用");
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
index 955071e10f..bd24f188d0 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
@@ -33,6 +33,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Calendar;
@@ -976,4 +977,44 @@ public void testCreatePartnerOrderV3() throws WxPayException {
WxPayUnifiedOrderV3Result.JsapiResult result = payService.createPartnerOrderV3(TradeTypeEnum.JSAPI, request);
System.out.println(result);
}
+
+ @Test
+ public void test_certSerialNoExtractedFromPrivateCertContentOrPrivateCertString() throws Exception {
+ WxPayConfig wxPayConfig = new WxPayConfig();
+ //服务商的参数
+ wxPayConfig.setMchId("xxx");
+ wxPayConfig.setAppId("xxx");
+ wxPayConfig.setApiV3Key("xxx");
+ wxPayConfig.setPrivateKeyContent("xxx".getBytes(StandardCharsets.UTF_8));
+ wxPayConfig.setPrivateCertContent("xxx".getBytes(StandardCharsets.UTF_8)
+ );
+ wxPayConfig.setPublicKeyId("xxx");
+ wxPayConfig.setPublicKeyContent("xxx".getBytes(StandardCharsets.UTF_8));
+ //创建支付服务
+ WxPayService wxPayService = new WxPayServiceImpl();
+ wxPayService.setConfig(wxPayConfig);
+
+ String outTradeNo = RandomUtils.getRandomStr();
+ String notifyUrl = "https://api.qq.com/";
+ System.out.println("outTradeNo = " + outTradeNo);
+ WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
+ request.setOutTradeNo(outTradeNo);
+ request.setNotifyUrl(notifyUrl);
+ request.setDescription("test");
+
+ WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
+ payer.setOpenid("xxx");
+ request.setPayer(payer);
+
+ //构建金额信息
+ WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
+ //设置币种信息
+ amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+ //设置金额
+ amount.setTotal(BaseWxPayRequest.yuan2Fen(BigDecimal.ONE));
+ request.setAmount(amount);
+
+ wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
+ }
+
}
From 28fac4ec2e616f82456335cd690d9f0c7153d7d4 Mon Sep 17 00:00:00 2001
From: helloJetBase-tech <178346048+marktech0813@users.noreply.github.com>
Date: Wed, 12 Nov 2025 07:44:18 +0200
Subject: [PATCH 016/111] =?UTF-8?q?:art:=20#3756=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E4=BF=AE=E5=A4=8D=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=20API=20=E5=9B=9E=E8=B0=83=E9=AA=8C?=
=?UTF-8?q?=E7=AD=BE=E8=BF=87=E7=A8=8B=E4=B8=AD=20WxCryptUtil.decrypt=20?=
=?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8F=AF=E8=83=BD=E6=8A=9B=E5=87=BA=E5=BC=82?=
=?UTF-8?q?=E5=B8=B8=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../common/util/crypto/WxCryptUtil.java | 20 ++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
index 4039580cf1..0b0590b1e6 100755
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
@@ -333,14 +333,28 @@ public String decrypt(String cipherText) {
byte[] bytes = PKCS7Encoder.decode(original);
// 分离16位随机字符串,网络字节序和AppId
+ if (bytes == null || bytes.length < 20) {
+ throw new WxRuntimeException("解密后数据长度异常,可能为错误的密文或EncodingAESKey");
+ }
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int xmlLength = bytesNetworkOrder2Number(networkOrder);
- xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
- fromAppid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
+ // 长度边界校验,避免非法长度导致的越界/参数异常
+ int startIndex = 20;
+ int endIndex = startIndex + xmlLength;
+ if (xmlLength < 0 || endIndex > bytes.length) {
+ throw new WxRuntimeException("解密后数据格式非法:消息长度不正确,可能为错误的密文或EncodingAESKey");
+ }
+
+ xmlContent = new String(Arrays.copyOfRange(bytes, startIndex, endIndex), CHARSET);
+ fromAppid = new String(Arrays.copyOfRange(bytes, endIndex, bytes.length), CHARSET);
} catch (Exception e) {
- throw new WxRuntimeException(e);
+ if (e instanceof WxRuntimeException) {
+ throw (WxRuntimeException) e;
+ } else {
+ throw new WxRuntimeException(e);
+ }
}
// appid不相同的情况 暂时忽略这段判断
From 9ab64167d5d2f26622183c0c9bcab2ff04d116e4 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 16:47:06 +0800
Subject: [PATCH 017/111] =?UTF-8?q?:art:=20#3680=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8DPEM?=
=?UTF-8?q?=E6=A0=BC=E5=BC=8F=E7=9A=84=E7=A7=81=E9=92=A5=E5=92=8C=E8=AF=81?=
=?UTF-8?q?=E4=B9=A6=E5=A4=84=E7=90=86=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/config/WxPayConfig.java | 10 +-
.../config/WxPayConfigPrivateKeyTest.java | 116 ++++++++++++++++++
2 files changed, 125 insertions(+), 1 deletion(-)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigPrivateKeyTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index 7e27744002..efae89ce93 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -33,6 +33,7 @@
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -435,7 +436,14 @@ private InputStream loadConfigInputStream(String configString, String configPath
}
if (StringUtils.isNotEmpty(configString)) {
- configContent = Base64.getDecoder().decode(configString);
+ // 判断是否为PEM格式的字符串(包含-----BEGIN和-----END标记)
+ if (configString.contains("-----BEGIN") && configString.contains("-----END")) {
+ // PEM格式直接转为字节流,让PemUtils处理
+ configContent = configString.getBytes(StandardCharsets.UTF_8);
+ } else {
+ // 纯Base64格式,需要先解码
+ configContent = Base64.getDecoder().decode(configString);
+ }
return new ByteArrayInputStream(configContent);
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigPrivateKeyTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigPrivateKeyTest.java
new file mode 100644
index 0000000000..927e0c4125
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigPrivateKeyTest.java
@@ -0,0 +1,116 @@
+package com.github.binarywang.wxpay.config;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * Test cases for private key format handling in WxPayConfig
+ */
+public class WxPayConfigPrivateKeyTest {
+
+ @Test
+ public void testPrivateKeyStringFormat_PemFormat() {
+ WxPayConfig config = new WxPayConfig();
+
+ // Set minimal required configuration
+ config.setMchId("1234567890");
+ config.setApiV3Key("test-api-v3-key-32-characters-long");
+ config.setCertSerialNo("test-serial-number");
+
+ // Test with PEM format private key string that would previously fail
+ String pemKey = "-----BEGIN PRIVATE KEY-----\n" +
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2pK3buBufh8Vo\n" +
+ "X4sfYbZ5CcPeGMnVQTGmj0b6\n" +
+ "-----END PRIVATE KEY-----";
+
+ config.setPrivateKeyString(pemKey);
+
+ // This should not throw a "无效的密钥格式" exception immediately
+ // The actual key validation will happen during HTTP client initialization
+ // but at least the format parsing should not fail
+
+ try {
+ // Try to initialize API V3 HTTP client - this might fail for other reasons
+ // (like invalid key content) but should not fail due to format parsing
+ config.initApiV3HttpClient();
+ // If we get here without InvalidKeySpecException, the format detection worked
+ } catch (WxPayException e) {
+ // Check that it's not the specific "无效的密钥格式" error from PemUtils
+ if (e.getCause() != null &&
+ e.getCause().getMessage() != null &&
+ e.getCause().getMessage().contains("无效的密钥格式")) {
+ fail("Private key format detection failed - PEM format was not handled correctly: " + e.getMessage());
+ }
+ // Other exceptions are acceptable for this test since we're using a dummy key
+ } catch (Exception e) {
+ // Check for the specific InvalidKeySpecException that indicates format problems
+ if (e.getCause() != null &&
+ e.getCause().getMessage() != null &&
+ e.getCause().getMessage().contains("无效的密钥格式")) {
+ fail("Private key format detection failed - PEM format was not handled correctly: " + e.getMessage());
+ }
+ // Other exceptions are acceptable for this test since we're using a dummy key
+ }
+ }
+
+ @Test
+ public void testPrivateKeyStringFormat_EmptyString() {
+ WxPayConfig config = new WxPayConfig();
+
+ // Test with empty string - should not cause format errors
+ config.setPrivateKeyString("");
+
+ // This should handle empty strings gracefully
+ // No assertion needed, just ensuring no exceptions during object creation
+ assertNotNull(config);
+ }
+
+ @Test
+ public void testPrivateKeyStringFormat_NullString() {
+ WxPayConfig config = new WxPayConfig();
+
+ // Test with null string - should not cause format errors
+ config.setPrivateKeyString(null);
+
+ // This should handle null strings gracefully
+ assertNotNull(config);
+ }
+
+ @Test
+ public void testPrivateCertStringFormat_PemFormat() {
+ WxPayConfig config = new WxPayConfig();
+
+ // Set minimal required configuration
+ config.setMchId("1234567890");
+ config.setApiV3Key("test-api-v3-key-32-characters-long");
+
+ // Test with PEM format certificate string that would previously fail
+ String pemCert = "-----BEGIN CERTIFICATE-----\n" +
+ "MIICdTCCAd4CAQAwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV\n" +
+ "BAsKClRlc3QgQ2VydCBEYXRhMRswGQYDVQQDDBJUZXN0IENlcnRpZmljYXRlQ0Ew\n" +
+ "-----END CERTIFICATE-----";
+
+ config.setPrivateCertString(pemCert);
+
+ // This should not throw a format parsing exception immediately
+ // The actual certificate validation will happen during HTTP client initialization
+ // but at least the format parsing should not fail
+
+ try {
+ // Try to initialize API V3 HTTP client - this might fail for other reasons
+ // (like invalid cert content) but should not fail due to format parsing
+ config.initApiV3HttpClient();
+ // If we get here without Base64 decoding issues, the format detection worked
+ } catch (Exception e) {
+ // Check that it's not the specific Base64 decoding error
+ if (e.getCause() != null &&
+ e.getCause().getMessage() != null &&
+ e.getCause().getMessage().contains("Illegal base64 character")) {
+ fail("Certificate format detection failed - PEM format was not handled correctly: " + e.getMessage());
+ }
+ // Other exceptions are acceptable for this test since we're using a dummy cert
+ }
+ }
+}
\ No newline at end of file
From 4d4294707bf76596918be0b15b85191939bd7c51 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 16:52:49 +0800
Subject: [PATCH 018/111] =?UTF-8?q?:art:=20#3728=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8DV3?=
=?UTF-8?q?=E6=94=AF=E4=BB=98=E5=85=AC=E9=92=A5=E8=BD=AC=E8=B4=A6=E5=87=BA?=
=?UTF-8?q?=E7=8E=B0=E7=9A=84=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81=E5=A4=B1?=
=?UTF-8?q?=E8=B4=A5=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/v3/auth/PublicCertificateVerifier.java | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
index 8c9c4f3569..ac1dfbca6b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
@@ -24,9 +24,17 @@ public void setOtherVerifier(Verifier 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);
From 57e12dee298def34d8d5802eb984f520b5f158df Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 17:03:08 +0800
Subject: [PATCH 019/111] =?UTF-8?q?:bug:=20#3704=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E4=BF=AE=E5=A4=8D=E7=89=A9=E6=B5=81?=
=?UTF-8?q?=E6=9C=8D=E5=8A=A1-=E5=90=8C=E5=9F=8E=E9=85=8D=E9=80=81?=
=?UTF-8?q?=E6=9C=8D=E5=8A=A1-=E6=9F=A5=E8=AF=A2=E9=97=A8=E5=BA=97?=
=?UTF-8?q?=E4=BD=99=E9=A2=9D=E6=8E=A5=E5=8F=A3=E9=94=99=E8=AF=AF=E7=9A=84?=
=?UTF-8?q?=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/api/impl/WxMaIntracityServiceImpl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java
index 3e21dab79f..e2681f7cc7 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java
@@ -170,7 +170,7 @@ public WxMaStoreFlowResponse extends WxMaStoreFlowResponse.BasicFlowRecord> qu
@Override
public WxMaStoreBalance balanceQuery(String wxStoreId, String serviceTransId, PayMode payMode)
throws WxErrorException {
- if (wxStoreId == null && (payMode != null && payMode != PayMode.STORE)) {
+ if (wxStoreId == null && (payMode == null || payMode == PayMode.STORE)) {
throw new IllegalArgumentException("payMode是PAY_MODE_STORE或null时,必须传递wxStoreId");
}
Map request = new HashMap<>();
From 0854e4d971de8e63dd8f6ae745293e8b7478ea6f Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 17:04:27 +0800
Subject: [PATCH 020/111] =?UTF-8?q?:new:=20#3507=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=AE=9E=E7=8E=B0=E8=BF=90?=
=?UTF-8?q?=E8=90=A5=E5=B7=A5=E5=85=B7-=E5=95=86=E5=AE=B6=E8=BD=AC?=
=?UTF-8?q?=E8=B4=A6API=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...BusinessOperationTransferQueryRequest.java | 44 ++++++
.../BusinessOperationTransferQueryResult.java | 101 +++++++++++++
.../BusinessOperationTransferRequest.java | 89 ++++++++++++
.../BusinessOperationTransferResult.java | 64 +++++++++
.../wxpay/constant/WxPayConstants.java | 23 +++
.../BusinessOperationTransferExample.java | 135 ++++++++++++++++++
.../BusinessOperationTransferService.java | 82 +++++++++++
.../wxpay/service/WxPayService.java | 7 +
.../service/impl/BaseWxPayServiceImpl.java | 8 ++
.../BusinessOperationTransferServiceImpl.java | 74 ++++++++++
.../BusinessOperationTransferServiceTest.java | 93 ++++++++++++
11 files changed, 720 insertions(+)
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java
new file mode 100644
index 0000000000..f1323655a1
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java
@@ -0,0 +1,44 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 运营工具-商家转账查询请求参数
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class BusinessOperationTransferQueryRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 商户系统内部的商家单号
+ * 与transfer_bill_no二选一
+ */
+ @SerializedName("out_bill_no")
+ private String outBillNo;
+
+ /**
+ * 微信转账单号
+ * 与out_bill_no二选一
+ */
+ @SerializedName("transfer_bill_no")
+ private String transferBillNo;
+
+ /**
+ * 直连商户的appid
+ * 可选
+ */
+ @SerializedName("appid")
+ private String appid;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java
new file mode 100644
index 0000000000..0cfd8f8570
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java
@@ -0,0 +1,101 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 运营工具-商家转账查询结果
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+@Data
+@NoArgsConstructor
+public class BusinessOperationTransferQueryResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 直连商户的appid
+ */
+ @SerializedName("appid")
+ private String appid;
+
+ /**
+ * 商户系统内部的商家单号
+ */
+ @SerializedName("out_bill_no")
+ private String outBillNo;
+
+ /**
+ * 微信转账单号
+ */
+ @SerializedName("transfer_bill_no")
+ private String transferBillNo;
+
+ /**
+ * 运营工具转账场景ID
+ */
+ @SerializedName("operation_scene_id")
+ private String operationSceneId;
+
+ /**
+ * 用户在直连商户应用下的用户标示
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 收款用户姓名
+ * 已脱敏
+ */
+ @SerializedName("user_name")
+ private String userName;
+
+ /**
+ * 转账金额
+ * 单位为"分"
+ */
+ @SerializedName("transfer_amount")
+ private Integer transferAmount;
+
+ /**
+ * 转账备注
+ */
+ @SerializedName("transfer_remark")
+ private String transferRemark;
+
+ /**
+ * 转账状态
+ * WAIT_PAY:等待确认
+ * PROCESSING:转账中
+ * SUCCESS:转账成功
+ * FAIL:转账失败
+ * REFUND:已退款
+ */
+ @SerializedName("transfer_state")
+ private String transferState;
+
+ /**
+ * 发起转账的时间
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ * 转账更新时间
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("update_time")
+ private String updateTime;
+
+ /**
+ * 失败原因
+ * 当transfer_state为FAIL时返回
+ */
+ @SerializedName("fail_reason")
+ private String failReason;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
new file mode 100644
index 0000000000..91d9438833
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
@@ -0,0 +1,89 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.github.binarywang.wxpay.v3.SpecEncrypt;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 运营工具-商家转账请求参数
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class BusinessOperationTransferRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 直连商户的appid
+ * 必须
+ */
+ @SerializedName("appid")
+ private String appid;
+
+ /**
+ * 商户系统内部的商家单号
+ * 必须,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
+ */
+ @SerializedName("out_bill_no")
+ private String outBillNo;
+
+ /**
+ * 运营工具转账场景ID
+ * 必须,用于标识运营工具转账的具体业务场景
+ */
+ @SerializedName("operation_scene_id")
+ private String operationSceneId;
+
+ /**
+ * 用户在直连商户应用下的用户标示
+ * 必须
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 收款用户姓名
+ * 可选,传入则校验收款用户姓名
+ * 使用RSA加密,使用OAEP填充方式
+ */
+ @SpecEncrypt
+ @SerializedName("user_name")
+ private String userName;
+
+ /**
+ * 转账金额
+ * 必须,单位为"分"
+ */
+ @SerializedName("transfer_amount")
+ private Integer transferAmount;
+
+ /**
+ * 转账备注
+ * 必须,会在转账成功消息和转账详情页向用户展示
+ */
+ @SerializedName("transfer_remark")
+ private String transferRemark;
+
+ /**
+ * 用户收款感知
+ * 可选,用于在转账成功消息中向用户展示特定内容
+ */
+ @SerializedName("user_recv_perception")
+ private String userRecvPerception;
+
+ /**
+ * 异步接收微信支付转账结果通知的回调地址
+ * 可选,通知URL必须为外网可以正常访问的地址,不能携带查询参数
+ */
+ @SerializedName("notify_url")
+ private String notifyUrl;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
new file mode 100644
index 0000000000..a380d6133e
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
@@ -0,0 +1,64 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 运营工具-商家转账结果
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+@Data
+@NoArgsConstructor
+public class BusinessOperationTransferResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 商户系统内部的商家单号
+ */
+ @SerializedName("out_bill_no")
+ private String outBillNo;
+
+ /**
+ * 微信转账单号
+ * 微信商家转账系统返回的唯一标识
+ */
+ @SerializedName("transfer_bill_no")
+ private String transferBillNo;
+
+ /**
+ * 转账状态
+ * WAIT_PAY:等待确认
+ * PROCESSING:转账中
+ * SUCCESS:转账成功
+ * FAIL:转账失败
+ * REFUND:已退款
+ */
+ @SerializedName("transfer_state")
+ private String transferState;
+
+ /**
+ * 发起转账的时间
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ * 转账更新时间
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("update_time")
+ private String updateTime;
+
+ /**
+ * 失败原因
+ * 当transfer_state为FAIL时返回
+ */
+ @SerializedName("fail_reason")
+ private String failReason;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
index e8a6b6acb3..b1a57ccc0f 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
@@ -412,6 +412,29 @@ public static class TransformSceneId {
public static final String CASH_MARKETING = "1001";
}
+ /**
+ * 【运营工具转账场景ID】 运营工具专用转账场景,用于商户日常运营活动
+ *
+ * @see 运营工具-商家转账API
+ */
+ @UtilityClass
+ public static class OperationSceneId {
+ /**
+ * 运营工具现金营销
+ */
+ public static final String OPERATION_CASH_MARKETING = "2001";
+
+ /**
+ * 运营工具佣金报酬
+ */
+ public static final String OPERATION_COMMISSION = "2002";
+
+ /**
+ * 运营工具推广奖励
+ */
+ public static final String OPERATION_PROMOTION = "2003";
+ }
+
/**
* 用户收款感知
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
new file mode 100644
index 0000000000..d11738816b
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
@@ -0,0 +1,135 @@
+package com.github.binarywang.wxpay.example;
+
+import com.github.binarywang.wxpay.bean.transfer.*;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.BusinessOperationTransferService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+
+/**
+ * 运营工具-商家转账API使用示例
+ *
+ * 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+public class BusinessOperationTransferExample {
+
+ private WxPayService wxPayService;
+ private BusinessOperationTransferService businessOperationTransferService;
+
+ public void init() {
+ // 初始化配置
+ WxPayConfig config = new WxPayConfig();
+ config.setAppId("your_app_id");
+ config.setMchId("your_mch_id");
+ config.setMchKey("your_mch_key");
+ config.setKeyPath("path_to_your_cert.p12");
+
+ // 初始化服务
+ wxPayService = new WxPayServiceImpl();
+ wxPayService.setConfig(config);
+ businessOperationTransferService = wxPayService.getBusinessOperationTransferService();
+ }
+
+ /**
+ * 发起运营工具转账示例
+ */
+ public void createOperationTransferExample() {
+ try {
+ // 构建转账请求
+ BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
+ .appid("your_app_id") // 应用ID
+ .outBillNo("OT" + System.currentTimeMillis()) // 商户转账单号
+ .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID
+ .openid("user_openid") // 用户openid
+ .userName("张三") // 用户姓名(可选)
+ .transferAmount(100) // 转账金额,单位分
+ .transferRemark("运营活动奖励") // 转账备注
+ .userRecvPerception(WxPayConstants.UserRecvPerception.CASH_MARKETING.CASH) // 用户收款感知
+ .notifyUrl("https://your-domain.com/notify") // 回调通知地址
+ .build();
+
+ // 发起转账
+ BusinessOperationTransferResult result = businessOperationTransferService.createOperationTransfer(request);
+
+ System.out.println("转账成功!");
+ System.out.println("商户单号: " + result.getOutBillNo());
+ System.out.println("微信转账单号: " + result.getTransferBillNo());
+ System.out.println("转账状态: " + result.getTransferState());
+ System.out.println("创建时间: " + result.getCreateTime());
+
+ } catch (WxPayException e) {
+ System.err.println("转账失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 通过商户单号查询转账结果示例
+ */
+ public void queryByOutBillNoExample() {
+ try {
+ String outBillNo = "OT1640995200000"; // 商户转账单号
+
+ BusinessOperationTransferQueryResult result = businessOperationTransferService
+ .queryOperationTransferByOutBillNo(outBillNo);
+
+ System.out.println("查询成功!");
+ System.out.println("商户单号: " + result.getOutBillNo());
+ System.out.println("微信转账单号: " + result.getTransferBillNo());
+ System.out.println("转账状态: " + result.getTransferState());
+ System.out.println("转账金额: " + result.getTransferAmount() + "分");
+ System.out.println("创建时间: " + result.getCreateTime());
+ System.out.println("更新时间: " + result.getUpdateTime());
+
+ } catch (WxPayException e) {
+ System.err.println("查询失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 通过微信转账单号查询转账结果示例
+ */
+ public void queryByTransferBillNoExample() {
+ try {
+ String transferBillNo = "1040000071100999991182020050700019480001"; // 微信转账单号
+
+ BusinessOperationTransferQueryResult result = businessOperationTransferService
+ .queryOperationTransferByTransferBillNo(transferBillNo);
+
+ System.out.println("查询成功!");
+ System.out.println("商户单号: " + result.getOutBillNo());
+ System.out.println("微信转账单号: " + result.getTransferBillNo());
+ System.out.println("运营场景ID: " + result.getOperationSceneId());
+ System.out.println("转账状态: " + result.getTransferState());
+
+ } catch (WxPayException e) {
+ System.err.println("查询失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 使用配置示例
+ */
+ public static void main(String[] args) {
+ BusinessOperationTransferExample example = new BusinessOperationTransferExample();
+
+ // 初始化配置
+ example.init();
+
+ // 1. 发起运营工具转账
+ example.createOperationTransferExample();
+
+ // 2. 查询转账结果
+ // example.queryByOutBillNoExample();
+
+ // 3. 通过微信转账单号查询
+ // example.queryByTransferBillNoExample();
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java
new file mode 100644
index 0000000000..740c2af83f
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java
@@ -0,0 +1,82 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferRequest;
+import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferResult;
+import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferQueryRequest;
+import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferQueryResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * 运营工具-商家转账API
+ *
+ * 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+public interface BusinessOperationTransferService {
+
+ /**
+ *
+ * 发起运营工具商家转账
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills
+ *
+ * 文档地址:运营工具-商家转账API
+ *
+ *
+ * @param request 运营工具转账请求参数
+ * @return BusinessOperationTransferResult 转账结果
+ * @throws WxPayException 微信支付异常
+ */
+ BusinessOperationTransferResult createOperationTransfer(BusinessOperationTransferRequest request) throws WxPayException;
+
+ /**
+ *
+ * 查询运营工具转账结果
+ *
+ * 请求方式:GET(HTTPS)
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+ *
+ * 文档地址:运营工具-商家转账API
+ *
+ *
+ * @param request 查询请求参数
+ * @return BusinessOperationTransferQueryResult 查询结果
+ * @throws WxPayException 微信支付异常
+ */
+ BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOperationTransferQueryRequest request) throws WxPayException;
+
+ /**
+ *
+ * 通过商户单号查询运营工具转账结果
+ *
+ * 请求方式:GET(HTTPS)
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+ *
+ * 文档地址:运营工具-商家转账API
+ *
+ *
+ * @param outBillNo 商户单号
+ * @return BusinessOperationTransferQueryResult 查询结果
+ * @throws WxPayException 微信支付异常
+ */
+ BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException;
+
+ /**
+ *
+ * 通过微信转账单号查询运营工具转账结果
+ *
+ * 请求方式:GET(HTTPS)
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}
+ *
+ * 文档地址:运营工具-商家转账API
+ *
+ *
+ * @param transferBillNo 微信转账单号
+ * @return BusinessOperationTransferQueryResult 查询结果
+ * @throws WxPayException 微信支付异常
+ */
+ BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index 4ee5226d3d..775915b858 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -1646,6 +1646,13 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
*/
TransferService getTransferService();
+ /**
+ * 获取运营工具-商家转账服务类
+ *
+ * @return the business operation transfer service
+ */
+ BusinessOperationTransferService getBusinessOperationTransferService();
+
/**
* 获取服务商支付分服务类
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 3884881b8d..e5c9abf53b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -130,6 +130,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BrandMerchantTransferService brandMerchantTransferService = new BrandMerchantTransferServiceImpl(this);
+ @Getter
+ private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this);
+
protected Map configMap = new ConcurrentHashMap<>();
@Override
@@ -1415,4 +1418,9 @@ public BankService getBankService() {
public TransferService getTransferService() {
return transferService;
}
+
+ @Override
+ public BusinessOperationTransferService getBusinessOperationTransferService() {
+ return businessOperationTransferService;
+ }
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java
new file mode 100644
index 0000000000..5e74bdbaab
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java
@@ -0,0 +1,74 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.transfer.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.BusinessOperationTransferService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * 运营工具-商家转账API实现
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class BusinessOperationTransferServiceImpl implements BusinessOperationTransferService {
+
+ private static final Gson GSON = new GsonBuilder().create();
+ private final WxPayService wxPayService;
+
+ @Override
+ public BusinessOperationTransferResult createOperationTransfer(BusinessOperationTransferRequest request) throws WxPayException {
+ // 设置默认appid
+ if (StringUtils.isEmpty(request.getAppid())) {
+ request.setAppid(this.wxPayService.getConfig().getAppId());
+ }
+
+ String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills", this.wxPayService.getPayBaseUrl());
+
+ // 如果传入了用户姓名,需要进行RSA加密
+ if (StringUtils.isNotEmpty(request.getUserName())) {
+ X509Certificate validCertificate = this.wxPayService.getConfig().getVerifier().getValidCertificate();
+ RsaCryptoUtil.encryptFields(request, validCertificate);
+ }
+
+ String response = wxPayService.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(response, BusinessOperationTransferResult.class);
+ }
+
+ @Override
+ public BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOperationTransferQueryRequest request) throws WxPayException {
+ if (StringUtils.isNotEmpty(request.getOutBillNo())) {
+ return queryOperationTransferByOutBillNo(request.getOutBillNo());
+ } else if (StringUtils.isNotEmpty(request.getTransferBillNo())) {
+ return queryOperationTransferByTransferBillNo(request.getTransferBillNo());
+ } else {
+ throw new WxPayException("商户单号(out_bill_no)和微信转账单号(transfer_bill_no)必须提供其中一个");
+ }
+ }
+
+ @Override
+ public BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException {
+ String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/%s",
+ this.wxPayService.getPayBaseUrl(), outBillNo);
+ String response = wxPayService.getV3(url);
+ return GSON.fromJson(response, BusinessOperationTransferQueryResult.class);
+ }
+
+ @Override
+ public BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException {
+ String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills/transfer-bill-no/%s",
+ this.wxPayService.getPayBaseUrl(), transferBillNo);
+ String response = wxPayService.getV3(url);
+ return GSON.fromJson(response, BusinessOperationTransferQueryResult.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
new file mode 100644
index 0000000000..4107be4347
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
@@ -0,0 +1,93 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.transfer.*;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 运营工具-商家转账API测试
+ *
+ * @author WxJava Team
+ */
+public class BusinessOperationTransferServiceTest {
+
+ private WxPayService wxPayService;
+
+ @BeforeClass
+ public void setup() {
+ WxPayConfig config = new WxPayConfig();
+ config.setAppId("test_app_id");
+ config.setMchId("test_mch_id");
+
+ wxPayService = new WxPayServiceImpl();
+ wxPayService.setConfig(config);
+ }
+
+ @Test
+ public void testServiceInitialization() {
+ BusinessOperationTransferService service = this.wxPayService.getBusinessOperationTransferService();
+ assertThat(service).isNotNull();
+ }
+
+ @Test
+ public void testRequestBuilder() {
+ BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
+ .appid("test_app_id")
+ .outBillNo("OT" + System.currentTimeMillis())
+ .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING)
+ .openid("test_openid")
+ .transferAmount(100)
+ .transferRemark("测试转账")
+ .userRecvPerception(WxPayConstants.UserRecvPerception.CASH_MARKETING.CASH)
+ .build();
+
+ assertThat(request.getAppid()).isEqualTo("test_app_id");
+ assertThat(request.getOperationSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING);
+ assertThat(request.getTransferAmount()).isEqualTo(100);
+ assertThat(request.getTransferRemark()).isEqualTo("测试转账");
+ }
+
+ @Test
+ public void testQueryRequestBuilder() {
+ BusinessOperationTransferQueryRequest request = BusinessOperationTransferQueryRequest.newBuilder()
+ .outBillNo("OT123456789")
+ .appid("test_app_id")
+ .build();
+
+ assertThat(request.getOutBillNo()).isEqualTo("OT123456789");
+ assertThat(request.getAppid()).isEqualTo("test_app_id");
+ }
+
+ @Test
+ public void testConstants() {
+ // 测试运营工具转账场景ID常量
+ assertThat(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING).isEqualTo("2001");
+ assertThat(WxPayConstants.OperationSceneId.OPERATION_COMMISSION).isEqualTo("2002");
+ assertThat(WxPayConstants.OperationSceneId.OPERATION_PROMOTION).isEqualTo("2003");
+ }
+
+ @Test
+ public void testResultClasses() {
+ // 测试结果类的基本功能
+ BusinessOperationTransferResult result = new BusinessOperationTransferResult();
+ result.setOutBillNo("test_out_bill_no");
+ result.setTransferBillNo("test_transfer_bill_no");
+ result.setTransferState("SUCCESS");
+
+ assertThat(result.getOutBillNo()).isEqualTo("test_out_bill_no");
+ assertThat(result.getTransferBillNo()).isEqualTo("test_transfer_bill_no");
+ assertThat(result.getTransferState()).isEqualTo("SUCCESS");
+
+ BusinessOperationTransferQueryResult queryResult = new BusinessOperationTransferQueryResult();
+ queryResult.setOperationSceneId("2001");
+ queryResult.setTransferAmount(100);
+
+ assertThat(queryResult.getOperationSceneId()).isEqualTo("2001");
+ assertThat(queryResult.getTransferAmount()).isEqualTo(100);
+ }
+}
\ No newline at end of file
From 69a2ab9cc751fe1726ee474616f77335689185d6 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 17:09:29 +0800
Subject: [PATCH 021/111] =?UTF-8?q?:new:=20#3720=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=AE=9E=E7=8E=B0=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=8A=BC=E9=87=91=E6=94=AF=E4=BB=98=E7=9A=84=E7=9B=B8?=
=?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../bean/request/WxDepositConsumeRequest.java | 96 ++++++++
.../request/WxDepositOrderQueryRequest.java | 69 ++++++
.../bean/request/WxDepositRefundRequest.java | 115 +++++++++
.../request/WxDepositUnfreezeRequest.java | 96 ++++++++
.../request/WxDepositUnifiedOrderRequest.java | 223 ++++++++++++++++++
.../bean/result/WxDepositConsumeResult.java | 103 ++++++++
.../result/WxDepositOrderQueryResult.java | 152 ++++++++++++
.../bean/result/WxDepositRefundResult.java | 103 ++++++++
.../bean/result/WxDepositUnfreezeResult.java | 103 ++++++++
.../result/WxDepositUnifiedOrderResult.java | 89 +++++++
.../wxpay/service/WxDepositService.java | 90 +++++++
.../wxpay/service/WxPayService.java | 7 +
.../service/impl/BaseWxPayServiceImpl.java | 3 +
.../service/impl/WxDepositServiceImpl.java | 84 +++++++
.../service/impl/WxDepositServiceTest.java | 135 +++++++++++
15 files changed, 1468 insertions(+)
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositConsumeRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositConsumeResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositConsumeRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositConsumeRequest.java
new file mode 100644
index 0000000000..b99093cd44
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositConsumeRequest.java
@@ -0,0 +1,96 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.*;
+import me.chanjar.weixin.common.annotation.Required;
+
+import java.util.Map;
+
+/**
+ *
+ * 押金消费请求
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=4
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositConsumeRequest extends BaseWxPayRequest {
+
+ /**
+ *
+ * 微信押金订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1009660380201506130728806387
+ * 微信押金订单号
+ *
+ */
+ @Required
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户消费单号
+ * out_trade_no
+ * 是
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的消费单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
+ *
+ */
+ @Required
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 消费金额
+ * consume_fee
+ * 是
+ * Int
+ * 88
+ * 消费金额,单位为分,不能大于押金金额
+ *
+ */
+ @Required
+ @XStreamAlias("consume_fee")
+ private Integer consumeFee;
+
+ /**
+ *
+ * 消费描述
+ * consume_desc
+ * 否
+ * String(128)
+ * 单车使用费
+ * 对一笔消费的具体描述信息
+ *
+ */
+ @XStreamAlias("consume_desc")
+ private String consumeDesc;
+
+ @Override
+ protected void checkConstraints() throws WxPayException {
+ // No additional constraints beyond @Required fields
+ }
+
+ @Override
+ protected void storeMap(Map map) {
+ map.put("transaction_id", transactionId);
+ map.put("out_trade_no", outTradeNo);
+ map.put("consume_fee", consumeFee.toString());
+ if (consumeDesc != null) {
+ map.put("consume_desc", consumeDesc);
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java
new file mode 100644
index 0000000000..d087649fb5
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java
@@ -0,0 +1,69 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.*;
+import me.chanjar.weixin.common.annotation.Required;
+
+import java.util.Map;
+
+/**
+ *
+ * 查询押金订单请求
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=3
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositOrderQueryRequest extends BaseWxPayRequest {
+
+ /**
+ *
+ * 微信订单号
+ * transaction_id
+ * 否
+ * String(32)
+ * 1009660380201506130728806387
+ * 微信的订单号,优先使用
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户订单号
+ * out_trade_no
+ * 否
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的订单号,当没提供transaction_id时需要传这个
+ *
+ */
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ @Override
+ protected void checkConstraints() throws WxPayException {
+ if (transactionId == null && outTradeNo == null) {
+ throw new WxPayException("transaction_id 和 out_trade_no 不能同时为空");
+ }
+ }
+
+ @Override
+ protected void storeMap(Map map) {
+ if (transactionId != null) {
+ map.put("transaction_id", transactionId);
+ }
+ if (outTradeNo != null) {
+ map.put("out_trade_no", outTradeNo);
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java
new file mode 100644
index 0000000000..65394be4a9
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java
@@ -0,0 +1,115 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.*;
+import me.chanjar.weixin.common.annotation.Required;
+
+import java.util.Map;
+
+/**
+ *
+ * 押金退款请求
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=6
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositRefundRequest extends BaseWxPayRequest {
+
+ /**
+ *
+ * 微信押金订单号
+ * transaction_id
+ * 否
+ * String(32)
+ * 1009660380201506130728806387
+ * 微信押金订单号,与out_trade_no二选一
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户押金订单号
+ * out_trade_no
+ * 否
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的押金订单号,与transaction_id二选一
+ *
+ */
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 商户退款单号
+ * out_refund_no
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
+ *
+ */
+ @Required
+ @XStreamAlias("out_refund_no")
+ private String outRefundNo;
+
+ /**
+ *
+ * 退款金额
+ * refund_fee
+ * 是
+ * Int
+ * 100
+ * 退款总金额,订单总金额,单位为分,只能为整数
+ *
+ */
+ @Required
+ @XStreamAlias("refund_fee")
+ private Integer refundFee;
+
+ /**
+ *
+ * 退款原因
+ * refund_desc
+ * 否
+ * String(80)
+ * 商品已售完
+ * 若商户传入,会在下发给用户的退款消息中体现退款原因
+ *
+ */
+ @XStreamAlias("refund_desc")
+ private String refundDesc;
+
+ @Override
+ protected void checkConstraints() throws WxPayException {
+ if (transactionId == null && outTradeNo == null) {
+ throw new WxPayException("transaction_id 和 out_trade_no 不能同时为空");
+ }
+ }
+
+ @Override
+ protected void storeMap(Map map) {
+ if (transactionId != null) {
+ map.put("transaction_id", transactionId);
+ }
+ if (outTradeNo != null) {
+ map.put("out_trade_no", outTradeNo);
+ }
+ map.put("out_refund_no", outRefundNo);
+ map.put("refund_fee", refundFee.toString());
+ if (refundDesc != null) {
+ map.put("refund_desc", refundDesc);
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java
new file mode 100644
index 0000000000..63980fc00f
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java
@@ -0,0 +1,96 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.*;
+import me.chanjar.weixin.common.annotation.Required;
+
+import java.util.Map;
+
+/**
+ *
+ * 押金撤销请求
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=5
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositUnfreezeRequest extends BaseWxPayRequest {
+
+ /**
+ *
+ * 微信押金订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1009660380201506130728806387
+ * 微信押金订单号
+ *
+ */
+ @Required
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户撤销单号
+ * out_trade_no
+ * 是
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的撤销单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
+ *
+ */
+ @Required
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 撤销金额
+ * unfreeze_fee
+ * 是
+ * Int
+ * 99
+ * 撤销金额,单位为分,不能大于剩余押金金额
+ *
+ */
+ @Required
+ @XStreamAlias("unfreeze_fee")
+ private Integer unfreezeFee;
+
+ /**
+ *
+ * 撤销原因
+ * unfreeze_desc
+ * 否
+ * String(128)
+ * 用户主动取消
+ * 对一笔撤销的具体原因描述
+ *
+ */
+ @XStreamAlias("unfreeze_desc")
+ private String unfreezeDesc;
+
+ @Override
+ protected void checkConstraints() throws WxPayException {
+ // No additional constraints beyond @Required fields
+ }
+
+ @Override
+ protected void storeMap(Map map) {
+ map.put("transaction_id", transactionId);
+ map.put("out_trade_no", outTradeNo);
+ map.put("unfreeze_fee", unfreezeFee.toString());
+ if (unfreezeDesc != null) {
+ map.put("unfreeze_desc", unfreezeDesc);
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java
new file mode 100644
index 0000000000..cffb9a34e5
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java
@@ -0,0 +1,223 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.*;
+import me.chanjar.weixin.common.annotation.Required;
+
+import java.util.Map;
+
+/**
+ *
+ * 押金下单请求
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=2
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositUnifiedOrderRequest extends BaseWxPayRequest {
+
+ /**
+ *
+ * 押金商品描述
+ * body
+ * 是
+ * String(128)
+ * 共享单车押金
+ * 押金商品描述
+ *
+ */
+ @Required
+ @XStreamAlias("body")
+ private String body;
+
+ /**
+ *
+ * 商户订单号
+ * out_trade_no
+ * 是
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
+ *
+ */
+ @Required
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 押金金额
+ * total_fee
+ * 是
+ * Int
+ * 99
+ * 押金金额,单位为分,只能为整数,详见支付金额
+ *
+ */
+ @Required
+ @XStreamAlias("total_fee")
+ private Integer totalFee;
+
+ /**
+ *
+ * 终端IP
+ * spbill_create_ip
+ * 是
+ * String(16)
+ * 123.12.12.123
+ * 用户端实际ip
+ *
+ */
+ @Required
+ @XStreamAlias("spbill_create_ip")
+ private String spbillCreateIp;
+
+ /**
+ *
+ * 通知地址
+ * notify_url
+ * 是
+ * String(256)
+ * http://www.weixin.qq.com/wxpay/pay.php
+ * 接收微信支付异步通知回调地址
+ *
+ */
+ @Required
+ @XStreamAlias("notify_url")
+ private String notifyUrl;
+
+ /**
+ *
+ * 交易类型
+ * trade_type
+ * 是
+ * String(16)
+ * JSAPI
+ * 交易类型,取值如下:JSAPI,NATIVE,APP,WAP
+ *
+ */
+ @Required
+ @XStreamAlias("trade_type")
+ private String tradeType;
+
+ /**
+ *
+ * 用户标识
+ * openid
+ * 否
+ * String(128)
+ * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ * trade_type=JSAPI时,此参数必传,用户在商户appid下的唯一标识。
+ *
+ */
+ @XStreamAlias("openid")
+ private String openid;
+
+ /**
+ *
+ * 商品详情
+ * detail
+ * 否
+ * String(8192)
+ * 详情
+ * 商品名称明细列表
+ *
+ */
+ @XStreamAlias("detail")
+ private String detail;
+
+ /**
+ *
+ * 附加数据
+ * attach
+ * 否
+ * String(127)
+ * 深圳分店
+ * 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
+ *
+ */
+ @XStreamAlias("attach")
+ private String attach;
+
+ /**
+ *
+ * 货币类型
+ * fee_type
+ * 否
+ * String(16)
+ * CNY
+ * 符合ISO 4217标准的三位字母代码,默认人民币:CNY
+ *
+ */
+ @XStreamAlias("fee_type")
+ private String feeType;
+
+ /**
+ *
+ * 交易起始时间
+ * time_start
+ * 否
+ * String(14)
+ * 20091225091010
+ * 订单生成时间,格式为yyyyMMddHHmmss
+ *
+ */
+ @XStreamAlias("time_start")
+ private String timeStart;
+
+ /**
+ *
+ * 交易结束时间
+ * time_expire
+ * 否
+ * String(14)
+ * 20091227091010
+ * 订单失效时间,格式为yyyyMMddHHmmss
+ *
+ */
+ @XStreamAlias("time_expire")
+ private String timeExpire;
+
+ @Override
+ protected void checkConstraints() throws WxPayException {
+ if ("JSAPI".equals(this.tradeType) && this.openid == null) {
+ throw new WxPayException("当trade_type为JSAPI时,openid为必填参数");
+ }
+ }
+
+ @Override
+ protected void storeMap(Map map) {
+ map.put("body", body);
+ map.put("out_trade_no", outTradeNo);
+ map.put("total_fee", totalFee.toString());
+ map.put("spbill_create_ip", spbillCreateIp);
+ map.put("notify_url", notifyUrl);
+ map.put("trade_type", tradeType);
+ if (openid != null) {
+ map.put("openid", openid);
+ }
+ if (detail != null) {
+ map.put("detail", detail);
+ }
+ if (attach != null) {
+ map.put("attach", attach);
+ }
+ if (feeType != null) {
+ map.put("fee_type", feeType);
+ }
+ if (timeStart != null) {
+ map.put("time_start", timeStart);
+ }
+ if (timeExpire != null) {
+ map.put("time_expire", timeExpire);
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositConsumeResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositConsumeResult.java
new file mode 100644
index 0000000000..dfb1bd3e69
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositConsumeResult.java
@@ -0,0 +1,103 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Document;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 押金消费结果
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=4
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositConsumeResult extends BaseWxPayResult implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 微信订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 微信支付押金订单号
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户消费单号
+ * out_trade_no
+ * 是
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的消费单号
+ *
+ */
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 消费金额
+ * consume_fee
+ * 是
+ * Int
+ * 88
+ * 本次消费的金额,单位为分
+ *
+ */
+ @XStreamAlias("consume_fee")
+ private Integer consumeFee;
+
+ /**
+ *
+ * 剩余押金
+ * remain_fee
+ * 是
+ * Int
+ * 11
+ * 剩余押金金额,单位为分
+ *
+ */
+ @XStreamAlias("remain_fee")
+ private Integer remainFee;
+
+ /**
+ *
+ * 消费时间
+ * time_end
+ * 是
+ * String(14)
+ * 20141030133525
+ * 消费完成时间,格式为yyyyMMddHHmmss
+ *
+ */
+ @XStreamAlias("time_end")
+ private String timeEnd;
+
+ @Override
+ protected void loadXml(Document d) {
+ transactionId = readXmlString(d, "transaction_id");
+ outTradeNo = readXmlString(d, "out_trade_no");
+ consumeFee = readXmlInteger(d, "consume_fee");
+ remainFee = readXmlInteger(d, "remain_fee");
+ timeEnd = readXmlString(d, "time_end");
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java
new file mode 100644
index 0000000000..66a5acb658
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java
@@ -0,0 +1,152 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Document;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 查询押金订单结果
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=3
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositOrderQueryResult extends BaseWxPayResult implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 微信订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 微信支付订单号
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户订单号
+ * out_trade_no
+ * 是
+ * String(32)
+ * 20150806125346
+ * 商户系统内部订单号
+ *
+ */
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 交易状态
+ * trade_state
+ * 是
+ * String(32)
+ * SUCCESS
+ * 交易状态:
+ * SUCCESS—支付成功
+ * REFUND—转入退款
+ * NOTPAY—未支付
+ * CLOSED—已关闭
+ * REVOKED—已撤销(付款码支付)
+ * USERPAYING—用户支付中(付款码支付)
+ * PAYERROR—支付失败(其他原因,如银行返回失败)
+ *
+ */
+ @XStreamAlias("trade_state")
+ private String tradeState;
+
+ /**
+ *
+ * 交易状态描述
+ * trade_state_desc
+ * 是
+ * String(256)
+ * 支付成功
+ * 对当前查询订单状态的描述和下一步操作的指引
+ *
+ */
+ @XStreamAlias("trade_state_desc")
+ private String tradeStateDesc;
+
+ /**
+ *
+ * 押金金额
+ * total_fee
+ * 否
+ * Int
+ * 99
+ * 订单总金额,单位为分
+ *
+ */
+ @XStreamAlias("total_fee")
+ private Integer totalFee;
+
+ /**
+ *
+ * 现金支付金额
+ * cash_fee
+ * 否
+ * Int
+ * 99
+ * 现金支付金额订单现金支付金额
+ *
+ */
+ @XStreamAlias("cash_fee")
+ private Integer cashFee;
+
+ /**
+ *
+ * 支付完成时间
+ * time_end
+ * 否
+ * String(14)
+ * 20141030133525
+ * 订单支付时间,格式为yyyyMMddHHmmss
+ *
+ */
+ @XStreamAlias("time_end")
+ private String timeEnd;
+
+ /**
+ *
+ * 剩余押金
+ * remain_fee
+ * 否
+ * Int
+ * 88
+ * 剩余押金金额,单位为分
+ *
+ */
+ @XStreamAlias("remain_fee")
+ private Integer remainFee;
+
+ @Override
+ protected void loadXml(Document d) {
+ transactionId = readXmlString(d, "transaction_id");
+ outTradeNo = readXmlString(d, "out_trade_no");
+ tradeState = readXmlString(d, "trade_state");
+ tradeStateDesc = readXmlString(d, "trade_state_desc");
+ totalFee = readXmlInteger(d, "total_fee");
+ cashFee = readXmlInteger(d, "cash_fee");
+ timeEnd = readXmlString(d, "time_end");
+ remainFee = readXmlInteger(d, "remain_fee");
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java
new file mode 100644
index 0000000000..7c25b534a5
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java
@@ -0,0 +1,103 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Document;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 押金退款结果
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=6
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositRefundResult extends BaseWxPayResult implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 微信订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 微信支付押金订单号
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户退款单号
+ * out_refund_no
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 商户系统内部的退款单号
+ *
+ */
+ @XStreamAlias("out_refund_no")
+ private String outRefundNo;
+
+ /**
+ *
+ * 微信退款单号
+ * refund_id
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 微信退款单号
+ *
+ */
+ @XStreamAlias("refund_id")
+ private String refundId;
+
+ /**
+ *
+ * 退款金额
+ * refund_fee
+ * 是
+ * Int
+ * 100
+ * 退款总金额,单位为分,可以做部分退款
+ *
+ */
+ @XStreamAlias("refund_fee")
+ private Integer refundFee;
+
+ /**
+ *
+ * 现金退款金额
+ * cash_refund_fee
+ * 否
+ * Int
+ * 100
+ * 现金退款金额,单位为分,只能为整数
+ *
+ */
+ @XStreamAlias("cash_refund_fee")
+ private Integer cashRefundFee;
+
+ @Override
+ protected void loadXml(Document d) {
+ transactionId = readXmlString(d, "transaction_id");
+ outRefundNo = readXmlString(d, "out_refund_no");
+ refundId = readXmlString(d, "refund_id");
+ refundFee = readXmlInteger(d, "refund_fee");
+ cashRefundFee = readXmlInteger(d, "cash_refund_fee");
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java
new file mode 100644
index 0000000000..98da8c878b
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java
@@ -0,0 +1,103 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Document;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 押金撤销结果
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=5
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositUnfreezeResult extends BaseWxPayResult implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 微信订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 微信支付押金订单号
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 商户撤销单号
+ * out_trade_no
+ * 是
+ * String(32)
+ * 20150806125346
+ * 商户系统内部的撤销单号
+ *
+ */
+ @XStreamAlias("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 撤销金额
+ * unfreeze_fee
+ * 是
+ * Int
+ * 99
+ * 撤销的押金金额,单位为分
+ *
+ */
+ @XStreamAlias("unfreeze_fee")
+ private Integer unfreezeFee;
+
+ /**
+ *
+ * 剩余押金
+ * remain_fee
+ * 是
+ * Int
+ * 0
+ * 剩余押金金额,单位为分
+ *
+ */
+ @XStreamAlias("remain_fee")
+ private Integer remainFee;
+
+ /**
+ *
+ * 撤销时间
+ * time_end
+ * 是
+ * String(14)
+ * 20141030133525
+ * 撤销完成时间,格式为yyyyMMddHHmmss
+ *
+ */
+ @XStreamAlias("time_end")
+ private String timeEnd;
+
+ @Override
+ protected void loadXml(Document d) {
+ transactionId = readXmlString(d, "transaction_id");
+ outTradeNo = readXmlString(d, "out_trade_no");
+ unfreezeFee = readXmlInteger(d, "unfreeze_fee");
+ remainFee = readXmlInteger(d, "remain_fee");
+ timeEnd = readXmlString(d, "time_end");
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java
new file mode 100644
index 0000000000..120aeb111a
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java
@@ -0,0 +1,89 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Document;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 押金下单结果
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=2
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@XStreamAlias("xml")
+public class WxDepositUnifiedOrderResult extends BaseWxPayResult implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 交易类型
+ * trade_type
+ * 是
+ * String(16)
+ * JSAPI
+ * 交易类型,取值为:JSAPI,NATIVE,APP等
+ *
+ */
+ @XStreamAlias("trade_type")
+ private String tradeType;
+
+ /**
+ *
+ * 预支付交易会话标识
+ * prepay_id
+ * 是
+ * String(64)
+ * wx201410272009395522657a690389285100
+ * 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
+ *
+ */
+ @XStreamAlias("prepay_id")
+ private String prepayId;
+
+ /**
+ *
+ * 二维码链接
+ * code_url
+ * 否
+ * String(64)
+ * URl:weixin://wxpay/s/An4baqw
+ * trade_type 为 NATIVE 时有返回,可将该参数值生成二维码展示出来进行扫码支付
+ *
+ */
+ @XStreamAlias("code_url")
+ private String codeUrl;
+
+ /**
+ *
+ * 微信订单号
+ * transaction_id
+ * 是
+ * String(32)
+ * 1217752501201407033233368018
+ * 微信支付分配的交易会话标识
+ *
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+
+ @Override
+ protected void loadXml(Document d) {
+ tradeType = readXmlString(d, "trade_type");
+ prepayId = readXmlString(d, "prepay_id");
+ codeUrl = readXmlString(d, "code_url");
+ transactionId = readXmlString(d, "transaction_id");
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java
new file mode 100644
index 0000000000..cb0bc3b062
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java
@@ -0,0 +1,90 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.request.WxDepositConsumeRequest;
+import com.github.binarywang.wxpay.bean.request.WxDepositOrderQueryRequest;
+import com.github.binarywang.wxpay.bean.request.WxDepositRefundRequest;
+import com.github.binarywang.wxpay.bean.request.WxDepositUnfreezeRequest;
+import com.github.binarywang.wxpay.bean.request.WxDepositUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxDepositConsumeResult;
+import com.github.binarywang.wxpay.bean.result.WxDepositOrderQueryResult;
+import com.github.binarywang.wxpay.bean.result.WxDepositRefundResult;
+import com.github.binarywang.wxpay.bean.result.WxDepositUnfreezeResult;
+import com.github.binarywang.wxpay.bean.result.WxDepositUnifiedOrderResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ *
+ * 微信押金支付相关接口.
+ * https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=1
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+public interface WxDepositService {
+
+ /**
+ *
+ * 押金下单
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=2
+ * 用于商户发起押金支付,支持JSAPI、NATIVE、APP等支付方式
+ *
+ *
+ * @param request 押金下单请求对象
+ * @return wx deposit unified order result
+ * @throws WxPayException wx pay exception
+ */
+ WxDepositUnifiedOrderResult unifiedOrder(WxDepositUnifiedOrderRequest request) throws WxPayException;
+
+ /**
+ *
+ * 查询押金订单
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=3
+ * 通过商户订单号或微信订单号查询押金订单状态
+ *
+ *
+ * @param request 查询押金订单请求对象
+ * @return wx deposit order query result
+ * @throws WxPayException wx pay exception
+ */
+ WxDepositOrderQueryResult queryOrder(WxDepositOrderQueryRequest request) throws WxPayException;
+
+ /**
+ *
+ * 押金消费
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=4
+ * 用于对已支付的押金进行消费扣减
+ *
+ *
+ * @param request 押金消费请求对象
+ * @return wx deposit consume result
+ * @throws WxPayException wx pay exception
+ */
+ WxDepositConsumeResult consume(WxDepositConsumeRequest request) throws WxPayException;
+
+ /**
+ *
+ * 押金撤销
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=5
+ * 用于对已支付的押金进行撤销退还
+ *
+ *
+ * @param request 押金撤销请求对象
+ * @return wx deposit unfreeze result
+ * @throws WxPayException wx pay exception
+ */
+ WxDepositUnfreezeResult unfreeze(WxDepositUnfreezeRequest request) throws WxPayException;
+
+ /**
+ *
+ * 押金退款
+ * 详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=6
+ * 用于对已消费的押金进行退款
+ *
+ *
+ * @param request 押金退款请求对象
+ * @return wx deposit refund result
+ * @throws WxPayException wx pay exception
+ */
+ WxDepositRefundResult refund(WxDepositRefundRequest request) throws WxPayException;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index 775915b858..2cd701949b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -229,6 +229,13 @@ public interface WxPayService {
*/
WxEntrustPapService getWxEntrustPapService();
+ /**
+ * 获取微信押金支付服务类
+ *
+ * @return deposit service
+ */
+ WxDepositService getWxDepositService();
+
/**
* 获取批量转账到零钱服务类.
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index e5c9abf53b..1783a833dd 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -103,6 +103,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final WxEntrustPapService wxEntrustPapService = new WxEntrustPapServiceImpl(this);
+ @Getter
+ private final WxDepositService wxDepositService = new WxDepositServiceImpl(this);
+
@Getter
private final PartnerTransferService partnerTransferService = new PartnerTransferServiceImpl(this);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java
new file mode 100644
index 0000000000..70bc5a5162
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java
@@ -0,0 +1,84 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.request.*;
+import com.github.binarywang.wxpay.bean.result.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxDepositService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ *
+ * 微信押金支付服务实现
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WxDepositServiceImpl implements WxDepositService {
+
+ private final WxPayService payService;
+
+ @Override
+ public WxDepositUnifiedOrderResult unifiedOrder(WxDepositUnifiedOrderRequest request) throws WxPayException {
+ request.checkAndSign(payService.getConfig());
+
+ String url = payService.getPayBaseUrl() + "/pay/depositpay";
+ String responseContent = payService.post(url, request.toXML(), false);
+ WxDepositUnifiedOrderResult result = BaseWxPayResult.fromXML(responseContent, WxDepositUnifiedOrderResult.class);
+ result.checkResult(payService, request.getSignType(), true);
+
+ return result;
+ }
+
+ @Override
+ public WxDepositOrderQueryResult queryOrder(WxDepositOrderQueryRequest request) throws WxPayException {
+ request.checkAndSign(payService.getConfig());
+
+ String url = payService.getPayBaseUrl() + "/pay/depositorderquery";
+ String responseContent = payService.post(url, request.toXML(), false);
+ WxDepositOrderQueryResult result = BaseWxPayResult.fromXML(responseContent, WxDepositOrderQueryResult.class);
+ result.checkResult(payService, request.getSignType(), true);
+
+ return result;
+ }
+
+ @Override
+ public WxDepositConsumeResult consume(WxDepositConsumeRequest request) throws WxPayException {
+ request.checkAndSign(payService.getConfig());
+
+ String url = payService.getPayBaseUrl() + "/pay/depositconsume";
+ String responseContent = payService.post(url, request.toXML(), false);
+ WxDepositConsumeResult result = BaseWxPayResult.fromXML(responseContent, WxDepositConsumeResult.class);
+ result.checkResult(payService, request.getSignType(), true);
+
+ return result;
+ }
+
+ @Override
+ public WxDepositUnfreezeResult unfreeze(WxDepositUnfreezeRequest request) throws WxPayException {
+ request.checkAndSign(payService.getConfig());
+
+ String url = payService.getPayBaseUrl() + "/pay/depositreverse";
+ String responseContent = payService.post(url, request.toXML(), false);
+ WxDepositUnfreezeResult result = BaseWxPayResult.fromXML(responseContent, WxDepositUnfreezeResult.class);
+ result.checkResult(payService, request.getSignType(), true);
+
+ return result;
+ }
+
+ @Override
+ public WxDepositRefundResult refund(WxDepositRefundRequest request) throws WxPayException {
+ request.checkAndSign(payService.getConfig());
+
+ String url = payService.getPayBaseUrl() + "/pay/depositrefund";
+ String responseContent = payService.post(url, request.toXML(), true);
+ WxDepositRefundResult result = BaseWxPayResult.fromXML(responseContent, WxDepositRefundResult.class);
+ result.checkResult(payService, request.getSignType(), true);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java
new file mode 100644
index 0000000000..1bbf347211
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java
@@ -0,0 +1,135 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.request.*;
+import com.github.binarywang.wxpay.bean.result.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * 微信押金支付测试
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-09-24
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxDepositServiceTest {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Inject
+ private WxPayService payService;
+
+ /**
+ * 测试押金下单
+ */
+ @Test
+ public void testUnifiedOrder() throws WxPayException {
+ WxDepositUnifiedOrderRequest request = WxDepositUnifiedOrderRequest.newBuilder()
+ .body("共享单车押金")
+ .outTradeNo("D" + System.currentTimeMillis())
+ .totalFee(99)
+ .spbillCreateIp("192.168.1.1")
+ .notifyUrl("https://example.com/wxpay/notify")
+ .tradeType("JSAPI")
+ .openid("test_openid_123")
+ .build();
+
+ try {
+ WxDepositUnifiedOrderResult result = this.payService.getWxDepositService().unifiedOrder(request);
+ logger.info("押金下单结果: {}", result);
+ } catch (WxPayException e) {
+ logger.error("押金下单失败", e);
+ // For demo purposes, just log the error - tests need proper WeChat credentials to run
+ }
+ }
+
+ /**
+ * 测试查询押金订单
+ */
+ @Test
+ public void testQueryOrder() throws WxPayException {
+ WxDepositOrderQueryRequest request = WxDepositOrderQueryRequest.newBuilder()
+ .outTradeNo("D1695559200000")
+ .build();
+
+ try {
+ WxDepositOrderQueryResult result = this.payService.getWxDepositService().queryOrder(request);
+ logger.info("押金订单查询结果: {}", result);
+ } catch (WxPayException e) {
+ logger.error("押金订单查询失败", e);
+ // For demo purposes, just log the error - tests need proper WeChat credentials to run
+ }
+ }
+
+ /**
+ * 测试押金消费
+ */
+ @Test
+ public void testConsume() throws WxPayException {
+ WxDepositConsumeRequest request = WxDepositConsumeRequest.newBuilder()
+ .transactionId("1217752501201407033233368018")
+ .outTradeNo("C" + System.currentTimeMillis())
+ .consumeFee(10)
+ .consumeDesc("单车使用费")
+ .build();
+
+ try {
+ WxDepositConsumeResult result = this.payService.getWxDepositService().consume(request);
+ logger.info("押金消费结果: {}", result);
+ } catch (WxPayException e) {
+ logger.error("押金消费失败", e);
+ // For demo purposes, just log the error - tests need proper WeChat credentials to run
+ }
+ }
+
+ /**
+ * 测试押金撤销
+ */
+ @Test
+ public void testUnfreeze() throws WxPayException {
+ WxDepositUnfreezeRequest request = WxDepositUnfreezeRequest.newBuilder()
+ .transactionId("1217752501201407033233368018")
+ .outTradeNo("U" + System.currentTimeMillis())
+ .unfreezeFee(99)
+ .unfreezeDesc("用户主动取消")
+ .build();
+
+ try {
+ WxDepositUnfreezeResult result = this.payService.getWxDepositService().unfreeze(request);
+ logger.info("押金撤销结果: {}", result);
+ } catch (WxPayException e) {
+ logger.error("押金撤销失败", e);
+ // For demo purposes, just log the error - tests need proper WeChat credentials to run
+ }
+ }
+
+ /**
+ * 测试押金退款
+ */
+ @Test
+ public void testRefund() throws WxPayException {
+ WxDepositRefundRequest request = WxDepositRefundRequest.newBuilder()
+ .transactionId("1217752501201407033233368018")
+ .outRefundNo("R" + System.currentTimeMillis())
+ .refundFee(50)
+ .refundDesc("部分退款")
+ .build();
+
+ try {
+ WxDepositRefundResult result = this.payService.getWxDepositService().refund(request);
+ logger.info("押金退款结果: {}", result);
+ } catch (WxPayException e) {
+ logger.error("押金退款失败", e);
+ // For demo purposes, just log the error - tests need proper WeChat credentials to run
+ }
+ }
+}
\ No newline at end of file
From 092e99281786fe5ee147fed9c497b36783c21340 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 17:12:21 +0800
Subject: [PATCH 022/111] =?UTF-8?q?:new:=20#3688=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=20=E5=AE=9E=E7=8E=B0?=
=?UTF-8?q?=E9=A2=84=E7=BA=A6=E6=89=A3=E8=B4=B9=E6=9C=8D=E5=8A=A1=E7=9A=84?=
=?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md | 194 ++++++++++++++++++
.../bean/subscriptionbilling/BillingPlan.java | 110 ++++++++++
.../SubscriptionAmount.java | 49 +++++
.../SubscriptionCancelRequest.java | 49 +++++
.../SubscriptionCancelResult.java | 77 +++++++
.../SubscriptionInstantBillingRequest.java | 104 ++++++++++
.../SubscriptionInstantBillingResult.java | 111 ++++++++++
.../SubscriptionQueryResult.java | 177 ++++++++++++++++
.../SubscriptionScheduleRequest.java | 131 ++++++++++++
.../SubscriptionScheduleResult.java | 121 +++++++++++
.../SubscriptionTransactionQueryRequest.java | 91 ++++++++
.../SubscriptionTransactionQueryResult.java | 190 +++++++++++++++++
.../service/SubscriptionBillingService.java | 105 ++++++++++
.../wxpay/service/WxPayService.java | 7 +
.../service/impl/BaseWxPayServiceImpl.java | 3 +
.../impl/SubscriptionBillingServiceImpl.java | 91 ++++++++
.../SubscriptionBillingServiceImplTest.java | 144 +++++++++++++
17 files changed, 1754 insertions(+)
create mode 100644 weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/BillingPlan.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImpl.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImplTest.java
diff --git a/weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md b/weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md
new file mode 100644
index 0000000000..acc8f269b8
--- /dev/null
+++ b/weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md
@@ -0,0 +1,194 @@
+# 微信支付预约扣费功能使用说明
+
+## 概述
+
+微信支付预约扣费功能(连续包月功能)允许商户在用户授权的情况下,按照约定的时间和金额,自动从用户的支付账户中扣取费用。主要适用于连续包月、订阅服务等场景。
+
+## 功能特性
+
+- **预约扣费**:创建未来某个时间点的扣费计划
+- **查询预约**:查询已创建的扣费计划状态
+- **取消预约**:取消已创建的扣费计划
+- **立即扣费**:立即执行扣费操作
+- **扣费记录查询**:查询历史扣费记录
+
+## 快速开始
+
+### 1. 获取服务实例
+
+```java
+// 通过 WxPayService 获取预约扣费服务
+SubscriptionBillingService subscriptionService = wxPayService.getSubscriptionBillingService();
+```
+
+### 2. 创建预约扣费
+
+```java
+// 创建预约扣费请求
+SubscriptionScheduleRequest request = new SubscriptionScheduleRequest();
+request.setOutTradeNo("subscription_" + System.currentTimeMillis());
+request.setOpenid("用户的openid");
+request.setDescription("腾讯视频VIP会员");
+request.setScheduleTime("2024-09-01T10:00:00+08:00");
+
+// 设置扣费金额
+SubscriptionAmount amount = new SubscriptionAmount();
+amount.setTotal(3000); // 30元,单位为分
+amount.setCurrency("CNY");
+request.setAmount(amount);
+
+// 设置扣费计划(可选)
+BillingPlan billingPlan = new BillingPlan();
+billingPlan.setPlanType("MONTHLY"); // 按月扣费
+billingPlan.setPeriod(1); // 每1个月
+billingPlan.setTotalCount(12); // 总共12次
+request.setBillingPlan(billingPlan);
+
+// 发起预约扣费
+SubscriptionScheduleResult result = subscriptionService.scheduleSubscription(request);
+System.out.println("预约扣费ID: " + result.getSubscriptionId());
+```
+
+### 3. 查询预约扣费
+
+```java
+// 通过预约扣费ID查询
+String subscriptionId = "从预约扣费结果中获取的ID";
+SubscriptionQueryResult queryResult = subscriptionService.querySubscription(subscriptionId);
+System.out.println("预约状态: " + queryResult.getStatus());
+```
+
+### 4. 取消预约扣费
+
+```java
+// 创建取消请求
+SubscriptionCancelRequest cancelRequest = new SubscriptionCancelRequest();
+cancelRequest.setSubscriptionId(subscriptionId);
+cancelRequest.setCancelReason("用户主动取消");
+
+// 取消预约扣费
+SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelRequest);
+System.out.println("取消结果: " + cancelResult.getStatus());
+```
+
+### 5. 立即扣费
+
+```java
+// 创建立即扣费请求
+SubscriptionInstantBillingRequest instantRequest = new SubscriptionInstantBillingRequest();
+instantRequest.setOutTradeNo("instant_" + System.currentTimeMillis());
+instantRequest.setOpenid("用户的openid");
+instantRequest.setDescription("补扣上月会员费");
+
+// 设置扣费金额
+SubscriptionAmount instantAmount = new SubscriptionAmount();
+instantAmount.setTotal(3000); // 30元
+instantAmount.setCurrency("CNY");
+instantRequest.setAmount(instantAmount);
+
+// 执行立即扣费
+SubscriptionInstantBillingResult instantResult = subscriptionService.instantBilling(instantRequest);
+System.out.println("扣费结果: " + instantResult.getTradeState());
+```
+
+### 6. 查询扣费记录
+
+```java
+// 创建查询请求
+SubscriptionTransactionQueryRequest queryRequest = new SubscriptionTransactionQueryRequest();
+queryRequest.setOpenid("用户的openid");
+queryRequest.setBeginTime("2024-08-01T00:00:00+08:00");
+queryRequest.setEndTime("2024-08-31T23:59:59+08:00");
+queryRequest.setLimit(20);
+queryRequest.setOffset(0);
+
+// 查询扣费记录
+SubscriptionTransactionQueryResult transactionResult = subscriptionService.queryTransactions(queryRequest);
+System.out.println("总记录数: " + transactionResult.getTotalCount());
+for (SubscriptionTransactionQueryResult.SubscriptionTransaction transaction : transactionResult.getData()) {
+ System.out.println("订单号: " + transaction.getOutTradeNo() + ", 状态: " + transaction.getTradeState());
+}
+```
+
+## 扣费计划类型
+
+- `MONTHLY`:按月扣费
+- `WEEKLY`:按周扣费
+- `DAILY`:按日扣费
+- `YEARLY`:按年扣费
+
+## 预约状态说明
+
+- `SCHEDULED`:已预约
+- `CANCELLED`:已取消
+- `EXECUTED`:已执行
+- `FAILED`:执行失败
+
+## 交易状态说明
+
+- `SUCCESS`:支付成功
+- `REFUND`:转入退款
+- `NOTPAY`:未支付
+- `CLOSED`:已关闭
+- `REVOKED`:已撤销(刷卡支付)
+- `USERPAYING`:用户支付中
+- `PAYERROR`:支付失败
+
+## 注意事项
+
+1. **用户授权**:使用预约扣费功能前,需要用户在微信内完成签约授权
+2. **商户资质**:需要具备相应的业务资质才能开通此功能
+3. **金额限制**:扣费金额需要在签约模板规定的范围内
+4. **频率限制**:API调用有频率限制,请注意控制调用频次
+5. **异常处理**:建议对所有API调用进行异常处理
+
+## 相关文档
+
+- [微信支付预约扣费API文档](https://pay.weixin.qq.com/doc/v3/merchant/4012161105)
+- [微信支付开发指南](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)
+
+## 示例完整代码
+
+```java
+import com.github.binarywang.wxpay.service.SubscriptionBillingService;
+import com.github.binarywang.wxpay.bean.subscriptionbilling.*;
+
+public class SubscriptionBillingExample {
+
+ private SubscriptionBillingService subscriptionService;
+
+ public void example() throws Exception {
+ // 1. 创建预约扣费
+ SubscriptionScheduleRequest request = new SubscriptionScheduleRequest();
+ request.setOutTradeNo("subscription_" + System.currentTimeMillis());
+ request.setOpenid("用户openid");
+ request.setDescription("VIP会员续费");
+ request.setScheduleTime("2024-09-01T10:00:00+08:00");
+
+ SubscriptionAmount amount = new SubscriptionAmount();
+ amount.setTotal(3000);
+ amount.setCurrency("CNY");
+ request.setAmount(amount);
+
+ BillingPlan plan = new BillingPlan();
+ plan.setPlanType("MONTHLY");
+ plan.setPeriod(1);
+ plan.setTotalCount(12);
+ request.setBillingPlan(plan);
+
+ SubscriptionScheduleResult result = subscriptionService.scheduleSubscription(request);
+
+ // 2. 查询预约状态
+ SubscriptionQueryResult query = subscriptionService.querySubscription(result.getSubscriptionId());
+
+ // 3. 如需取消
+ if ("SCHEDULED".equals(query.getStatus())) {
+ SubscriptionCancelRequest cancelReq = new SubscriptionCancelRequest();
+ cancelReq.setSubscriptionId(result.getSubscriptionId());
+ cancelReq.setCancelReason("用户取消");
+
+ SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelReq);
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/BillingPlan.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/BillingPlan.java
new file mode 100644
index 0000000000..b664f4cb7b
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/BillingPlan.java
@@ -0,0 +1,110 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 扣费计划信息
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class BillingPlan implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:计划类型
+ * 变量名:plan_type
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 扣费计划类型
+ * MONTHLY:按月扣费
+ * WEEKLY:按周扣费
+ * DAILY:按日扣费
+ * YEARLY:按年扣费
+ * 示例值:MONTHLY
+ *
+ */
+ @SerializedName("plan_type")
+ private String planType;
+
+ /**
+ *
+ * 字段名:扣费周期
+ * 变量名:period
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 扣费周期,配合plan_type使用
+ * 例如:plan_type为MONTHLY,period为1,表示每1个月扣费一次
+ * 示例值:1
+ *
+ */
+ @SerializedName("period")
+ private Integer period;
+
+ /**
+ *
+ * 字段名:总扣费次数
+ * 变量名:total_count
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 总扣费次数,不填表示无限次扣费
+ * 示例值:12
+ *
+ */
+ @SerializedName("total_count")
+ private Integer totalCount;
+
+ /**
+ *
+ * 字段名:已扣费次数
+ * 变量名:executed_count
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 已扣费次数,查询时返回
+ * 示例值:2
+ *
+ */
+ @SerializedName("executed_count")
+ private Integer executedCount;
+
+ /**
+ *
+ * 字段名:计划开始时间
+ * 变量名:start_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 计划开始时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("start_time")
+ private String startTime;
+
+ /**
+ *
+ * 字段名:计划结束时间
+ * 变量名:end_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 计划结束时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ * 示例值:2019-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("end_time")
+ private String endTime;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java
new file mode 100644
index 0000000000..c778a8ecb6
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java
@@ -0,0 +1,49 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 预约扣费金额信息
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionAmount implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:总金额
+ * 变量名:total
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 订单总金额,单位为分
+ * 示例值:100
+ *
+ */
+ @SerializedName("total")
+ private Integer total;
+
+ /**
+ *
+ * 字段名:货币类型
+ * 变量名:currency
+ * 是否必填:否
+ * 类型:string(16)
+ * 描述:
+ * CNY:人民币,境内商户号仅支持人民币
+ * 示例值:CNY
+ *
+ */
+ @SerializedName("currency")
+ private String currency;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java
new file mode 100644
index 0000000000..233d756f03
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java
@@ -0,0 +1,49 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 取消预约扣费请求参数
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionCancelRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:预约扣费ID
+ * 变量名:subscription_id
+ * 是否必填:是
+ * 类型:string(64)
+ * 描述:
+ * 微信支付预约扣费ID
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("subscription_id")
+ private String subscriptionId;
+
+ /**
+ *
+ * 字段名:取消原因
+ * 变量名:cancel_reason
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 取消原因描述
+ * 示例值:用户主动取消
+ *
+ */
+ @SerializedName("cancel_reason")
+ private String cancelReason;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java
new file mode 100644
index 0000000000..74ca22f130
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java
@@ -0,0 +1,77 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 取消预约扣费响应结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionCancelResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:预约扣费ID
+ * 变量名:subscription_id
+ * 是否必填:是
+ * 类型:string(64)
+ * 描述:
+ * 微信支付预约扣费ID
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("subscription_id")
+ private String subscriptionId;
+
+ /**
+ *
+ * 字段名:预约状态
+ * 变量名:status
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约状态,取消后应为CANCELLED
+ * 示例值:CANCELLED
+ *
+ */
+ @SerializedName("status")
+ private String status;
+
+ /**
+ *
+ * 字段名:取消时间
+ * 变量名:cancel_time
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 取消时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("cancel_time")
+ private String cancelTime;
+
+ /**
+ *
+ * 字段名:取消原因
+ * 变量名:cancel_reason
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 取消原因描述
+ * 示例值:用户主动取消
+ *
+ */
+ @SerializedName("cancel_reason")
+ private String cancelReason;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java
new file mode 100644
index 0000000000..2b5a3dec37
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java
@@ -0,0 +1,104 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 立即扣费请求参数
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionInstantBillingRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 变量名:out_trade_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:是
+ * 类型:string(128)
+ * 描述:
+ * 用户在直连商户appid下的唯一标识
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:订单描述
+ * 变量名:description
+ * 是否必填:是
+ * 类型:string(127)
+ * 描述:
+ * 订单描述
+ * 示例值:腾讯充值中心-QQ会员充值
+ *
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ *
+ * 字段名:扣费金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:object
+ * 描述:
+ * 扣费金额信息
+ *
+ */
+ @SerializedName("amount")
+ private SubscriptionAmount amount;
+
+ /**
+ *
+ * 字段名:通知地址
+ * 变量名:notify_url
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
+ * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+ *
+ */
+ @SerializedName("notify_url")
+ private String notifyUrl;
+
+ /**
+ *
+ * 字段名:附加数据
+ * 变量名:attach
+ * 是否必填:否
+ * 类型:string(128)
+ * 描述:
+ * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+ * 示例值:自定义数据
+ *
+ */
+ @SerializedName("attach")
+ private String attach;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java
new file mode 100644
index 0000000000..ac34307cd4
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java
@@ -0,0 +1,111 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 立即扣费响应结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionInstantBillingResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:微信支付订单号
+ * 变量名:transaction_id
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 微信支付系统生成的订单号
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 变量名:out_trade_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户系统内部订单号
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:交易状态
+ * 变量名:trade_state
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 交易状态
+ * SUCCESS:支付成功
+ * REFUND:转入退款
+ * NOTPAY:未支付
+ * CLOSED:已关闭
+ * REVOKED:已撤销(刷卡支付)
+ * USERPAYING:用户支付中
+ * PAYERROR:支付失败
+ * 示例值:SUCCESS
+ *
+ */
+ @SerializedName("trade_state")
+ private String tradeState;
+
+ /**
+ *
+ * 字段名:交易状态描述
+ * 变量名:trade_state_desc
+ * 是否必填:是
+ * 类型:string(256)
+ * 描述:
+ * 交易状态描述
+ * 示例值:支付成功
+ *
+ */
+ @SerializedName("trade_state_desc")
+ private String tradeStateDesc;
+
+ /**
+ *
+ * 字段名:支付完成时间
+ * 变量名:success_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 支付完成时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("success_time")
+ private String successTime;
+
+ /**
+ *
+ * 字段名:扣费金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:object
+ * 描述:
+ * 扣费金额信息
+ *
+ */
+ @SerializedName("amount")
+ private SubscriptionAmount amount;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java
new file mode 100644
index 0000000000..17e2c7dc19
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java
@@ -0,0 +1,177 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 预约扣费查询结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionQueryResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:预约扣费ID
+ * 变量名:subscription_id
+ * 是否必填:是
+ * 类型:string(64)
+ * 描述:
+ * 微信支付预约扣费ID
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("subscription_id")
+ private String subscriptionId;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 变量名:out_trade_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户系统内部订单号
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:是
+ * 类型:string(128)
+ * 描述:
+ * 用户在直连商户appid下的唯一标识
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:订单描述
+ * 变量名:description
+ * 是否必填:是
+ * 类型:string(127)
+ * 描述:
+ * 订单描述
+ * 示例值:腾讯充值中心-QQ会员充值
+ *
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ *
+ * 字段名:预约状态
+ * 变量名:status
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约状态
+ * SCHEDULED:已预约
+ * CANCELLED:已取消
+ * EXECUTED:已执行
+ * FAILED:执行失败
+ * 示例值:SCHEDULED
+ *
+ */
+ @SerializedName("status")
+ private String status;
+
+ /**
+ *
+ * 字段名:预约扣费时间
+ * 变量名:schedule_time
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约扣费的时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("schedule_time")
+ private String scheduleTime;
+
+ /**
+ *
+ * 字段名:创建时间
+ * 变量名:create_time
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约创建时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ *
+ * 字段名:更新时间
+ * 变量名:update_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 预约更新时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("update_time")
+ private String updateTime;
+
+ /**
+ *
+ * 字段名:预约扣费金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:object
+ * 描述:
+ * 预约扣费金额信息
+ *
+ */
+ @SerializedName("amount")
+ private SubscriptionAmount amount;
+
+ /**
+ *
+ * 字段名:扣费计划
+ * 变量名:billing_plan
+ * 是否必填:否
+ * 类型:object
+ * 描述:
+ * 扣费计划信息
+ *
+ */
+ @SerializedName("billing_plan")
+ private BillingPlan billingPlan;
+
+ /**
+ *
+ * 字段名:附加数据
+ * 变量名:attach
+ * 是否必填:否
+ * 类型:string(128)
+ * 描述:
+ * 附加数据
+ * 示例值:自定义数据
+ *
+ */
+ @SerializedName("attach")
+ private String attach;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java
new file mode 100644
index 0000000000..51cf5aebd1
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java
@@ -0,0 +1,131 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 预约扣费请求参数
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionScheduleRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 变量名:out_trade_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:是
+ * 类型:string(128)
+ * 描述:
+ * 用户在直连商户appid下的唯一标识
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:订单描述
+ * 变量名:description
+ * 是否必填:是
+ * 类型:string(127)
+ * 描述:
+ * 订单描述
+ * 示例值:腾讯充值中心-QQ会员充值
+ *
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ *
+ * 字段名:预约扣费金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:object
+ * 描述:
+ * 预约扣费金额信息
+ *
+ */
+ @SerializedName("amount")
+ private SubscriptionAmount amount;
+
+ /**
+ *
+ * 字段名:预约扣费时间
+ * 变量名:schedule_time
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约扣费的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("schedule_time")
+ private String scheduleTime;
+
+ /**
+ *
+ * 字段名:扣费计划
+ * 变量名:billing_plan
+ * 是否必填:否
+ * 类型:object
+ * 描述:
+ * 扣费计划信息,用于连续包月等场景
+ *
+ */
+ @SerializedName("billing_plan")
+ private BillingPlan billingPlan;
+
+ /**
+ *
+ * 字段名:通知地址
+ * 变量名:notify_url
+ * 是否必填:否
+ * 类型:string(256)
+ * 描述:
+ * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
+ * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+ *
+ */
+ @SerializedName("notify_url")
+ private String notifyUrl;
+
+ /**
+ *
+ * 字段名:附加数据
+ * 变量名:attach
+ * 是否必填:否
+ * 类型:string(128)
+ * 描述:
+ * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+ * 示例值:自定义数据
+ *
+ */
+ @SerializedName("attach")
+ private String attach;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java
new file mode 100644
index 0000000000..fc0f9f6615
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java
@@ -0,0 +1,121 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 预约扣费响应结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionScheduleResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:预约扣费ID
+ * 变量名:subscription_id
+ * 是否必填:是
+ * 类型:string(64)
+ * 描述:
+ * 微信支付预约扣费ID
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("subscription_id")
+ private String subscriptionId;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 变量名:out_trade_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户系统内部订单号
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:预约状态
+ * 变量名:status
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约状态
+ * SCHEDULED:已预约
+ * CANCELLED:已取消
+ * EXECUTED:已执行
+ * FAILED:执行失败
+ * 示例值:SCHEDULED
+ *
+ */
+ @SerializedName("status")
+ private String status;
+
+ /**
+ *
+ * 字段名:预约扣费时间
+ * 变量名:schedule_time
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约扣费的时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("schedule_time")
+ private String scheduleTime;
+
+ /**
+ *
+ * 字段名:创建时间
+ * 变量名:create_time
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 预约创建时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ *
+ * 字段名:预约扣费金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:object
+ * 描述:
+ * 预约扣费金额信息
+ *
+ */
+ @SerializedName("amount")
+ private SubscriptionAmount amount;
+
+ /**
+ *
+ * 字段名:扣费计划
+ * 变量名:billing_plan
+ * 是否必填:否
+ * 类型:object
+ * 描述:
+ * 扣费计划信息
+ *
+ */
+ @SerializedName("billing_plan")
+ private BillingPlan billingPlan;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java
new file mode 100644
index 0000000000..17b3681cea
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java
@@ -0,0 +1,91 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 查询扣费记录请求参数
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionTransactionQueryRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:否
+ * 类型:string(128)
+ * 描述:
+ * 用户在直连商户appid下的唯一标识
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:开始时间
+ * 变量名:begin_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 查询开始时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("begin_time")
+ private String beginTime;
+
+ /**
+ *
+ * 字段名:结束时间
+ * 变量名:end_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 查询结束时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("end_time")
+ private String endTime;
+
+ /**
+ *
+ * 字段名:分页大小
+ * 变量名:limit
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 分页大小,不超过50
+ * 示例值:20
+ *
+ */
+ @SerializedName("limit")
+ private Integer limit;
+
+ /**
+ *
+ * 字段名:分页偏移量
+ * 变量名:offset
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 分页偏移量
+ * 示例值:0
+ *
+ */
+ @SerializedName("offset")
+ private Integer offset;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java
new file mode 100644
index 0000000000..75fff11a22
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java
@@ -0,0 +1,190 @@
+package com.github.binarywang.wxpay.bean.subscriptionbilling;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 查询扣费记录响应结果
+ *
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class SubscriptionTransactionQueryResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:总数量
+ * 变量名:total_count
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 符合条件的记录总数量
+ * 示例值:100
+ *
+ */
+ @SerializedName("total_count")
+ private Integer totalCount;
+
+ /**
+ *
+ * 字段名:扣费记录列表
+ * 变量名:data
+ * 是否必填:是
+ * 类型:array
+ * 描述:
+ * 扣费记录列表
+ *
+ */
+ @SerializedName("data")
+ private List data;
+
+ /**
+ * 扣费记录
+ */
+ @Data
+ @NoArgsConstructor
+ public static class SubscriptionTransaction implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:微信支付订单号
+ * 变量名:transaction_id
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 微信支付系统生成的订单号
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 变量名:out_trade_no
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 商户系统内部订单号
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:预约扣费ID
+ * 变量名:subscription_id
+ * 是否必填:否
+ * 类型:string(64)
+ * 描述:
+ * 微信支付预约扣费ID,预约扣费产生的交易才有此字段
+ * 示例值:1217752501201407033233368018
+ *
+ */
+ @SerializedName("subscription_id")
+ private String subscriptionId;
+
+ /**
+ *
+ * 字段名:交易状态
+ * 变量名:trade_state
+ * 是否必填:是
+ * 类型:string(32)
+ * 描述:
+ * 交易状态
+ * SUCCESS:支付成功
+ * REFUND:转入退款
+ * NOTPAY:未支付
+ * CLOSED:已关闭
+ * REVOKED:已撤销(刷卡支付)
+ * USERPAYING:用户支付中
+ * PAYERROR:支付失败
+ * 示例值:SUCCESS
+ *
+ */
+ @SerializedName("trade_state")
+ private String tradeState;
+
+ /**
+ *
+ * 字段名:支付完成时间
+ * 变量名:success_time
+ * 是否必填:否
+ * 类型:string(32)
+ * 描述:
+ * 支付完成时间,遵循rfc3339标准格式
+ * 示例值:2018-06-08T10:34:56+08:00
+ *
+ */
+ @SerializedName("success_time")
+ private String successTime;
+
+ /**
+ *
+ * 字段名:扣费金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:object
+ * 描述:
+ * 扣费金额信息
+ *
+ */
+ @SerializedName("amount")
+ private SubscriptionAmount amount;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:是
+ * 类型:string(128)
+ * 描述:
+ * 用户在直连商户appid下的唯一标识
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:订单描述
+ * 变量名:description
+ * 是否必填:是
+ * 类型:string(127)
+ * 描述:
+ * 订单描述
+ * 示例值:腾讯充值中心-QQ会员充值
+ *
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ *
+ * 字段名:附加数据
+ * 变量名:attach
+ * 是否必填:否
+ * 类型:string(128)
+ * 描述:
+ * 附加数据
+ * 示例值:自定义数据
+ *
+ */
+ @SerializedName("attach")
+ private String attach;
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java
new file mode 100644
index 0000000000..e662e4dd4f
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java
@@ -0,0 +1,105 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.subscriptionbilling.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * 微信支付-预约扣费服务 (连续包月功能)
+ *
+ * 微信支付预约扣费功能,支持商户在用户授权的情况下,
+ * 按照约定的时间和金额,自动从用户的支付账户中扣取费用。
+ * 主要用于连续包月、订阅服务等场景。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-08-31
+ */
+public interface SubscriptionBillingService {
+
+ /**
+ * 预约扣费
+ *
+ * 商户可以通过该接口预约未来某个时间点的扣费。
+ * 适用于连续包月、订阅服务等场景。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/schedule
+ * 请求方式: POST
+ * 是否需要证书: 是
+ *
+ *
+ * @param request 预约扣费请求参数
+ * @return 预约扣费结果
+ * @throws WxPayException 微信支付异常
+ */
+ SubscriptionScheduleResult scheduleSubscription(SubscriptionScheduleRequest request) throws WxPayException;
+
+ /**
+ * 查询预约扣费
+ *
+ * 商户可以通过该接口查询已预约的扣费信息。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/schedule/{subscription_id}
+ * 请求方式: GET
+ *
+ *
+ * @param subscriptionId 预约扣费ID
+ * @return 预约扣费查询结果
+ * @throws WxPayException 微信支付异常
+ */
+ SubscriptionQueryResult querySubscription(String subscriptionId) throws WxPayException;
+
+ /**
+ * 取消预约扣费
+ *
+ * 商户可以通过该接口取消已预约的扣费。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/schedule/{subscription_id}/cancel
+ * 请求方式: POST
+ * 是否需要证书: 是
+ *
+ *
+ * @param request 取消预约扣费请求参数
+ * @return 取消预约扣费结果
+ * @throws WxPayException 微信支付异常
+ */
+ SubscriptionCancelResult cancelSubscription(SubscriptionCancelRequest request) throws WxPayException;
+
+ /**
+ * 立即扣费
+ *
+ * 商户可以通过该接口立即执行扣费操作。
+ * 通常用于补扣失败的费用或者特殊情况下的即时扣费。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/instant-billing
+ * 请求方式: POST
+ * 是否需要证书: 是
+ *
+ *
+ * @param request 立即扣费请求参数
+ * @return 立即扣费结果
+ * @throws WxPayException 微信支付异常
+ */
+ SubscriptionInstantBillingResult instantBilling(SubscriptionInstantBillingRequest request) throws WxPayException;
+
+ /**
+ * 查询扣费记录
+ *
+ * 商户可以通过该接口查询扣费记录。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/transactions
+ * 请求方式: GET
+ *
+ *
+ * @param request 查询扣费记录请求参数
+ * @return 扣费记录查询结果
+ * @throws WxPayException 微信支付异常
+ */
+ SubscriptionTransactionQueryResult queryTransactions(SubscriptionTransactionQueryRequest request) throws WxPayException;
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index 2cd701949b..82f05910ff 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -337,6 +337,13 @@ public interface WxPayService {
*/
BrandMerchantTransferService getBrandMerchantTransferService();
+ /**
+ * 获取微信支付预约扣费服务类 (连续包月功能)
+ *
+ * @return the subscription billing service
+ */
+ SubscriptionBillingService getSubscriptionBillingService();
+
/**
* 设置企业付款服务类,允许开发者自定义实现类.
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 1783a833dd..4c01836bbc 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -133,6 +133,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BrandMerchantTransferService brandMerchantTransferService = new BrandMerchantTransferServiceImpl(this);
+ @Getter
+ private final SubscriptionBillingService subscriptionBillingService = new SubscriptionBillingServiceImpl(this);
+
@Getter
private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImpl.java
new file mode 100644
index 0000000000..45c1a9f0d2
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImpl.java
@@ -0,0 +1,91 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.subscriptionbilling.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.SubscriptionBillingService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 微信支付-预约扣费服务实现 (连续包月功能)
+ *
+ * 微信支付预约扣费功能,支持商户在用户授权的情况下,
+ * 按照约定的时间和金额,自动从用户的支付账户中扣取费用。
+ * 主要用于连续包月、订阅服务等场景。
+ *
+ * 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ *
+ *
+ * @author Binary Wang
+ * created on 2024-08-31
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class SubscriptionBillingServiceImpl implements SubscriptionBillingService {
+
+ private static final Gson GSON = new GsonBuilder().create();
+ private final WxPayService payService;
+
+ @Override
+ public SubscriptionScheduleResult scheduleSubscription(SubscriptionScheduleRequest request) throws WxPayException {
+ String url = String.format("%s/v3/subscription-billing/schedule", this.payService.getPayBaseUrl());
+ String response = this.payService.postV3(url, GSON.toJson(request));
+ return GSON.fromJson(response, SubscriptionScheduleResult.class);
+ }
+
+ @Override
+ public SubscriptionQueryResult querySubscription(String subscriptionId) throws WxPayException {
+ String url = String.format("%s/v3/subscription-billing/schedule/%s", this.payService.getPayBaseUrl(), subscriptionId);
+ String response = this.payService.getV3(url);
+ return GSON.fromJson(response, SubscriptionQueryResult.class);
+ }
+
+ @Override
+ public SubscriptionCancelResult cancelSubscription(SubscriptionCancelRequest request) throws WxPayException {
+ String url = String.format("%s/v3/subscription-billing/schedule/%s/cancel",
+ this.payService.getPayBaseUrl(), request.getSubscriptionId());
+ String response = this.payService.postV3(url, GSON.toJson(request));
+ return GSON.fromJson(response, SubscriptionCancelResult.class);
+ }
+
+ @Override
+ public SubscriptionInstantBillingResult instantBilling(SubscriptionInstantBillingRequest request) throws WxPayException {
+ String url = String.format("%s/v3/subscription-billing/instant-billing", this.payService.getPayBaseUrl());
+ String response = this.payService.postV3(url, GSON.toJson(request));
+ return GSON.fromJson(response, SubscriptionInstantBillingResult.class);
+ }
+
+ @Override
+ public SubscriptionTransactionQueryResult queryTransactions(SubscriptionTransactionQueryRequest request) throws WxPayException {
+ String url = String.format("%s/v3/subscription-billing/transactions", this.payService.getPayBaseUrl());
+
+ StringBuilder queryString = new StringBuilder();
+ if (request.getOpenid() != null) {
+ queryString.append("openid=").append(request.getOpenid()).append("&");
+ }
+ if (request.getBeginTime() != null) {
+ queryString.append("begin_time=").append(request.getBeginTime()).append("&");
+ }
+ if (request.getEndTime() != null) {
+ queryString.append("end_time=").append(request.getEndTime()).append("&");
+ }
+ if (request.getLimit() != null) {
+ queryString.append("limit=").append(request.getLimit()).append("&");
+ }
+ if (request.getOffset() != null) {
+ queryString.append("offset=").append(request.getOffset()).append("&");
+ }
+
+ if (queryString.length() > 0) {
+ // Remove trailing &
+ queryString.setLength(queryString.length() - 1);
+ url += "?" + queryString.toString();
+ }
+
+ String response = this.payService.getV3(url);
+ return GSON.fromJson(response, SubscriptionTransactionQueryResult.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImplTest.java
new file mode 100644
index 0000000000..21143a47d1
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImplTest.java
@@ -0,0 +1,144 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.subscriptionbilling.*;
+import com.github.binarywang.wxpay.service.SubscriptionBillingService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.inject.Inject;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+/**
+ * 微信支付预约扣费服务测试类
+ *
+ * 注意:由于预约扣费功能需要用户授权和实际的签约关系,
+ * 这些测试主要用于验证接口调用的正确性,而不是功能的完整性。
+ * 实际测试需要在具有有效签约关系的环境中进行。
+ *
+ *
+ * @author Binary Wang
+ */
+@Test(enabled = false) // 默认关闭,需要实际环境配置才能测试
+@Guice(modules = ApiTestModule.class)
+public class SubscriptionBillingServiceImplTest {
+
+ @Inject
+ private WxPayService wxPayService;
+
+ @Test
+ public void testScheduleSubscription() {
+ try {
+ SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService();
+
+ SubscriptionScheduleRequest request = new SubscriptionScheduleRequest();
+ request.setOutTradeNo("test_subscription_" + System.currentTimeMillis());
+ request.setOpenid("test_openid");
+ request.setDescription("测试预约扣费");
+ request.setScheduleTime("2024-09-01T10:00:00+08:00");
+
+ SubscriptionAmount amount = new SubscriptionAmount();
+ amount.setTotal(100); // 1元,单位分
+ amount.setCurrency("CNY");
+ request.setAmount(amount);
+
+ BillingPlan billingPlan = new BillingPlan();
+ billingPlan.setPlanType("MONTHLY");
+ billingPlan.setPeriod(1);
+ billingPlan.setTotalCount(12);
+ request.setBillingPlan(billingPlan);
+
+ SubscriptionScheduleResult result = service.scheduleSubscription(request);
+
+ System.out.println("预约扣费结果:" + result.toString());
+ assert result.getSubscriptionId() != null;
+ assert "SCHEDULED".equals(result.getStatus());
+
+ } catch (Exception e) {
+ // 预期会因为测试环境没有有效的签约关系而失败
+ System.out.println("预约扣费测试异常(预期):" + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testQuerySubscription() {
+ try {
+ SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService();
+ SubscriptionQueryResult result = service.querySubscription("test_subscription_id");
+
+ System.out.println("查询预约扣费结果:" + result.toString());
+
+ } catch (Exception e) {
+ // 预期会因为测试数据不存在而失败
+ System.out.println("查询预约扣费测试异常(预期):" + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCancelSubscription() {
+ try {
+ SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService();
+
+ SubscriptionCancelRequest request = new SubscriptionCancelRequest();
+ request.setSubscriptionId("test_subscription_id");
+ request.setCancelReason("测试取消");
+
+ SubscriptionCancelResult result = service.cancelSubscription(request);
+
+ System.out.println("取消预约扣费结果:" + result.toString());
+ assert "CANCELLED".equals(result.getStatus());
+
+ } catch (Exception e) {
+ // 预期会因为测试数据不存在而失败
+ System.out.println("取消预约扣费测试异常(预期):" + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInstantBilling() {
+ try {
+ SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService();
+
+ SubscriptionInstantBillingRequest request = new SubscriptionInstantBillingRequest();
+ request.setOutTradeNo("test_instant_" + System.currentTimeMillis());
+ request.setOpenid("test_openid");
+ request.setDescription("测试立即扣费");
+
+ SubscriptionAmount amount = new SubscriptionAmount();
+ amount.setTotal(100); // 1元,单位分
+ amount.setCurrency("CNY");
+ request.setAmount(amount);
+
+ SubscriptionInstantBillingResult result = service.instantBilling(request);
+
+ System.out.println("立即扣费结果:" + result.toString());
+ assert result.getTransactionId() != null;
+
+ } catch (Exception e) {
+ // 预期会因为测试环境没有有效的签约关系而失败
+ System.out.println("立即扣费测试异常(预期):" + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testQueryTransactions() {
+ try {
+ SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService();
+
+ SubscriptionTransactionQueryRequest request = new SubscriptionTransactionQueryRequest();
+ request.setOpenid("test_openid");
+ request.setBeginTime("2024-08-01T00:00:00+08:00");
+ request.setEndTime("2024-08-31T23:59:59+08:00");
+ request.setLimit(20);
+ request.setOffset(0);
+
+ SubscriptionTransactionQueryResult result = service.queryTransactions(request);
+
+ System.out.println("查询扣费记录结果:" + result.toString());
+ assert result.getTotalCount() != null;
+
+ } catch (Exception e) {
+ // 预期会因为测试环境数据问题而失败
+ System.out.println("查询扣费记录测试异常(预期):" + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
From 76c7e994a54c27c9a787bcb04d39fa57ad7e73fe Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 15 Nov 2025 17:14:45 +0800
Subject: [PATCH 023/111] =?UTF-8?q?:bug:=20#3700=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E7=A9=BA=E6=8C=87=E9=92=88=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/open/bean/message/WxOpenXmlMessage.java | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java
index 9185a4e030..449697c5a6 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java
@@ -299,7 +299,9 @@ public static String wxMpOutXmlMessageToEncryptedXml(WxMpXmlOutMessage message,
public static WxOpenXmlMessage fromXml(String xml) {
//修改微信变态的消息内容格式,方便解析
- xml = xml.replace("", "");
+ if (xml != null) {
+ xml = xml.replace(" ", "");
+ }
return XStreamTransformer.fromXml(WxOpenXmlMessage.class, xml);
}
@@ -321,6 +323,11 @@ public static WxOpenXmlMessage fromEncryptedXml(String encryptedXml, WxOpenConfi
WxOpenCryptUtil cryptUtil = new WxOpenCryptUtil(wxOpenConfigStorage);
String plainText = cryptUtil.decryptXml(msgSignature, timestamp, nonce, encryptedXml);
log.debug("解密后的原始xml消息内容:{}", plainText);
+
+ if (plainText == null || plainText.trim().isEmpty()) {
+ throw new WxRuntimeException("解密后的xml消息内容为空,请检查加密参数是否正确");
+ }
+
WxOpenXmlMessage wxOpenXmlMessage = fromXml(plainText);
wxOpenXmlMessage.setContext(plainText);
return wxOpenXmlMessage;
From ebb9ccdebe2471cd86941f68bd6d308427f22e76 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 10:33:09 +0800
Subject: [PATCH 024/111] =?UTF-8?q?:art:=20#3376=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E5=AE=8C=E5=96=84=E4=BA=8B=E4=BB=B6?=
=?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81=E5=AF=B9json=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E6=A0=BC=E5=BC=8F=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../miniapp/message/WxMaJsonOutMessage.java | 67 +++++++++++++++++++
.../miniapp/message/WxMaMessageHandler.java | 6 +-
.../wx/miniapp/message/WxMaMessageRouter.java | 6 +-
.../message/WxMaMessageRouterRule.java | 4 +-
.../wx/miniapp/message/WxMaOutMessage.java | 43 ++++++++++++
.../wx/miniapp/message/WxMaXmlOutMessage.java | 22 +++++-
.../wx/miniapp/demo/WxMaDemoServer.java | 19 +++---
.../wx/miniapp/demo/WxMaPortalServlet.java | 18 +++--
.../message/WxMaJsonOutMessageTest.java | 56 ++++++++++++++++
9 files changed, 218 insertions(+), 23 deletions(-)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessage.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java
create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessage.java
new file mode 100644
index 0000000000..3f9bbe200c
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessage.java
@@ -0,0 +1,67 @@
+package cn.binarywang.wx.miniapp.message;
+
+import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * 微信小程序输出给微信服务器的JSON格式消息.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Accessors(chain = true)
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WxMaJsonOutMessage implements WxMaOutMessage {
+ private static final long serialVersionUID = 4241135225946919154L;
+
+ protected String toUserName;
+ protected String fromUserName;
+ protected Long createTime;
+ protected String msgType;
+
+ /**
+ * 转换成JSON格式.
+ */
+ @Override
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 转换成XML格式(对于JSON消息类型,返回JSON格式).
+ */
+ @Override
+ public String toXml() {
+ // JSON消息类型默认返回JSON格式
+ return toJson();
+ }
+
+ /**
+ * 转换成加密的JSON格式.
+ */
+ @Override
+ public String toEncryptedJson(WxMaConfig config) {
+ String plainJson = toJson();
+ WxMaCryptUtils pc = new WxMaCryptUtils(config);
+ return pc.encrypt(plainJson);
+ }
+
+ /**
+ * 转换成加密的XML格式(对于JSON消息类型,返回加密的JSON格式).
+ */
+ @Override
+ public String toEncryptedXml(WxMaConfig config) {
+ // JSON消息类型默认返回加密的JSON格式
+ return toEncryptedJson(config);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java
index 9fdd956934..c222692e8f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java
@@ -20,10 +20,10 @@ public interface WxMaMessageHandler {
* @param context 上下文
* @param service 服务类
* @param sessionManager session管理器
- * @return 输出消息
+ * @return 输出消息,可以是XML格式或JSON格式
* @throws WxErrorException 异常
*/
- WxMaXmlOutMessage handle(WxMaMessage message, Map context,
- WxMaService service, WxSessionManager sessionManager) throws WxErrorException;
+ WxMaOutMessage handle(WxMaMessage message, Map context,
+ WxMaService service, WxSessionManager sessionManager) throws WxErrorException;
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java
index fd369f517a..b46003d98b 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java
@@ -107,7 +107,7 @@ public WxMaMessageRouterRule rule() {
/**
* 处理微信消息.
*/
- public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map context) {
+ public WxMaOutMessage route(final WxMaMessage wxMessage, final Map context) {
if (isMsgDuplicated(wxMessage)) {
// 如果是重复消息,那么就不做处理
return null;
@@ -129,7 +129,7 @@ public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map> futures = new ArrayList<>();
- WxMaXmlOutMessage result = null;
+ WxMaOutMessage result = null;
for (final WxMaMessageRouterRule rule : matchRules) {
// 返回最后一个非异步的rule的执行结果
if (rule.isAsync()) {
@@ -168,7 +168,7 @@ public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map(2));
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java
index 99181e0434..ebff3fb50b 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java
@@ -201,7 +201,7 @@ protected boolean test(WxMaMessage wxMessage) {
/**
* 处理微信推送过来的消息.
*/
- protected WxMaXmlOutMessage service(WxMaMessage wxMessage,
+ protected WxMaOutMessage service(WxMaMessage wxMessage,
Map context,
WxMaService wxMaService,
WxSessionManager sessionManager,
@@ -210,7 +210,7 @@ protected WxMaXmlOutMessage service(WxMaMessage wxMessage,
context = new HashMap<>(16);
}
- WxMaXmlOutMessage outMessage = null;
+ WxMaOutMessage outMessage = null;
try {
// 如果拦截器不通过
for (WxMaMessageInterceptor interceptor : this.interceptors) {
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java
new file mode 100644
index 0000000000..595db304c5
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java
@@ -0,0 +1,43 @@
+package cn.binarywang.wx.miniapp.message;
+
+import cn.binarywang.wx.miniapp.config.WxMaConfig;
+
+import java.io.Serializable;
+
+/**
+ * 微信小程序输出消息的通用接口,支持XML和JSON两种格式.
+ *
+ * @author Binary Wang
+ */
+public interface WxMaOutMessage extends Serializable {
+
+ /**
+ * 转换成XML格式.
+ *
+ * @return XML格式的消息
+ */
+ String toXml();
+
+ /**
+ * 转换成JSON格式.
+ *
+ * @return JSON格式的消息
+ */
+ String toJson();
+
+ /**
+ * 转换成加密的XML格式.
+ *
+ * @param config 配置对象
+ * @return 加密后的XML格式消息
+ */
+ String toEncryptedXml(WxMaConfig config);
+
+ /**
+ * 转换成加密的JSON格式.
+ *
+ * @param config 配置对象
+ * @return 加密后的JSON格式消息
+ */
+ String toEncryptedJson(WxMaConfig config);
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java
index a6c2b828ae..b66563a95f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java
@@ -26,7 +26,7 @@
@Builder
@AllArgsConstructor
@NoArgsConstructor
-public class WxMaXmlOutMessage implements Serializable {
+public class WxMaXmlOutMessage implements WxMaOutMessage {
private static final long serialVersionUID = 4241135225946919153L;
@XStreamAlias("ToUserName")
@@ -45,16 +45,36 @@ public class WxMaXmlOutMessage implements Serializable {
protected String msgType;
@SuppressWarnings("unchecked")
+ @Override
public String toXml() {
return XStreamTransformer.toXml((Class) this.getClass(), this);
}
+ /**
+ * 转换成JSON格式(对于XML消息类型,返回XML格式).
+ */
+ @Override
+ public String toJson() {
+ // XML消息类型默认返回XML格式
+ return toXml();
+ }
+
/**
* 转换成加密的xml格式.
*/
+ @Override
public String toEncryptedXml(WxMaConfig config) {
String plainXml = toXml();
WxMaCryptUtils pc = new WxMaCryptUtils(config);
return pc.encrypt(plainXml);
}
+
+ /**
+ * 转换成加密的JSON格式(对于XML消息类型,返回加密的XML格式).
+ */
+ @Override
+ public String toEncryptedJson(WxMaConfig config) {
+ // XML消息类型默认返回加密的XML格式
+ return toEncryptedXml(config);
+ }
}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
index 7784cf3a1d..a1a796c715 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
@@ -8,6 +8,7 @@
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
+import cn.binarywang.wx.miniapp.message.WxMaOutMessage;
import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
import cn.binarywang.wx.miniapp.test.TestConfig;
import me.chanjar.weixin.common.api.WxConsts;
@@ -32,8 +33,8 @@ public class WxMaDemoServer {
private static final WxMaMessageHandler logHandler = new WxMaMessageHandler() {
@Override
- public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
- WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
+ public WxMaOutMessage handle(WxMaMessage wxMessage, Map context,
+ WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
System.out.println("收到消息:" + wxMessage.toString());
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
.toUser(wxMessage.getFromUser()).build());
@@ -43,8 +44,8 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte
private static final WxMaMessageHandler textHandler = new WxMaMessageHandler() {
@Override
- public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
- WxMaService service, WxSessionManager sessionManager)
+ public WxMaOutMessage handle(WxMaMessage wxMessage, Map context,
+ WxMaService service, WxSessionManager sessionManager)
throws WxErrorException {
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
.toUser(wxMessage.getFromUser()).build());
@@ -55,8 +56,8 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte
private static final WxMaMessageHandler picHandler = new WxMaMessageHandler() {
@Override
- public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
- WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
+ public WxMaOutMessage handle(WxMaMessage wxMessage, Map context,
+ WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
try {
WxMediaUploadResult uploadResult = service.getMediaService()
.uploadMedia(WxMaConstants.MediaType.IMAGE, "png",
@@ -76,8 +77,8 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte
private static final WxMaMessageHandler qrcodeHandler = new WxMaMessageHandler() {
@Override
- public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
- WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
+ public WxMaOutMessage handle(WxMaMessage wxMessage, Map context,
+ WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
try {
final File file = service.getQrcodeService().createQrcode("123", 430);
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia(WxMaConstants.MediaType.IMAGE, file);
@@ -96,7 +97,7 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte
private static final WxMaMessageHandler customerServiceMessageHandler = new WxMaMessageHandler() {
@Override
- public WxMaXmlOutMessage handle(WxMaMessage message, Map context, WxMaService service, WxSessionManager sessionManager) {
+ public WxMaOutMessage handle(WxMaMessage message, Map context, WxMaService service, WxSessionManager sessionManager) {
return new WxMaXmlOutMessage()
.setMsgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE)
.setFromUserName(message.getToUser())
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java
index c209082d45..cf004510cd 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java
@@ -5,7 +5,7 @@
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
-import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
+import cn.binarywang.wx.miniapp.message.WxMaOutMessage;
import lombok.AllArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@@ -62,9 +62,13 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
inMessage = WxMaMessage.fromXml(request.getInputStream());
}
- final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage);
+ final WxMaOutMessage outMessage = this.messageRouter.route(inMessage);
if (outMessage != null) {
- response.getWriter().write(outMessage.toXml());
+ if (isJson) {
+ response.getWriter().write(outMessage.toJson());
+ } else {
+ response.getWriter().write(outMessage.toXml());
+ }
return;
}
@@ -82,9 +86,13 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.config, timestamp, nonce, msgSignature);
}
- final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage);
+ final WxMaOutMessage outMessage = this.messageRouter.route(inMessage);
if (outMessage != null) {
- response.getWriter().write(outMessage.toEncryptedXml(this.config));
+ if (isJson) {
+ response.getWriter().write(outMessage.toEncryptedJson(this.config));
+ } else {
+ response.getWriter().write(outMessage.toEncryptedXml(this.config));
+ }
return;
}
response.getWriter().write("success");
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java
new file mode 100644
index 0000000000..09f3beaf22
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java
@@ -0,0 +1,56 @@
+package cn.binarywang.wx.miniapp.message;
+
+import me.chanjar.weixin.common.api.WxConsts;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WxMaJsonOutMessageTest {
+
+ @Test
+ public void testToJson() {
+ WxMaJsonOutMessage message = WxMaJsonOutMessage.builder()
+ .fromUserName("test_from_user")
+ .toUserName("test_to_user")
+ .msgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE)
+ .createTime(System.currentTimeMillis() / 1000)
+ .build();
+
+ String jsonResult = message.toJson();
+ assertThat(jsonResult).isNotEmpty();
+ assertThat(jsonResult).contains("test_from_user");
+ assertThat(jsonResult).contains("test_to_user");
+ assertThat(jsonResult).contains(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE);
+
+ System.out.println("JSON Output:");
+ System.out.println(jsonResult);
+ }
+
+ @Test
+ public void testEmptyMessage() {
+ WxMaJsonOutMessage message = new WxMaJsonOutMessage();
+ String jsonResult = message.toJson();
+ assertThat(jsonResult).isNotEmpty();
+ System.out.println("Empty message JSON:");
+ System.out.println(jsonResult);
+ }
+
+ @Test
+ public void testImplementsInterface() {
+ WxMaJsonOutMessage message = WxMaJsonOutMessage.builder()
+ .fromUserName("test_from_user")
+ .toUserName("test_to_user")
+ .msgType(WxConsts.XmlMsgType.TEXT)
+ .createTime(System.currentTimeMillis() / 1000)
+ .build();
+
+ // Test that it implements WxMaOutMessage interface
+ WxMaOutMessage outMessage = message;
+ assertThat(outMessage).isNotNull();
+
+ // Test both toJson and toXml methods (for JSON messages, both return JSON format)
+ assertThat(outMessage.toJson()).isNotEmpty();
+ assertThat(outMessage.toXml()).isNotEmpty();
+ assertThat(outMessage.toJson()).isEqualTo(outMessage.toXml());
+ }
+}
\ No newline at end of file
From 730ac24c3f9ceb816468d61966a3c323edbab6b5 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Mon, 17 Nov 2025 10:47:29 +0800
Subject: [PATCH 025/111] =?UTF-8?q?:art:=20=E4=BC=98=E5=8C=96=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E9=85=8D=E7=BD=AE=E7=B1=BB=E7=9A=84javadoc?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/config/WxMaConfig.java | 164 ++++++++++--------
1 file changed, 87 insertions(+), 77 deletions(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
index ba71b931cc..59652ed0a9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
@@ -16,9 +16,9 @@ public interface WxMaConfig {
default void setUpdateAccessTokenBefore(Consumer updateAccessTokenBefore) {}
/**
- * Gets access token.
+ * 获取当前的 access_token
*
- * @return the access token
+ * @return 当前的 access_token 字符串
*/
String getAccessToken();
@@ -30,26 +30,28 @@ default void setUpdateAccessTokenBefore(Consumer updateAcce
// endregion
/**
- * Gets access token lock.
+ * 获取用于保护 access_token 更新的锁(线程安全用)
*
- * @return the access token lock
+ * @return access_token 的锁对象
*/
Lock getAccessTokenLock();
/**
- * Is access token expired boolean.
+ * 判断 access_token 是否已过期
*
- * @return the boolean
+ * @return 如果已过期则返回 true,否则返回 false
*/
boolean isAccessTokenExpired();
- /** 强制将access token过期掉 */
+ /**
+ * 强制将 access_token 标记为已过期
+ */
void expireAccessToken();
/**
* 应该是线程安全的
*
- * @param accessToken 要更新的WxAccessToken对象
+ * @param accessToken 要更新的 WxAccessToken 对象
*/
default void updateAccessToken(WxAccessToken accessToken) {
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
@@ -58,8 +60,8 @@ default void updateAccessToken(WxAccessToken accessToken) {
/**
* 应该是线程安全的
*
- * @param accessToken 新的accessToken值
- * @param expiresInSeconds 过期时间,以秒为单位
+ * @param accessToken 新的 access_token 值
+ * @param expiresInSeconds 过期时间,单位:秒
*/
void updateAccessToken(String accessToken, int expiresInSeconds);
@@ -75,229 +77,237 @@ default void updateAccessTokenProcessor(String accessToken, int expiresInSeconds
default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {}
/**
- * Gets jsapi ticket.
+ * 获取当前的 JSAPI ticket
*
- * @return the jsapi ticket
+ * @return 当前的 jsapi_ticket 字符串
*/
String getJsapiTicket();
/**
- * Gets jsapi ticket lock.
+ * 获取用于保护 jsapi_ticket 更新的锁(线程安全用)
*
- * @return the jsapi ticket lock
+ * @return jsapi_ticket 的锁对象
*/
Lock getJsapiTicketLock();
/**
- * Is jsapi ticket expired boolean.
+ * 判断 jsapi_ticket 是否已过期
*
- * @return the boolean
+ * @return 如果已过期则返回 true,否则返回 false
*/
boolean isJsapiTicketExpired();
- /** 强制将jsapi ticket过期掉 */
+ /**
+ * 强制将 jsapi_ticket 标记为已过期
+ */
void expireJsapiTicket();
/**
* 应该是线程安全的
*
- * @param jsapiTicket 新的jsapi ticket值
- * @param expiresInSeconds 过期时间,以秒为单位
+ * @param jsapiTicket 新的 jsapi_ticket 值
+ * @param expiresInSeconds 过期时间,单位:秒
*/
void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);
/**
- * 卡券api_ticket.
+ * 获取卡券相关的 api_ticket
*
- * @return the card api ticket
+ * @return 卡券 api_ticket 字符串
*/
String getCardApiTicket();
/**
- * Gets card api ticket lock.
+ * 获取用于保护卡券 api_ticket 更新的锁(线程安全用)
*
- * @return the card api ticket lock
+ * @return 卡券 api_ticket 的锁对象
*/
Lock getCardApiTicketLock();
/**
- * Is card api ticket expired boolean.
+ * 判断卡券 api_ticket 是否已过期
*
- * @return the boolean
+ * @return 如果已过期则返回 true,否则返回 false
*/
boolean isCardApiTicketExpired();
- /** 强制将卡券api ticket过期掉. */
+ /**
+ * 强制将卡券 api_ticket 标记为已过期
+ */
void expireCardApiTicket();
/**
- * 应该是线程安全的.
+ * 应该是线程安全的
*
- * @param apiTicket 新的卡券api ticket值
- * @param expiresInSeconds 过期时间,以秒为单位
+ * @param apiTicket 新的卡券 api_ticket 值
+ * @param expiresInSeconds 过期时间,单位:秒
*/
void updateCardApiTicket(String apiTicket, int expiresInSeconds);
/**
- * Gets appid.
+ * 获取小程序的 appId
*
- * @return the appid
+ * @return 小程序的 appId
*/
String getAppid();
/**
- * Gets secret.
+ * 获取小程序的 secret
*
- * @return the secret
+ * @return 小程序的 secret
*/
String getSecret();
/**
- * Gets token.
+ * 获取消息校验用的 token
*
- * @return the token
+ * @return token 字符串
*/
String getToken();
/**
- * Gets aes key.
+ * 获取消息加解密使用的 AES 密钥(用于消息加密/解密)
*
- * @return the aes key
+ * @return AES 密钥字符串
*/
String getAesKey();
/**
- * Gets original id.
+ * 获取原始 ID(原始公众号/小程序 ID)
*
- * @return the original id
+ * @return 原始 ID 字符串
*/
String getOriginalId();
/**
- * Gets cloud env.
+ * 获取云开发(Cloud)环境标识
*
- * @return the cloud env
+ * @return 云环境 ID
*/
String getCloudEnv();
/**
- * Gets msg data format.
+ * 获取消息数据的格式(例如 json)
*
- * @return the msg data format
+ * @return 消息数据格式字符串
*/
String getMsgDataFormat();
/**
- * Gets expires time.
+ * 获取 access_token 或 ticket 的过期时间(时间戳)
*
- * @return the expires time
+ * @return 过期时间的毫秒时间戳
*/
long getExpiresTime();
/**
- * Gets http proxy host.
+ * 获取 HTTP 代理主机
*
- * @return the http proxy host
+ * @return 代理主机名或 IP
*/
String getHttpProxyHost();
/**
- * Gets http proxy port.
+ * 获取 HTTP 代理端口
*
- * @return the http proxy port
+ * @return 代理端口号
*/
int getHttpProxyPort();
/**
- * Gets http proxy username.
+ * 获取 HTTP 代理用户名
*
- * @return the http proxy username
+ * @return 代理用户名
*/
String getHttpProxyUsername();
/**
- * Gets http proxy password.
+ * 获取 HTTP 代理密码
*
- * @return the http proxy password
+ * @return 代理密码
*/
String getHttpProxyPassword();
/**
- * http 请求重试间隔
+ * HTTP 请求重试间隔(毫秒)
*
*
* {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
*
+ *
+ * @return 重试间隔,单位:毫秒
*/
int getRetrySleepMillis();
/**
- * http 请求最大重试次数
+ * HTTP 请求最大重试次数
*
*
* {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
*
+ *
+ * @return 最大重试次数
*/
int getMaxRetryTimes();
/**
- * http client builder
+ * 获取用于创建 HTTP 客户端的 ApacheHttpClientBuilder
*
- * @return ApacheHttpClientBuilder apache http client builder
+ * @return ApacheHttpClientBuilder 实例
*/
ApacheHttpClientBuilder getApacheHttpClientBuilder();
/**
- * 是否自动刷新token
+ * 是否在 token 失效时自动刷新
*
- * @return the boolean
+ * @return 如果自动刷新则返回 true,否则返回 false
*/
boolean autoRefreshToken();
/**
- * 设置自定义的apiHost地址
- * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html
+ * 设置自定义的 apiHost 地址
+ * 具体取值,可以参考 API 域名文档
*
- * @param apiHostUrl api域名地址
+ * @param apiHostUrl api 域名地址
*/
void setApiHostUrl(String apiHostUrl);
/**
- * 获取自定义的apiHost地址,用于替换原请求中的https://api.weixin.qq.com
- * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html
+ * 获取自定义的 apiHost 地址,用于替换原请求中的 https://api.weixin.qq.com
+ * 具体取值,可以参考 API 域名文档
*
- * @return 自定义的api域名地址
+ * @return 自定义的 api 域名地址
*/
String getApiHostUrl();
/**
- * 获取自定义的获取accessToken地址,用于向自定义统一服务获取accessToken
+ * 获取自定义的获取 accessToken 地址,用于向自定义统一服务获取 accessToken
*
- * @return 自定义的获取accessToken地址
+ * @return 自定义的获取 accessToken 地址
*/
String getAccessTokenUrl();
/**
- * 设置自定义的获取accessToken地址 可用于设置获取accessToken的自定义服务
+ * 设置自定义的获取 accessToken 地址,可用于设置获取 accessToken 的自定义服务
*
- * @param accessTokenUrl 自定义的获取accessToken地址
+ * @param accessTokenUrl 自定义的获取 accessToken 地址
*/
void setAccessTokenUrl(String accessTokenUrl);
/**
- * 服务端API签名用到的RSA私钥【pkcs8格式,会以 -----BEGIN PRIVATE KEY-----开头, 'BEGIN RSA PRIVATE
- * KEY'的是pkcs1格式,需要转换(可用openssl转换)。 设置参考:
- * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
+ * 服务端 API 签名用到的 RSA 私钥(pkcs8 格式,会以 -----BEGIN PRIVATE KEY----- 开头,
+ * 'BEGIN RSA PRIVATE KEY' 的是 pkcs1 格式,需要转换(可用 openssl 转换)。设置参考:
+ * API 签名文档
*
- * @return rsa private key string
+ * @return RSA 私钥字符串(pkcs8 格式)
*/
String getApiSignatureRsaPrivateKey();
/**
- * 服务端API签名用到的AES密钥
- * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
+ * 服务端 API 签名用到的 AES 密钥
+ * API 签名文档
*
- * @return aes key string
+ * @return AES 密钥字符串
*/
String getApiSignatureAesKey();
@@ -307,6 +317,6 @@ default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {}
/** 密钥对应的序号 */
String getApiSignatureRsaPrivateKeySn();
- /** 密钥对应的小程序ID (普通小程序同 appId, 托管第三方平台的是 componentAppId) */
+ /** 密钥对应的小程序 ID(普通小程序为 appId,托管第三方平台为 componentAppId) */
String getWechatMpAppid();
}
From b8d2bb345a0120818ecab31264ca8024cde92b44 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Mon, 17 Nov 2025 10:58:02 +0800
Subject: [PATCH 026/111] =?UTF-8?q?:memo:=20=E6=B7=BB=E5=8A=A0=E8=87=AA?=
=?UTF-8?q?=E5=AE=9A=E4=B9=89agent=E7=9A=84=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/agents/my-agent.agent.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100644 .github/agents/my-agent.agent.md
diff --git a/.github/agents/my-agent.agent.md b/.github/agents/my-agent.agent.md
new file mode 100644
index 0000000000..c51fbf9d6d
--- /dev/null
+++ b/.github/agents/my-agent.agent.md
@@ -0,0 +1,13 @@
+---
+# Fill in the fields below to create a basic custom agent for your repository.
+# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli
+# To make this agent available, merge this file into the default repository branch.
+# For format details, see: https://gh.io/customagents/config
+
+name: 自定义的
+description: 需要用中文
+---
+
+# My Agent
+
+请使用中文输出思考过程和总结,提交commit信息也要使用中文
From 3c440eacb384998f1efafe736a41ce7b021a432b Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 11:11:51 +0800
Subject: [PATCH 027/111] =?UTF-8?q?:art:=20=E4=BF=AE=E5=A4=8D=20GsonParser?=
=?UTF-8?q?=20=E4=B8=8E=E4=BD=8E=E7=89=88=E6=9C=AC=20Gson=20=E7=9A=84?=
=?UTF-8?q?=E5=85=BC=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/common/util/json/GsonParser.java | 6 +--
.../common/util/json/GsonParserTest.java | 47 +++++++++++++++++++
2 files changed, 50 insertions(+), 3 deletions(-)
create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonParserTest.java
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java
index f2646436c0..caa07d0eaf 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java
@@ -12,14 +12,14 @@
public class GsonParser {
public static JsonObject parse(String json) {
- return JsonParser.parseString(json).getAsJsonObject();
+ return new JsonParser().parse(json).getAsJsonObject();
}
public static JsonObject parse(Reader json) {
- return JsonParser.parseReader(json).getAsJsonObject();
+ return new JsonParser().parse(json).getAsJsonObject();
}
public static JsonObject parse(JsonReader json) {
- return JsonParser.parseReader(json).getAsJsonObject();
+ return new JsonParser().parse(json).getAsJsonObject();
}
}
diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonParserTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonParserTest.java
new file mode 100644
index 0000000000..ea069d4155
--- /dev/null
+++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonParserTest.java
@@ -0,0 +1,47 @@
+package me.chanjar.weixin.common.util.json;
+
+import com.google.gson.JsonObject;
+import com.google.gson.stream.JsonReader;
+import org.testng.annotations.Test;
+
+import java.io.StringReader;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * GsonParser 测试类
+ *
+ * @author Binary Wang
+ */
+public class GsonParserTest {
+
+ @Test
+ public void testParseString() {
+ String json = "{\"code\":\"ALREADY_EXISTS\",\"message\":\"当前订单已关闭,可查询订单了解关闭原因\"}";
+ JsonObject jsonObject = GsonParser.parse(json);
+ assertNotNull(jsonObject);
+ assertEquals(jsonObject.get("code").getAsString(), "ALREADY_EXISTS");
+ assertEquals(jsonObject.get("message").getAsString(), "当前订单已关闭,可查询订单了解关闭原因");
+ }
+
+ @Test
+ public void testParseReader() {
+ String json = "{\"code\":\"SUCCESS\",\"message\":\"处理成功\"}";
+ StringReader reader = new StringReader(json);
+ JsonObject jsonObject = GsonParser.parse(reader);
+ assertNotNull(jsonObject);
+ assertEquals(jsonObject.get("code").getAsString(), "SUCCESS");
+ assertEquals(jsonObject.get("message").getAsString(), "处理成功");
+ }
+
+ @Test
+ public void testParseJsonReader() {
+ String json = "{\"errcode\":0,\"errmsg\":\"ok\"}";
+ JsonReader jsonReader = new JsonReader(new StringReader(json));
+ JsonObject jsonObject = GsonParser.parse(jsonReader);
+ assertNotNull(jsonObject);
+ assertEquals(jsonObject.get("errcode").getAsInt(), 0);
+ assertEquals(jsonObject.get("errmsg").getAsString(), "ok");
+ }
+}
From 1922aaaa94c42c547d5d5238198acdf7e7d6865e Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 11:59:51 +0800
Subject: [PATCH 028/111] =?UTF-8?q?:art:=20#3751=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E6=8F=90=E4=BA=A4=E5=AE=A1?=
=?UTF-8?q?=E6=89=B9=E7=94=B3=E8=AF=B7=E6=8E=A5=E5=8F=A3=E5=A2=9E=E5=8A=A0?=
=?UTF-8?q?process=E5=8F=82=E6=95=B0=E4=BB=A5=E6=94=AF=E6=8C=81=E6=96=B0?=
=?UTF-8?q?=E7=89=88=E7=9A=84=E5=AE=A1=E6=89=B9=E6=B5=81=E7=A8=8B=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/bean/oa/WxCpOaApplyEventRequest.java | 50 +++++++++-
.../bean/oa/WxCpOaApplyEventRequestTest.java | 95 +++++++++++++++++++
2 files changed, 144 insertions(+), 1 deletion(-)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java
index 8aebb66003..30ae6497f8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java
@@ -44,7 +44,13 @@ public class WxCpOaApplyEventRequest implements Serializable {
private Integer chooseDepartment;
/**
- * 审批流程信息,用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
+ * 审批流程信息(新版流程列表),用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
+ */
+ @SerializedName("process")
+ private Process process;
+
+ /**
+ * 审批流程信息(旧版),用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
*/
@SerializedName("approver")
private List approvers;
@@ -118,4 +124,46 @@ public static class ApplyData implements Serializable {
private List contents;
}
+ /**
+ * 审批流程信息(新版).
+ */
+ @Data
+ @Accessors(chain = true)
+ public static class Process implements Serializable {
+ private static final long serialVersionUID = 4758206091546930988L;
+
+ /**
+ * 审批流程节点列表,当use_template_approver为0时必填
+ */
+ @SerializedName("node_list")
+ private List nodeList;
+ }
+
+ /**
+ * 审批流程节点.
+ */
+ @Data
+ @Accessors(chain = true)
+ public static class ProcessNode implements Serializable {
+ private static final long serialVersionUID = 1758206091546930988L;
+
+ /**
+ * 节点类型:1-审批人,2-抄送人,3-抄送人
+ */
+ @SerializedName("type")
+ private Integer type;
+
+ /**
+ * 多人审批方式:1-全签,2-或签,3-依次审批
+ */
+ @SerializedName("apv_rel")
+ private Integer apvRel;
+
+ /**
+ * 审批节点审批人userid列表,若为多人会签、多人或签,需填写每个人的userid
+ */
+ @SerializedName("userid")
+ private String[] userIds;
+ }
+
}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java
index f2dedb4ddf..e6fc2e8004 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java
@@ -95,4 +95,99 @@ public void testToJson() {
assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString());
}
+
+ /**
+ * Test to json with process.
+ */
+ @Test
+ public void testToJsonWithProcess() {
+ String json = "{\n" +
+ " \"creator_userid\": \"WangXiaoMing\",\n" +
+ " \"template_id\": \"3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioeFXkaaa\",\n" +
+ " \"use_template_approver\":0,\n" +
+ " \"process\": {\n" +
+ " \"node_list\": [\n" +
+ " {\n" +
+ " \"type\": 1,\n" +
+ " \"apv_rel\": 2,\n" +
+ " \"userid\": [\"WuJunJie\",\"WangXiaoMing\"]\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": 1,\n" +
+ " \"apv_rel\": 1,\n" +
+ " \"userid\": [\"LiuXiaoGang\"]\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": 2,\n" +
+ " \"userid\": [\"ZhangSan\",\"LiSi\"]\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"apply_data\": {\n" +
+ " \"contents\": [\n" +
+ " {\n" +
+ " \"control\": \"Text\",\n" +
+ " \"id\": \"Text-15111111111\",\n" +
+ " \"value\": {\n" +
+ " \"text\": \"文本填写的内容\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"summary_list\": [\n" +
+ " {\n" +
+ " \"summary_info\": [{\n" +
+ " \"text\": \"摘要第1行\",\n" +
+ " \"lang\": \"zh_CN\"\n" +
+ " }]\n" +
+ " },\n" +
+ " {\n" +
+ " \"summary_info\": [{\n" +
+ " \"text\": \"摘要第2行\",\n" +
+ " \"lang\": \"zh_CN\"\n" +
+ " }]\n" +
+ " },\n" +
+ " {\n" +
+ " \"summary_info\": [{\n" +
+ " \"text\": \"摘要第3行\",\n" +
+ " \"lang\": \"zh_CN\"\n" +
+ " }]\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest();
+ request.setCreatorUserId("WangXiaoMing")
+ .setTemplateId("3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioeFXkaaa")
+ .setUseTemplateApprover(0)
+ .setProcess(new WxCpOaApplyEventRequest.Process()
+ .setNodeList(Arrays.asList(
+ new WxCpOaApplyEventRequest.ProcessNode()
+ .setType(1)
+ .setApvRel(2)
+ .setUserIds(new String[]{"WuJunJie", "WangXiaoMing"}),
+ new WxCpOaApplyEventRequest.ProcessNode()
+ .setType(1)
+ .setApvRel(1)
+ .setUserIds(new String[]{"LiuXiaoGang"}),
+ new WxCpOaApplyEventRequest.ProcessNode()
+ .setType(2)
+ .setUserIds(new String[]{"ZhangSan", "LiSi"})
+ )))
+ .setApplyData(new WxCpOaApplyEventRequest.ApplyData()
+ .setContents(Collections.singletonList(new ApplyDataContent()
+ .setControl("Text").setId("Text-15111111111").setValue(new ContentValue().setText("文本填写的内容")))))
+ .setSummaryList(Arrays.asList(new SummaryInfo()
+ .setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
+ "摘要第1行"))),
+ new SummaryInfo()
+ .setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
+ "摘要第2行"))),
+ new SummaryInfo()
+ .setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
+ "摘要第3行")))))
+ ;
+
+ assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString());
+ }
}
From 833f60c68d218796d935d55e6d58455e2776b972 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 12:01:15 +0800
Subject: [PATCH 029/111] =?UTF-8?q?:new:=20#3736=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E5=A2=9E=E5=8A=A0=E8=A7=A3=E5=AF=86?=
=?UTF-8?q?=E7=BE=A4=E5=85=A5=E5=8F=A3=E6=95=8F=E6=84=9F=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/api/WxMaShareService.java | 13 ++++++
.../api/impl/WxMaShareServiceImpl.java | 6 +++
.../wx/miniapp/bean/WxMaGroupEnterInfo.java | 46 +++++++++++++++++++
.../api/impl/WxMaShareServiceImplTest.java | 13 ++++++
4 files changed, 78 insertions(+)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java
index 8c6030e53c..d32aee2c16 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java
@@ -1,5 +1,6 @@
package cn.binarywang.wx.miniapp.api;
+import cn.binarywang.wx.miniapp.bean.WxMaGroupEnterInfo;
import cn.binarywang.wx.miniapp.bean.WxMaShareInfo;
/**
@@ -18,4 +19,16 @@ public interface WxMaShareService {
*/
WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, String ivStr);
+ /**
+ * 解密群入口敏感数据.
+ * 对应 wx.getGroupEnterInfo 接口返回的 encryptedData 解密
+ *
+ * @param sessionKey 会话密钥
+ * @param encryptedData 消息密文
+ * @param ivStr 加密算法的初始向量
+ * @return 群入口信息
+ * @see wx.getGroupEnterInfo 官方文档
+ */
+ WxMaGroupEnterInfo getGroupEnterInfo(String sessionKey, String encryptedData, String ivStr);
+
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java
index fd1981aa03..a3a8e6176f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java
@@ -2,6 +2,7 @@
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaShareService;
+import cn.binarywang.wx.miniapp.bean.WxMaGroupEnterInfo;
import cn.binarywang.wx.miniapp.bean.WxMaShareInfo;
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
import lombok.RequiredArgsConstructor;
@@ -18,4 +19,9 @@ public WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, Strin
return WxMaShareInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
}
+
+ @Override
+ public WxMaGroupEnterInfo getGroupEnterInfo(String sessionKey, String encryptedData, String ivStr) {
+ return WxMaGroupEnterInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java
new file mode 100644
index 0000000000..e65ec602da
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java
@@ -0,0 +1,46 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 微信小程序群入口信息.
+ * 对应 wx.getGroupEnterInfo 接口返回的解密数据
+ *
+ * @see wx.getGroupEnterInfo 官方文档
+ */
+@Data
+public class WxMaGroupEnterInfo implements Serializable {
+ private static final long serialVersionUID = -8053613683499632227L;
+
+ /**
+ * 多聊群下返回的群唯一标识.
+ */
+ @SerializedName("opengid")
+ private String openGId;
+
+ /**
+ * 单聊群下返回的群唯一标识.
+ */
+ @SerializedName("open_single_roomid")
+ private String openSingleRoomid;
+
+ /**
+ * 用户在当前群的唯一标识.
+ */
+ @SerializedName("group_openid")
+ private String groupOpenid;
+
+ /**
+ * 聊天室类型.
+ */
+ @SerializedName("chat_type")
+ private Integer chatType;
+
+ public static WxMaGroupEnterInfo fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaGroupEnterInfo.class);
+ }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java
index dcf3726e33..f6d041ff35 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java
@@ -1,6 +1,7 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaGroupEnterInfo;
import cn.binarywang.wx.miniapp.bean.WxMaShareInfo;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.inject.Inject;
@@ -37,4 +38,16 @@ public void testGetShareInfo() {
assertNotNull(shareInfo);
System.out.println(shareInfo.toString());
}
+
+ /**
+ * TODO 测试数据有问题,需要替换为正确的数据
+ */
+ @Test
+ public void testGetGroupEnterInfo() {
+ WxMaGroupEnterInfo groupEnterInfo = this.wxService.getShareService().getGroupEnterInfo("tiihtNczf5v6AKRyjwEUhQ==",
+ "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==",
+ "r7BXXKkLb8qrSNn05n0qiA==");
+ assertNotNull(groupEnterInfo);
+ System.out.println(groupEnterInfo.toString());
+ }
}
From e57b7e1eb565dd7f80ddb3c8083d4ac58b2d000c Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 27 Nov 2025 23:04:11 +0800
Subject: [PATCH 030/111] =?UTF-8?q?:art:=20#3765=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E4=BB=8E?=
=?UTF-8?q?=20base64=20=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=8A=A0=E8=BD=BD?=
=?UTF-8?q?=E7=A7=81=E9=92=A5=E6=97=B6=E7=9A=84=E5=8F=8C=E9=87=8D=E8=A7=A3?=
=?UTF-8?q?=E7=A0=81=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/config/WxPayConfig.java | 43 ++++++++++++++++---
1 file changed, 38 insertions(+), 5 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index efae89ce93..ba62a8a502 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -55,6 +55,7 @@ public class WxPayConfig {
private static final String DEFAULT_PAY_BASE_URL = "https://api.mch.weixin.qq.com";
private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!";
private static final String NOT_FOUND_MSG = "证书文件【%s】不存在,请核实!";
+ private static final String CERT_NAME_P12 = "p12证书";
/**
* 微信支付接口请求地址域名部分.
@@ -306,7 +307,7 @@ public SSLContext initSSLContext() throws WxPayException {
}
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
- this.keyContent, "p12证书")) {
+ this.keyContent, CERT_NAME_P12)) {
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] partnerId2charArray = this.getMchId().toCharArray();
keystore.load(inputStream, partnerId2charArray);
@@ -437,12 +438,34 @@ private InputStream loadConfigInputStream(String configString, String configPath
if (StringUtils.isNotEmpty(configString)) {
// 判断是否为PEM格式的字符串(包含-----BEGIN和-----END标记)
- if (configString.contains("-----BEGIN") && configString.contains("-----END")) {
+ if (isPemFormat(configString)) {
// PEM格式直接转为字节流,让PemUtils处理
configContent = configString.getBytes(StandardCharsets.UTF_8);
} else {
- // 纯Base64格式,需要先解码
- configContent = Base64.getDecoder().decode(configString);
+ // 尝试Base64解码
+ try {
+ byte[] decoded = Base64.getDecoder().decode(configString);
+ // 检查解码后的内容是否为PEM格式(即用户传入的是base64编码的完整PEM文件)
+ String decodedString = new String(decoded, StandardCharsets.UTF_8);
+ if (isPemFormat(decodedString)) {
+ // 解码后是PEM格式,使用解码后的内容
+ configContent = decoded;
+ } else {
+ // 解码后不是PEM格式,可能是:
+ // 1. p12证书的二进制内容 - 应该返回解码后的二进制数据
+ // 2. 私钥/公钥的纯base64内容(不含PEM头尾) - 应该返回原始字符串,让PemUtils处理
+ // 通过certName区分:p12证书使用解码后的数据,其他情况返回原始字符串
+ if (CERT_NAME_P12.equals(certName)) {
+ configContent = decoded;
+ } else {
+ // 对于私钥/公钥/证书,返回原始字符串字节,让PemUtils处理base64解码
+ configContent = configString.getBytes(StandardCharsets.UTF_8);
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ // Base64解码失败,可能是格式不正确,抛出异常
+ throw new WxPayException(String.format("【%s】的Base64格式不正确", certName), e);
+ }
}
return new ByteArrayInputStream(configContent);
}
@@ -454,6 +477,16 @@ private InputStream loadConfigInputStream(String configString, String configPath
return this.loadConfigInputStream(configPath);
}
+ /**
+ * 判断字符串是否为PEM格式(包含-----BEGIN和-----END标记)
+ *
+ * @param content 要检查的字符串
+ * @return 是否为PEM格式
+ */
+ private boolean isPemFormat(String content) {
+ return content != null && content.contains("-----BEGIN") && content.contains("-----END");
+ }
+
/**
* 从配置路径 加载配置 信息(支持 classpath、本地路径、网络url)
@@ -523,7 +556,7 @@ private Object[] p12ToPem() {
// 分解p12证书文件
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
- this.keyContent, "p12证书")) {
+ this.keyContent, CERT_NAME_P12)) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(inputStream, key.toCharArray());
From 21d5f2fdd2a8648dd9632dbe20b149added4dd6d Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 27 Nov 2025 23:06:19 +0800
Subject: [PATCH 031/111] =?UTF-8?q?:new:=20#3764=20=E3=80=90=E5=BC=80?=
=?UTF-8?q?=E6=94=BE=E5=B9=B3=E5=8F=B0=E3=80=91=E6=B7=BB=E5=8A=A0=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=B1=BB=E7=9B=AE=E7=AE=A1=E7=90=86=20-=20?=
=?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=B1=BB=E7=9B=AE=E5=90=8D=E7=A7=B0=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF=E7=9A=84=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/open/api/WxOpenMaBasicService.java | 16 ++++
.../api/impl/WxOpenFastMaServiceImpl.java | 6 ++
.../api/impl/WxOpenMaBasicServiceImpl.java | 6 ++
.../WxOpenMaCategoryNameListResult.java | 75 +++++++++++++++++++
4 files changed, 103 insertions(+)
create mode 100644 weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaCategoryNameListResult.java
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java
index 3952f7dbf9..5929f83902 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java
@@ -73,6 +73,10 @@ public interface WxOpenMaBasicService {
* 8.6 修改类目
*/
String OPEN_MODIFY_CATEGORY = "https://api.weixin.qq.com/cgi-bin/wxopen/modifycategory";
+ /**
+ * 8.7 获取类目名称信息
+ */
+ String OPEN_GET_ALL_CATEGORY_NAME = "https://api.weixin.qq.com/cgi-bin/wxopen/getallcategorynamelist";
/**
* 获取订单页path信息
@@ -222,6 +226,18 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
*/
WxOpenResult modifyCategory(WxFastMaCategory category) throws WxErrorException;
+ /**
+ * 8.7 获取类目名称信息
+ *
+ * 获取所有类目名称信息,用于给用户展示选择
+ * https://developers.weixin.qq.com/doc/oplatform/openApi/miniprogram-management/category-management/api_getallcategoryname.html
+ *
+ *
+ * @return 类目名称列表
+ * @throws WxErrorException .
+ */
+ WxOpenMaCategoryNameListResult getAllCategoryName() throws WxErrorException;
+
/**
* 获取订单页Path信息
*
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java
index 84ff7b495c..c80ce03c3e 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java
@@ -152,6 +152,12 @@ public WxOpenResult modifyCategory(WxFastMaCategory category) throws WxErrorExce
return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
}
+ @Override
+ public WxOpenMaCategoryNameListResult getAllCategoryName() throws WxErrorException {
+ String response = get(OPEN_GET_ALL_CATEGORY_NAME, "");
+ return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaCategoryNameListResult.class);
+ }
+
/**
* 获取订单页Path信息
*
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java
index 56d209a322..6204b1e260 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java
@@ -146,6 +146,12 @@ public WxOpenResult modifyCategory(WxFastMaCategory category) throws WxErrorExce
return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
}
+ @Override
+ public WxOpenMaCategoryNameListResult getAllCategoryName() throws WxErrorException {
+ String response = wxMaService.get(OPEN_GET_ALL_CATEGORY_NAME, "");
+ return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaCategoryNameListResult.class);
+ }
+
/**
* 获取订单页Path信息
*
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaCategoryNameListResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaCategoryNameListResult.java
new file mode 100644
index 0000000000..014a4ae84a
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaCategoryNameListResult.java
@@ -0,0 +1,75 @@
+package me.chanjar.weixin.open.bean.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 获取类目名称信息的返回结果.
+ *
+ * 用于获取所有小程序类目的 ID 和名称信息,包括一级类目和二级类目。
+ *
+ *
+ * @author Binary Wang
+ * @see 官方文档
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxOpenMaCategoryNameListResult extends WxOpenResult {
+ private static final long serialVersionUID = 8989721350285449879L;
+
+ /**
+ * 类目名称列表.
+ */
+ @SerializedName("category_name_list")
+ private List categoryNameList;
+
+ @Override
+ public String toString() {
+ return WxOpenGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 类目名称信息.
+ *
+ * 包含一级类目和二级类目的 ID 和名称。
+ *
+ */
+ @Data
+ public static class CategoryName implements Serializable {
+ private static final long serialVersionUID = 8989721350285449880L;
+
+ /**
+ * 一级类目ID.
+ */
+ @SerializedName("first_id")
+ private Integer firstId;
+
+ /**
+ * 一级类目名称.
+ */
+ @SerializedName("first_name")
+ private String firstName;
+
+ /**
+ * 二级类目ID.
+ */
+ @SerializedName("second_id")
+ private Integer secondId;
+
+ /**
+ * 二级类目名称.
+ */
+ @SerializedName("second_name")
+ private String secondName;
+
+ @Override
+ public String toString() {
+ return WxOpenGsonBuilder.create().toJson(this);
+ }
+ }
+}
From 7fcd1ed5b323b8f7bc5aeb5a146db7c2ff33c668 Mon Sep 17 00:00:00 2001
From: hyf1844
Date: Thu, 27 Nov 2025 23:14:16 +0800
Subject: [PATCH 032/111] =?UTF-8?q?:art:=20#3767=20=E3=80=90=E8=A7=86?=
=?UTF-8?q?=E9=A2=91=E5=8F=B7=E3=80=91=E5=BE=AE=E4=BF=A1=E5=B0=8F=E5=BA=97?=
=?UTF-8?q?=E8=AE=A2=E5=8D=95=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=E4=BC=9A=E5=91=98=E6=9D=83=E7=9B=8A=E7=AD=89=E4=BC=98?=
=?UTF-8?q?=E6=83=A0=E9=87=91=E9=A2=9D=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../channel/bean/order/ChangeSkuInfo.java | 42 ++++++++++++++
.../channel/bean/order/DropshipInfo.java | 24 ++++++++
.../channel/bean/order/FreeGiftInfo.java | 25 ++++++++
.../channel/bean/order/MainProductInfo.java | 42 ++++++++++++++
.../channel/bean/order/OrderCouponInfo.java | 20 +++++++
.../channel/bean/order/OrderPriceInfo.java | 30 ++++++++++
.../channel/bean/order/OrderProductInfo.java | 57 ++++++++++++++++++-
7 files changed, 239 insertions(+), 1 deletion(-)
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.java
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.java
new file mode 100644
index 0000000000..b40a497755
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.java
@@ -0,0 +1,42 @@
+package me.chanjar.weixin.channel.bean.order;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 更换sku信息
+ */
+@Data
+@NoArgsConstructor
+public class ChangeSkuInfo implements Serializable {
+
+ private static final long serialVersionUID = 8783442929429377519L;
+
+ /**
+ * 发货前更换sku状态。3:等待商家处理,4:商家审核通过,5:商家拒绝,6:用户主动取消,7:超时默认拒绝
+ */
+ @JsonProperty("preshipment_change_sku_state")
+ private Integer preshipmentChangeSkuState;
+
+ /**
+ * 原sku_id
+ */
+ @JsonProperty("old_sku_id")
+ private String oldSkuId;
+
+ /**
+ * 用户申请更换的sku_id
+ */
+ @JsonProperty("new_sku_id")
+ private String newSkuId;
+
+ /**
+ * 商家处理请求的最后时间,只有当前换款请求处于"等待商家处理"才有值
+ */
+ @JsonProperty("ddl_time_stamp")
+ private Integer deadlineTimeStamp;
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java
new file mode 100644
index 0000000000..9c5340376d
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java
@@ -0,0 +1,24 @@
+package me.chanjar.weixin.channel.bean.order;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 代发相关信息
+ */
+@Data
+@NoArgsConstructor
+public class DropshipInfo implements Serializable {
+
+ private static final long serialVersionUID = -4562618835611282016L;
+
+ /**
+ * 代发单号
+ */
+ @JsonProperty("ds_order_id")
+ private Long dsOrderId;
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java
new file mode 100644
index 0000000000..b2612cfccd
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java
@@ -0,0 +1,25 @@
+package me.chanjar.weixin.channel.bean.order;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 赠品信息
+ */
+@Data
+@NoArgsConstructor
+public class FreeGiftInfo implements Serializable {
+
+ private static final long serialVersionUID = 2024061212345678901L;
+
+ /**
+ * 赠品对应的主品信息
+ */
+ @JsonProperty("main_product_list")
+ private List mainProductList;
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java
new file mode 100644
index 0000000000..bb13c0b0b7
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java
@@ -0,0 +1,42 @@
+package me.chanjar.weixin.channel.bean.order;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 赠品对应的主品信息
+ */
+@Data
+@NoArgsConstructor
+public class MainProductInfo implements Serializable {
+
+ private static final long serialVersionUID = 2024061212345678901L;
+
+ /**
+ * 赠品数量
+ */
+ @JsonProperty("gift_cnt")
+ private Integer giftCnt;
+
+ /**
+ * 活动id
+ */
+ @JsonProperty("task_id")
+ private Integer taskId;
+
+ /**
+ * 商品id
+ */
+ @JsonProperty("product_id")
+ private Integer productId;
+
+ /**
+ * 主品sku_id
+ */
+ @JsonProperty("sku_id")
+ private Integer skuId;
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java
index a8f020c0ef..34f2d670d0 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java
@@ -18,4 +18,24 @@ public class OrderCouponInfo implements Serializable {
/** 用户优惠券id */
@JsonProperty("user_coupon_id")
private String userCouponId;
+
+ /**
+ * 优惠券类型
+ * 1 商家优惠
+ * 2 达人优惠
+ * 3 平台优惠
+ * 4 国家补贴
+ * 5 地方补贴
+ */
+ @JsonProperty("coupon_type")
+ private Integer couponType;
+
+ /** 优惠金额,单位为分,该张优惠券、抵扣该商品的金额 */
+ @JsonProperty("discounted_price")
+ private Integer discountedPrice;
+
+ /** 优惠券id */
+ @JsonProperty("coupon_id")
+ private String couponId;
+
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java
index cad435df2b..50eac04e50 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java
@@ -107,4 +107,34 @@ public class OrderPriceInfo implements Serializable {
@JsonProperty("finder_discounted_price")
private Integer finderDiscountedPrice;
+ /**
+ * 订单维度会员权益优惠金额
+ */
+ @JsonProperty("vip_discounted_price")
+ private Integer vipDiscountedPrice;
+
+ /**
+ * 订单维度一起买优惠金额,单位为分
+ */
+ @JsonProperty("bulkbuy_discounted_price")
+ private Integer bulkbuyDiscountedPrice;
+
+ /**
+ * 订单维度国补优惠金额
+ */
+ @JsonProperty("national_subsidy_discounted_price")
+ private Integer nationalSubsidyDiscountedPrice;
+
+ /**
+ * 订单维度平台券优惠金额,单位为分
+ */
+ @JsonProperty("cash_coupon_discounted_price")
+ private Integer cashCouponDiscountedPrice;
+
+ /**
+ * 订单维度地方补贴优惠金额(商家出资),单位为分
+ */
+ @JsonProperty("national_subsidy_merchant_discounted_price")
+ private Integer nationalSubsidyMerchantDiscountedPrice;
+
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java
index 1e49455dc4..5c91c61897 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java
@@ -176,7 +176,7 @@ public class OrderProductInfo implements Serializable {
private Integer merchantDiscountedPrice;
/**
- * 商家优惠金额,单位为分
+ * 达人优惠金额,单位为分
*/
@JsonProperty("finder_discounted_price")
private Integer finderDiscountedPrice;
@@ -186,4 +186,59 @@ public class OrderProductInfo implements Serializable {
*/
@JsonProperty("is_free_gift")
private Boolean freeGift;
+
+ /**
+ * 订单内商品维度会员权益优惠金额,单位为分
+ */
+ @JsonProperty("vip_discounted_price")
+ private Integer vipDiscountedPrice;
+
+ /**
+ * 商品常量编号,订单内商品唯一标识,下单后不会发生变化
+ */
+ @JsonProperty("product_unique_id")
+ private String productUniqueId;
+
+ /**
+ * 更换sku信息
+ */
+ @JsonProperty("change_sku_info")
+ private ChangeSkuInfo changeSkuInfo;
+
+ /**
+ * 赠品信息
+ */
+ @JsonProperty("free_gift_info")
+ private FreeGiftInfo freeGiftInfo;
+
+ /**
+ * 订单内商品维度一起买优惠金额,单位为分
+ */
+ @JsonProperty("bulkbuy_discounted_price")
+ private Integer bulkbuyDiscountedPrice;
+
+ /**
+ * 订单内商品维度国补优惠金额,单位为分
+ */
+ @JsonProperty("national_subsidy_discounted_price")
+ private Integer nationalSubsidyDiscountedPrice;
+
+ /**
+ * 代发相关信息
+ */
+ @JsonProperty("dropship_info")
+ private DropshipInfo dropshipInfo;
+
+ /**
+ * 是否闪购商品
+ */
+ @JsonProperty("is_flash_sale")
+ private Boolean flashSale;
+
+ /**
+ * 订单内商品维度地方补贴优惠金额(商家出资),单位为分
+ */
+ @JsonProperty("national_subsidy_merchant_discounted_price")
+ private Integer nationalSubsidyMerchantDiscountedPrice;
+
}
From b1e7cd2cd782b12291887fb1bd839e0783e1d6da Mon Sep 17 00:00:00 2001
From: Dell Well <36149947+Crow0687@users.noreply.github.com>
Date: Thu, 27 Nov 2025 23:16:49 +0800
Subject: [PATCH 033/111] =?UTF-8?q?:bug:=20#3664:=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E5=88=86=E8=AE=A2=E5=8D=95=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E6=8F=90=E7=A4=BA=E2=80=9C=E5=95=86=E6=88=B7=E6=9A=82?=
=?UTF-8?q?=E6=97=A0=E6=9D=83=E9=99=90=E4=BD=BF=E7=94=A8=E6=AD=A4=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E2=80=9D=20=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/service/impl/PayScoreServiceImpl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java
index 249cfa3f56..ee92c6611a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java
@@ -230,7 +230,7 @@ public WxPayScoreResult modifyServiceOrder(WxPayScoreRequest request) throws WxP
if(Strings.isNullOrEmpty(request.getAppid())){
request.setAppid(config.getAppId());
}
- if(Strings.isNullOrEmpty(config.getServiceId())){
+ if(Strings.isNullOrEmpty(request.getServiceId())){
request.setServiceId(config.getServiceId());
}
request.setOutOrderNo(null);
From 8e46da50035855a0577e1e0706486e5f56c9352d Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 27 Nov 2025 23:20:04 +0800
Subject: [PATCH 034/111] =?UTF-8?q?:art:=20#3683=20=20=E3=80=90=E5=BC=80?=
=?UTF-8?q?=E6=94=BE=E5=B9=B3=E5=8F=B0=E3=80=91=E4=BF=AE=E6=94=B9component?=
=?UTF-8?q?=5Fverify=5Fticket=20=E7=9A=84=E6=9C=89=E6=95=88=E6=97=B6?=
=?UTF-8?q?=E9=97=B4=E4=B8=BA12=E5=B0=8F=E6=97=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../open/api/impl/WxOpenInRedisConfigStorage.java | 2 +-
.../impl/WxOpenInRedisTemplateConfigStorage.java | 2 +-
.../api/impl/WxOpenInRedissonConfigStorage.java | 2 +-
.../api/impl/WxOpenInRedisConfigStorageTest.java | 14 ++++++++++++++
.../impl/WxOpenInRedissonConfigStorageTest.java | 14 ++++++++++++++
5 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
index 9ce7851065..c6dbc8f468 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
@@ -36,7 +36,7 @@ public String getComponentVerifyTicket() {
@Override
public void setComponentVerifyTicket(String componentVerifyTicket) {
- redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS);
+ redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, 43200, TimeUnit.SECONDS);
}
@Override
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java
index 42034a1ffc..cb55e45ad0 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java
@@ -37,7 +37,7 @@ public String getComponentVerifyTicket() {
@Override
public void setComponentVerifyTicket(String componentVerifyTicket) {
- redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS);
+ redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, 43200, TimeUnit.SECONDS);
}
@Override
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java
index 7a3a9d79af..0de9b88d7e 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java
@@ -36,7 +36,7 @@ public String getComponentVerifyTicket() {
@Override
public void setComponentVerifyTicket(String componentVerifyTicket) {
- redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS);
+ redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, 43200, TimeUnit.SECONDS);
}
@Override
diff --git a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java
index 26a30a2040..bd82081113 100644
--- a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java
+++ b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java
@@ -128,4 +128,18 @@ public void testGetCardApiTicket() {
expired = this.wxOpenConfigStorage.isCardApiTicketExpired(appid);
Assert.assertEquals(expired, true);
}
+
+ @Test
+ public void testComponentVerifyTicketExpiration() {
+ // Test that ComponentVerifyTicket is set correctly
+ this.wxOpenConfigStorage.setComponentVerifyTicket("test_ticket_for_expiration");
+ String componentVerifyTicket = this.wxOpenConfigStorage.getComponentVerifyTicket();
+ Assert.assertEquals(componentVerifyTicket, "test_ticket_for_expiration");
+
+ // This test verifies that setComponentVerifyTicket now uses 43200 seconds (12 hours)
+ // instead of Integer.MAX_VALUE for expiration. The actual expiration test would
+ // require waiting or mocking time, which is not practical in unit tests.
+ // The change is validated by code inspection and the fact that other tokens
+ // use similar expiration patterns with specific timeouts.
+ }
}
diff --git a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java
index 7168d93f1b..8042853de2 100644
--- a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java
+++ b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java
@@ -126,4 +126,18 @@ public void testGetCardApiTicket() {
expired = this.wxOpenConfigStorage.isCardApiTicketExpired(appid);
Assert.assertEquals(expired, true);
}
+
+ @Test
+ public void testComponentVerifyTicketExpiration() {
+ // Test that ComponentVerifyTicket is set correctly
+ this.wxOpenConfigStorage.setComponentVerifyTicket("test_ticket_for_expiration");
+ String componentVerifyTicket = this.wxOpenConfigStorage.getComponentVerifyTicket();
+ Assert.assertEquals(componentVerifyTicket, "test_ticket_for_expiration");
+
+ // This test verifies that setComponentVerifyTicket now uses 43200 seconds (12 hours)
+ // instead of Integer.MAX_VALUE for expiration. The actual expiration test would
+ // require waiting or mocking time, which is not practical in unit tests.
+ // The change is validated by code inspection and the fact that other tokens
+ // use similar expiration patterns with specific timeouts.
+ }
}
From 1bd50d4d2f83cd2bc802044fc3820c9c036dafc7 Mon Sep 17 00:00:00 2001
From: chu <1723407619@qq.com>
Date: Thu, 27 Nov 2025 14:57:33 +0000
Subject: [PATCH 035/111] =?UTF-8?q?:new:=20#3770=20=E3=80=90=E8=A7=86?=
=?UTF-8?q?=E9=A2=91=E5=8F=B7=E3=80=91=E6=96=B0=E5=A2=9E=E5=94=AE=E5=90=8E?=
=?UTF-8?q?=E5=8D=95=E5=95=86=E5=AE=B6=E5=8D=8F=E5=95=86=E3=80=81=E6=8D=A2?=
=?UTF-8?q?=E8=B4=A7=E5=8F=91=E8=B4=A7=E4=B8=8E=E6=8B=92=E7=BB=9D=E5=8F=91?=
=?UTF-8?q?=E8=B4=A7=E7=AD=89=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/WxChannelAfterSaleService.java | 44 ++++++++++++--
.../impl/WxChannelAfterSaleServiceImpl.java | 25 +++++++-
.../AfterSaleAcceptExchangeReshipParam.java | 35 ++++++++++++
.../after/AfterSaleExchangeProductInfo.java | 8 +++
.../after/AfterSaleMerchantUpdateParam.java | 57 +++++++++++++++++++
.../AfterSaleRejectExchangeReshipParam.java | 41 +++++++++++++
.../bean/after/AfterSaleRejectReason.java | 5 ++
.../bean/complaint/ComplaintHistory.java | 2 +-
.../channel/bean/order/OrderPayInfo.java | 8 +--
.../weixin/channel/bean/product/SpuInfo.java | 12 ++++
.../constant/WxChannelApiUrlConstants.java | 6 ++
11 files changed, 230 insertions(+), 13 deletions(-)
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java
create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java
index dedbf5e4f2..85c945d428 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java
@@ -2,11 +2,8 @@
import java.util.List;
-import me.chanjar.weixin.channel.bean.after.AfterSaleInfoResponse;
-import me.chanjar.weixin.channel.bean.after.AfterSaleListParam;
-import me.chanjar.weixin.channel.bean.after.AfterSaleListResponse;
-import me.chanjar.weixin.channel.bean.after.AfterSaleReasonResponse;
-import me.chanjar.weixin.channel.bean.after.AfterSaleRejectReasonResponse;
+
+import me.chanjar.weixin.channel.bean.after.*;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
import me.chanjar.weixin.channel.bean.complaint.ComplaintOrderResponse;
import me.chanjar.weixin.common.error.WxErrorException;
@@ -149,4 +146,41 @@ WxChannelBaseResponse addComplaintEvidence(String complaintId, String content, L
* @throws WxErrorException 异常
*/
AfterSaleRejectReasonResponse getRejectReason() throws WxErrorException;
+
+ /**
+ * 换货发货
+ * 文档地址:https://developers.weixin.qq.com/doc/store/shop/API/channels-shop-aftersale/api_acceptexchangereship.html
+ *
+ * @param afterSaleOrderId 售后单号
+ * @param waybillId 快递单号
+ * @param deliveryId 快递公司id
+ * @return BaseResponse
+ *
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse acceptExchangeReship(String afterSaleOrderId, String waybillId, String deliveryId) throws WxErrorException;
+
+ /**
+ * 换货拒绝发货
+ * 文档地址:https://developers.weixin.qq.com/doc/store/shop/API/channels-shop-aftersale/api_rejectexchangereship.html
+ *
+ * @param afterSaleOrderId 售后单号
+ * @param rejectReason 拒绝原因具体描述 ,可使用默认描述,也可以自定义描述
+ * @param rejectReasonType 拒绝原因枚举值
+ * @param rejectCertificates 退款凭证,可使用图片上传接口获取media_id(数据类型填0)
+ * @return BaseResponse
+ *
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse rejectExchangeReship(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List rejectCertificates) throws WxErrorException;
+
+ /**
+ * 商家协商
+ * 文档地址:https://developers.weixin.qq.com/doc/store/shop/API/channels-shop-aftersale/api_merchantupdateaftersale.html
+ * @param param 参数
+ * @return BaseResponse
+ *
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse merchantUpdateAfterSale(AfterSaleMerchantUpdateParam param) throws WxErrorException;
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java
index 4e314d52fa..92f865444b 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java
@@ -22,7 +22,9 @@
@Slf4j
public class WxChannelAfterSaleServiceImpl implements WxChannelAfterSaleService {
- /** 微信商店服务 */
+ /**
+ * 微信商店服务
+ */
private final BaseWxChannelServiceImpl, ?> shopService;
public WxChannelAfterSaleServiceImpl(BaseWxChannelServiceImpl, ?> shopService) {
@@ -107,4 +109,25 @@ public AfterSaleRejectReasonResponse getRejectReason() throws WxErrorException {
String resJson = shopService.post(AFTER_SALE_REJECT_REASON_GET_URL, "{}");
return ResponseUtils.decode(resJson, AfterSaleRejectReasonResponse.class);
}
+
+ @Override
+ public WxChannelBaseResponse acceptExchangeReship(String afterSaleOrderId, String waybillId, String deliveryId) throws WxErrorException {
+ AfterSaleAcceptExchangeReshipParam param = new AfterSaleAcceptExchangeReshipParam(afterSaleOrderId, waybillId, deliveryId);
+ String resJson = shopService.post(AFTER_SALE_ACCEPT_EXCHANGE_RESHIP_URL, param);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse rejectExchangeReship(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List rejectCertificates) throws WxErrorException {
+ AfterSaleRejectExchangeReshipParam param = new AfterSaleRejectExchangeReshipParam(afterSaleOrderId, rejectReason, rejectReasonType, rejectCertificates);
+ String resJson = shopService.post(AFTER_SALE_REJECT_EXCHANGE_RESHIP_URL, param);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse merchantUpdateAfterSale(AfterSaleMerchantUpdateParam param) throws WxErrorException {
+ String resJson = shopService.post(AFTER_SALE_MERCHANT_UPDATE_URL, param);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java
new file mode 100644
index 0000000000..66be1c715d
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java
@@ -0,0 +1,35 @@
+package me.chanjar.weixin.channel.bean.after;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 售后单换货发货信息
+ *
+ * @author Chu
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AfterSaleAcceptExchangeReshipParam extends AfterSaleIdParam {
+ private static final long serialVersionUID = -7946679037747710613L;
+
+ /** 快递单号*/
+ @JsonProperty("waybill_id")
+ private String waybillId;
+
+ /** 快递公司id,通过获取快递公司列表接口获得,非主流快递公司可以填OTHER*/
+ @JsonProperty("delivery_id")
+ private String deliveryId;
+
+ public AfterSaleAcceptExchangeReshipParam() {
+
+ }
+
+ public AfterSaleAcceptExchangeReshipParam(String afterSaleOrderId, String waybillId, String deliveryId) {
+ super(afterSaleOrderId);
+ this.waybillId = waybillId;
+ this.deliveryId = deliveryId;
+ }
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java
index 1e862791ea..a73d6ae310 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java
@@ -31,4 +31,12 @@ public class AfterSaleExchangeProductInfo implements Serializable {
/** 数量 */
@JsonProperty("product_cnt")
private String productCnt;
+
+ /** 旧商品价格 */
+ @JsonProperty("old_sku_price")
+ private Integer oldSkuPrice;
+
+ /** 新商品价格 */
+ @JsonProperty("new_sku_price")
+ private Integer newSkuPrice;
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java
new file mode 100644
index 0000000000..275577e1df
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java
@@ -0,0 +1,57 @@
+package me.chanjar.weixin.channel.bean.after;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 售后单商家协商信息
+ *
+ * @author Chu
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AfterSaleMerchantUpdateParam extends AfterSaleIdParam {
+ private static final long serialVersionUID = -3672834150982780L;
+
+ /** 协商修改把售后单修改成该售后类型。1:退款;2:退货退款*/
+ @JsonProperty("type")
+ private Integer type;
+
+ /** 金额(单位:分)*/
+ @JsonProperty("amount")
+ private Integer amount;
+
+ /** 协商描述*/
+ @JsonProperty("merchant_update_desc")
+ private String merchantUpdateDesc;
+
+ /** 协商原因*/
+ @JsonProperty("update_reason_type")
+ private Integer updateReasonType;
+
+ /** 1:已协商一致,邀请买家取消售后; 2:邀请买家核实与补充凭证; 3:修改买家售后申请*/
+ @JsonProperty("merchant_update_type")
+ private Integer merchantUpdateType;
+
+ /** 协商凭证id列表,可使用图片上传接口获取media_id(数据类型填0),当update_reason_type对应的need_image为1时必填*/
+ @JsonProperty("media_ids")
+ private List mediaIds;
+
+ public AfterSaleMerchantUpdateParam() {
+ }
+
+ public AfterSaleMerchantUpdateParam(String afterSaleOrderId, Integer type, Integer updateReasonType, Integer merchantUpdateType
+ , Integer amount, String merchantUpdateDesc, List mediaIds) {
+ super(afterSaleOrderId);
+ this.type = type;
+ this.updateReasonType = updateReasonType;
+ this.merchantUpdateType = merchantUpdateType;
+ this.amount = amount;
+ this.merchantUpdateDesc = merchantUpdateDesc;
+ this.mediaIds = mediaIds;
+ }
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java
new file mode 100644
index 0000000000..668ffa11e9
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java
@@ -0,0 +1,41 @@
+package me.chanjar.weixin.channel.bean.after;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 售后单换货拒绝发货信息
+ *
+ * @author Chu
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AfterSaleRejectExchangeReshipParam extends AfterSaleIdParam {
+ private static final long serialVersionUID = -7946679037747710613L;
+
+ /** 拒绝原因具体描述 ,可使用默认描述,也可以自定义描述*/
+ @JsonProperty("reject_reason")
+ private String rejectReason;
+
+ /** 拒绝原因枚举 */
+ @JsonProperty("reject_reason_type")
+ private Integer rejectReasonType;
+
+ /** 退款凭证,可使用图片上传接口获取media_id(数据类型填0)*/
+ @JsonProperty("reject_certificates")
+ private List rejectCertificates;
+
+ public AfterSaleRejectExchangeReshipParam() {
+ }
+
+ public AfterSaleRejectExchangeReshipParam(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List rejectCertificates) {
+ super(afterSaleOrderId);
+ this.rejectReason = rejectReason;
+ this.rejectReasonType = rejectReasonType;
+ this.rejectCertificates = rejectCertificates;
+ }
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java
index 51c88ae222..7987153ec0 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java
@@ -36,4 +36,9 @@ public class AfterSaleRejectReason implements Serializable {
@JsonProperty("reject_reason")
private String rejectReason;
+ /**
+ * 售后拒绝原因适用场景
+ */
+ @JsonProperty("reject_scene")
+ private Integer rejectScene;
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java
index 4570fdc615..84a558b2b1 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java
@@ -26,7 +26,7 @@ public class ComplaintHistory implements Serializable {
/** 用户联系电话 */
@JsonProperty("phone_number")
- private Integer phoneNumber;
+ private String phoneNumber;
/** 相关文本内容 */
@JsonProperty("content")
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java
index 6c912f7c45..7a9f367d76 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java
@@ -16,12 +16,8 @@ public class OrderPayInfo implements Serializable {
private static final long serialVersionUID = -5085386252699113948L;
/** 预支付id */
- @JsonProperty("prepayId")
- private String prepayId;
-
- /** 预支付时间,秒级时间戳 */
- @JsonProperty("prepay_time")
- private Long prepayTime;
+ @JsonProperty("payment_method")
+ private Integer paymentMethod;
/** 支付时间,秒级时间戳 */
@JsonProperty("pay_time")
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java
index a160a31373..155148c178 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java
@@ -139,4 +139,16 @@ public class SpuInfo extends SpuSimpleInfo {
/** 尺码表信息 */
@JsonProperty("size_chart")
private SpuSizeChart sizeChart;
+
+ /** 短标题 */
+ @JsonProperty("short_title")
+ private String shortTitle;
+
+ /** 销量 */
+ @JsonProperty("total_sold_num")
+ private Integer totalSoldNum;
+
+ /** 发布模式,0: 普通模式;1: 极简模式 */
+ @JsonProperty("release_mode")
+ private Integer releaseMode;
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
index b7d3add72a..2d9aa84f84 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
@@ -232,6 +232,12 @@ public interface AfterSale {
String AFTER_SALE_REASON_GET_URL = "https://api.weixin.qq.com/channels/ec/aftersale/reason/get";
/** 获取拒绝售后原因*/
String AFTER_SALE_REJECT_REASON_GET_URL = "https://api.weixin.qq.com/channels/ec/aftersale/rejectreason/get";
+ /** 换货发货*/
+ String AFTER_SALE_ACCEPT_EXCHANGE_RESHIP_URL = "https://api.weixin.qq.com/channels/ec/aftersale/acceptexchangereship";
+ /** 换货拒绝发货*/
+ String AFTER_SALE_REJECT_EXCHANGE_RESHIP_URL = "https://api.weixin.qq.com/channels/ec/aftersale/rejectexchangereship";
+ /** 商家协商*/
+ String AFTER_SALE_MERCHANT_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantupdateaftersale";
}
/** 纠纷相关接口 */
From 9940546956f1645871aeb57aaed24076892e5afe Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:15:41 +0800
Subject: [PATCH 036/111] =?UTF-8?q?:bug:=20#3675=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E4=BF=AE=E5=A4=8D=E9=80=80=E8=B4=A7?=
=?UTF-8?q?=E7=BB=84=E4=BB=B6=20API=20=E6=8E=A5=E5=8F=A3=E8=AF=B7=E6=B1=82?=
=?UTF-8?q?=E6=96=B9=E6=B3=95=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/impl/WxMaExpressDeliveryReturnServiceImpl.java | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java
index aef8a2cad2..df4d9780c9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java
@@ -14,23 +14,23 @@ public class WxMaExpressDeliveryReturnServiceImpl implements WxMaExpressDelivery
@Override
public WxMaExpressReturnInfoResult addDeliveryReturn(WxMaExpressDeliveryReturnAddRequest wxMaExpressDeliveryReturnAddRequest) throws WxErrorException {
- String result= this.service.get(ADD_DELIVERY_RETURN_URL,wxMaExpressDeliveryReturnAddRequest.toJson());
+ String result = this.service.post(ADD_DELIVERY_RETURN_URL, wxMaExpressDeliveryReturnAddRequest.toJson());
return WxMaExpressReturnInfoResult.fromJson(result);
}
@Override
public WxMaExpressReturnInfoResult getDeliveryReturn(String returnId) throws WxErrorException {
JsonObject param = new JsonObject();
- param.addProperty("return_id",returnId);
- String result= this.service.get(GET_DELIVERY_RETURN_URL,param.toString());
+ param.addProperty("return_id", returnId);
+ String result = this.service.post(GET_DELIVERY_RETURN_URL, param);
return WxMaExpressReturnInfoResult.fromJson(result);
}
@Override
public WxMaExpressReturnInfoResult unbindDeliveryReturn(String returnId) throws WxErrorException {
JsonObject param = new JsonObject();
- param.addProperty("return_id",returnId);
- String result= this.service.get(UNBIND_DELIVERY_RETURN_URL,param.toString());
+ param.addProperty("return_id", returnId);
+ String result = this.service.post(UNBIND_DELIVERY_RETURN_URL, param);
return WxMaExpressReturnInfoResult.fromJson(result);
}
}
From e655a33956bbc54fbcc6c47c591625bd9ef33daa Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:17:33 +0800
Subject: [PATCH 037/111] =?UTF-8?q?:art:=20#3752=20=E4=BF=AE=E5=A4=8D=20Gs?=
=?UTF-8?q?on=20=E5=9C=A8=20Java=209+=20=E7=8E=AF=E5=A2=83=E4=B8=8B?=
=?UTF-8?q?=E5=8F=8D=E5=B0=84=E8=AE=BF=E9=97=AE=20java.io.File#path=20?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/common/util/json/WxGsonBuilder.java | 16 ++++++++++++++++
.../weixin/cp/util/json/WxCpGsonBuilder.java | 16 ++++++++++++++++
.../wx/miniapp/json/WxMaGsonBuilder.java | 17 +++++++++++++++++
.../open/util/json/WxOpenGsonBuilder.java | 17 +++++++++++++++++
.../qidian/util/json/WxQidianGsonBuilder.java | 17 +++++++++++++++++
5 files changed, 83 insertions(+)
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
index ff260c16fb..6ea269f7e4 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
@@ -1,5 +1,7 @@
package me.chanjar.weixin.common.util.json;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.chanjar.weixin.common.bean.WxAccessToken;
@@ -7,6 +9,9 @@
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+
+import java.io.File;
import java.util.Objects;
/**
@@ -25,6 +30,17 @@ public class WxGsonBuilder {
INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter());
INSTANCE.registerTypeAdapter(WxNetCheckResult.class, new WxNetCheckResultGsonAdapter());
+ INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> aClass) {
+ return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+ }
+ });
}
public static Gson create() {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
index 7b53aa337a..48228a0686 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
@@ -1,9 +1,12 @@
package me.chanjar.weixin.cp.util.json;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.json.WxErrorAdapter;
import me.chanjar.weixin.cp.bean.WxCpChat;
import me.chanjar.weixin.cp.bean.WxCpDepart;
@@ -11,6 +14,7 @@
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.bean.kf.WxCpKfGetCorpStatisticResp;
+import java.io.File;
import java.util.Objects;
/**
@@ -32,6 +36,18 @@ public class WxCpGsonBuilder {
INSTANCE.registerTypeAdapter(WxMenu.class, new WxCpMenuGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpKfGetCorpStatisticResp.StatisticList.class, new StatisticListAdapter());
+
+ INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> aClass) {
+ return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+ }
+ });
}
/**
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java
index 5379826656..33cc28d6a3 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java
@@ -9,8 +9,13 @@
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
import cn.binarywang.wx.miniapp.json.adaptor.*;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+
+import java.io.File;
import java.util.Objects;
/**
@@ -30,6 +35,18 @@ public class WxMaGsonBuilder {
INSTANCE.registerTypeAdapter(WxMaRetainInfo.class, new WxMaRetainInfoGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaUserPortrait.class, new WxMaUserPortraitGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaSubscribeMsgEvent.WxMaSubscribeMsgEventJson.class, new WxMaSubscribeMsgEventJsonAdapter());
+
+ INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> aClass) {
+ return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+ }
+ });
}
public static Gson create() {
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java
index 9cb4abd072..6b07438b11 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java
@@ -1,12 +1,17 @@
package me.chanjar.weixin.open.util.json;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken;
import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken;
import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo;
import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo;
import me.chanjar.weixin.open.bean.result.*;
+
+import java.io.File;
import java.util.Objects;
/**
@@ -27,6 +32,18 @@ public class WxOpenGsonBuilder {
INSTANCE.registerTypeAdapter(WxOpenAuthorizerInfoResult.class, new WxOpenAuthorizerInfoResultGsonAdapter());
INSTANCE.registerTypeAdapter(WxOpenAuthorizerOptionResult.class, new WxOpenAuthorizerOptionResultGsonAdapter());
INSTANCE.registerTypeAdapter(WxOpenAuthorizerListResult.class, new WxOpenAuthorizerListResultGsonAdapter());
+
+ INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> aClass) {
+ return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+ }
+ });
}
public static Gson create() {
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java
index bdce6bbedd..2e38b20220 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java
@@ -1,7 +1,12 @@
package me.chanjar.weixin.qidian.util.json;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+
+import java.io.File;
/**
* @author someone
@@ -12,6 +17,18 @@ public class WxQidianGsonBuilder {
static {
INSTANCE.disableHtmlEscaping();
+
+ INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> aClass) {
+ return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+ }
+ });
}
public static Gson create() {
From 1da6cf56ff19c71ef1fddccaab36f5f035050c5a Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:19:38 +0800
Subject: [PATCH 038/111] =?UTF-8?q?:art:=20#3755=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E4=BF=AE=E5=A4=8D=E4=BC=9A?=
=?UTF-8?q?=E8=AF=9D=E5=AD=98=E6=A1=A3SDK=E9=87=8D=E5=A4=8D=E5=88=9D?=
=?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=AF=BC=E8=87=B4=E6=8E=A5=E5=8F=A3=E8=B6=85?=
=?UTF-8?q?=E9=99=90=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/api/impl/WxCpMsgAuditServiceImpl.java | 108 ++++++++++--------
.../weixin/cp/config/WxCpConfigStorage.java | 29 +++++
.../cp/config/impl/WxCpDefaultConfigImpl.java | 31 ++++-
.../cp/config/impl/WxCpRedisConfigImpl.java | 27 +++++
4 files changed, 148 insertions(+), 47 deletions(-)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
index 7f9b693938..cdf559ad7a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
@@ -11,6 +11,7 @@
import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.msgaudit.*;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
@@ -35,20 +36,59 @@
public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
private final WxCpService cpService;
+ /**
+ * SDK初始化有效期,根据企微文档为7200秒
+ */
+ private static final int SDK_EXPIRES_TIME = 7200;
+
@Override
public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd,
@NonNull long timeout) throws Exception {
- String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath();
+ // 获取或初始化SDK
+ long sdk = this.initSdk();
+
+ long slice = Finance.NewSlice();
+ long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
+ if (ret != 0) {
+ Finance.FreeSlice(slice);
+ throw new WxErrorException("getchatdata err ret " + ret);
+ }
+
+ // 拉取会话存档
+ String content = Finance.GetContentFromSlice(slice);
+ Finance.FreeSlice(slice);
+ WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
+ if (chatDatas.getErrCode().intValue() != 0) {
+ throw new WxErrorException(chatDatas.toJson());
+ }
+
+ chatDatas.setSdk(sdk);
+ return chatDatas;
+ }
+
+ /**
+ * 获取或初始化SDK,如果SDK已过期则重新初始化
+ *
+ * @return sdk id
+ * @throws WxErrorException 初始化失败时抛出异常
+ */
+ private synchronized long initSdk() throws WxErrorException {
+ WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
+
+ // 检查SDK是否已缓存且未过期
+ if (!configStorage.isMsgAuditSdkExpired()) {
+ long cachedSdk = configStorage.getMsgAuditSdk();
+ if (cachedSdk > 0) {
+ return cachedSdk;
+ }
+ }
+
+ // SDK未初始化或已过期,需要重新初始化
+ String configPath = configStorage.getMsgAuditLibPath();
if (StringUtils.isEmpty(configPath)) {
throw new WxErrorException("请配置会话存档sdk文件的路径,不要配错了!!");
}
- /**
- * 完整的文件库路径:
- *
- * /www/osfile/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll,
- * libWeWorkFinanceSdk_Java.so
- */
// 替换斜杠
String replacePath = configPath.replace("\\", "/");
// 获取最后一个斜杠的下标,用作分割路径
@@ -79,36 +119,22 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S
Finance.loadingLibraries(osLib, prefixPath);
long sdk = Finance.NewSdk();
- //因为会话存档单独有个secret,优先使用会话存档的secret
- String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret();
+ // 因为会话存档单独有个secret,优先使用会话存档的secret
+ String msgAuditSecret = configStorage.getMsgAuditSecret();
if (StringUtils.isEmpty(msgAuditSecret)) {
- msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret();
+ msgAuditSecret = configStorage.getCorpSecret();
}
- long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret);
+ long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret);
if (ret != 0) {
Finance.DestroySdk(sdk);
throw new WxErrorException("init sdk err ret " + ret);
}
- long slice = Finance.NewSlice();
- ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
- if (ret != 0) {
- Finance.FreeSlice(slice);
- Finance.DestroySdk(sdk);
- throw new WxErrorException("getchatdata err ret " + ret);
- }
-
- // 拉取会话存档
- String content = Finance.GetContentFromSlice(slice);
- Finance.FreeSlice(slice);
- WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
- if (chatDatas.getErrCode().intValue() != 0) {
- Finance.DestroySdk(sdk);
- throw new WxErrorException(chatDatas.toJson());
- }
+ // 缓存SDK
+ configStorage.updateMsgAuditSdk(sdk, SDK_EXPIRES_TIME);
+ log.debug("初始化会话存档SDK成功,sdk={}", sdk);
- chatDatas.setSdk(sdk);
- return chatDatas;
+ return sdk;
}
@Override
@@ -128,36 +154,27 @@ public WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.Wx
* @throws Exception the exception
*/
public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception {
- /**
- * 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
- * msgAuditPriKey 会话存档私钥不能为空
- */
+ // 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
+ // msgAuditPriKey 会话存档私钥不能为空
String priKey = cpService.getWxCpConfigStorage().getMsgAuditPriKey();
if (StringUtils.isEmpty(priKey)) {
throw new WxErrorException("请配置会话存档私钥【msgAuditPriKey】");
}
String decryptByPriKey = WxCpCryptUtil.decryptPriKey(chatData.getEncryptRandomKey(), priKey, pkcs1);
- /**
- * 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
- */
+ // 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
long msg = Finance.NewSlice();
- /**
- * 解密会话存档内容
- * sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
- * 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
- */
+ // 解密会话存档内容
+ // sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
+ // 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg);
if (ret != 0) {
Finance.FreeSlice(msg);
- Finance.DestroySdk(sdk);
throw new WxErrorException("msg err ret " + ret);
}
- /**
- * 明文
- */
+ // 明文
String plainText = Finance.GetContentFromSlice(msg);
Finance.FreeSlice(msg);
return plainText;
@@ -209,7 +226,6 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr
ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData);
if (ret != 0) {
Finance.FreeMediaData(mediaData);
- Finance.DestroySdk(sdk);
throw new WxErrorException("getmediadata err ret " + ret);
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
index 36203aab11..8b968e540c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
@@ -260,7 +260,36 @@ public interface WxCpConfigStorage {
/**
* 获取会话存档的secret
+ *
* @return msg audit secret
*/
String getMsgAuditSecret();
+
+ /**
+ * 获取会话存档SDK
+ * 会话存档SDK初始化后有效期为7200秒,无需每次重新初始化
+ *
+ * @return sdk id,如果未初始化或已过期返回0
+ */
+ long getMsgAuditSdk();
+
+ /**
+ * 检查会话存档SDK是否已过期
+ *
+ * @return true: 已过期, false: 未过期
+ */
+ boolean isMsgAuditSdkExpired();
+
+ /**
+ * 更新会话存档SDK
+ *
+ * @param sdk sdk id
+ * @param expiresInSeconds 过期时间(秒)
+ */
+ void updateMsgAuditSdk(long sdk, int expiresInSeconds);
+
+ /**
+ * 使会话存档SDK过期
+ */
+ void expireMsgAuditSdk();
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
index 57647b3712..4bf13f24ea 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
@@ -49,6 +49,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
private volatile String msgAuditSecret;
private volatile String msgAuditPriKey;
private volatile String msgAuditLibPath;
+ /**
+ * 会话存档SDK及其过期时间
+ */
+ private volatile long msgAuditSdk;
+ private volatile long msgAuditSdkExpiresTime;
private volatile String oauth2redirectUri;
private volatile String httpProxyHost;
private volatile int httpProxyPort;
@@ -444,10 +449,34 @@ public String getMsgAuditSecret() {
/**
* 设置会话存档secret
- * @param msgAuditSecret
+ *
+ * @param msgAuditSecret the msg audit secret
+ * @return this
*/
public WxCpDefaultConfigImpl setMsgAuditSecret(String msgAuditSecret) {
this.msgAuditSecret = msgAuditSecret;
return this;
}
+
+ @Override
+ public long getMsgAuditSdk() {
+ return this.msgAuditSdk;
+ }
+
+ @Override
+ public boolean isMsgAuditSdkExpired() {
+ return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
+ }
+
+ @Override
+ public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
+ this.msgAuditSdk = sdk;
+ // 预留200秒的时间
+ this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+ }
+
+ @Override
+ public void expireMsgAuditSdk() {
+ this.msgAuditSdkExpiresTime = 0;
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
index 4225cff0af..15c0ff6727 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
@@ -50,6 +50,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
private volatile File tmpDirFile;
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
private volatile String webhookKey;
+ /**
+ * 会话存档SDK及其过期时间(SDK是本地JVM变量,不适合存储到Redis)
+ */
+ private volatile long msgAuditSdk;
+ private volatile long msgAuditSdkExpiresTime;
/**
* Instantiates a new Wx cp redis config.
@@ -470,4 +475,26 @@ public String getWebhookKey() {
public String getMsgAuditSecret() {
return null;
}
+
+ @Override
+ public long getMsgAuditSdk() {
+ return this.msgAuditSdk;
+ }
+
+ @Override
+ public boolean isMsgAuditSdkExpired() {
+ return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
+ }
+
+ @Override
+ public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
+ this.msgAuditSdk = sdk;
+ // 预留200秒的时间
+ this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+ }
+
+ @Override
+ public void expireMsgAuditSdk() {
+ this.msgAuditSdkExpiresTime = 0;
+ }
}
From f6d9e8ba4d22a4b1aa9b12d2f6d9e26ab15ac273 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:20:51 +0800
Subject: [PATCH 039/111] =?UTF-8?q?:art:=20#3738=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8DV3?=
=?UTF-8?q?=E6=96=B0=E5=95=86=E6=88=B7=E5=AE=8C=E5=85=A8=E5=85=AC=E9=92=A5?=
=?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=8BpublicKey=E4=B8=BA=E7=A9=BA=E7=9A=84?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/github/binarywang/wxpay/config/WxPayConfig.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index ba62a8a502..f4a1c3d008 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -377,6 +377,9 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
Verifier certificatesVerifier;
if (this.fullPublicKeyModel) {
// 使用完全公钥模式时,只加载公钥相关配置,避免下载平台证书使灰度切换无法达到100%覆盖
+ if (publicKey == null) {
+ throw new WxPayException("完全公钥模式下,请确保公钥配置(publicKeyPath/publicKeyString/publicKeyContent)及publicKeyId已设置");
+ }
certificatesVerifier = VerifierBuilder.buildPublicCertVerifier(this.publicKeyId, publicKey);
} else {
certificatesVerifier = VerifierBuilder.build(
From 03114b990040a3a2210e7b1396739ad8b2416a49 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:22:31 +0800
Subject: [PATCH 040/111] =?UTF-8?q?:art:=20#3753=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E6=B7=BB=E5=8A=A0=E5=95=86?=
=?UTF-8?q?=E5=AE=B6=E8=BD=AC=E8=B4=A6=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83?=
=?UTF-8?q?=E5=85=8D=E7=A1=AE=E8=AE=A4=E6=A8=A1=E5=BC=8F=E7=9B=B8=E5=85=B3?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ReservationTransferBatchGetResult.java | 170 +++++++++++++++++
.../ReservationTransferBatchRequest.java | 148 +++++++++++++++
.../ReservationTransferBatchResult.java | 51 ++++++
.../ReservationTransferNotifyResult.java | 173 ++++++++++++++++++
.../UserAuthorizationStatusResult.java | 63 +++++++
.../wxpay/constant/WxPayConstants.java | 65 +++++++
.../wxpay/service/TransferService.java | 119 ++++++++++++
.../service/impl/TransferServiceImpl.java | 81 ++++++++
8 files changed, 870 insertions(+)
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java
new file mode 100644
index 0000000000..d32db8c7c2
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java
@@ -0,0 +1,170 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ *
+ * 预约转账批次单号查询接口响应结果
+ * 通过预约批次单号查询批量预约商家转账批次单基本信息。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ *
+ *
+ * @author wanggang
+ * created on 2025/11/28
+ */
+@Data
+@NoArgsConstructor
+public class ReservationTransferBatchGetResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户号】 微信支付分配的商户号
+ */
+ @SerializedName("mch_id")
+ private String mchId;
+
+ /**
+ * 【商户预约批次单号】 商户系统内部的商家预约批次单号
+ */
+ @SerializedName("out_batch_no")
+ private String outBatchNo;
+
+ /**
+ * 【微信预约批次单号】 微信预约批次单号,微信商家转账系统返回的唯一标识
+ */
+ @SerializedName("reservation_batch_no")
+ private String reservationBatchNo;
+
+ /**
+ * 【商户AppID】 商户在微信申请公众号或移动应用成功后分配的账号ID
+ */
+ @SerializedName("appid")
+ private String appid;
+
+ /**
+ * 【批次备注】 批次备注
+ */
+ @SerializedName("batch_remark")
+ private String batchRemark;
+
+ /**
+ * 【转账场景ID】 商户在商户平台-产品中心-商家转账中申请的转账场景ID
+ */
+ @SerializedName("transfer_scene_id")
+ private String transferSceneId;
+
+ /**
+ * 【批次状态】
+ * ACCEPTED: 批次已受理
+ * PROCESSING: 批次处理中
+ * FINISHED: 批次处理完成
+ * CLOSED: 批次已关闭
+ */
+ @SerializedName("batch_state")
+ private String batchState;
+
+ /**
+ * 【转账总金额】 转账金额单位为"分"
+ */
+ @SerializedName("total_amount")
+ private Integer totalAmount;
+
+ /**
+ * 【转账总笔数】 转账总笔数
+ */
+ @SerializedName("total_num")
+ private Integer totalNum;
+
+ /**
+ * 【转账成功金额】 转账成功金额单位为"分"
+ */
+ @SerializedName("success_amount")
+ private Integer successAmount;
+
+ /**
+ * 【转账成功笔数】 转账成功笔数
+ */
+ @SerializedName("success_num")
+ private Integer successNum;
+
+ /**
+ * 【转账失败金额】 转账失败金额单位为"分"
+ */
+ @SerializedName("fail_amount")
+ private Integer failAmount;
+
+ /**
+ * 【转账失败笔数】 转账失败笔数
+ */
+ @SerializedName("fail_num")
+ private Integer failNum;
+
+ /**
+ * 【批次创建时间】 批次受理成功时返回
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ * 【批次更新时间】 批次最后更新时间
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("update_time")
+ private String updateTime;
+
+ /**
+ * 【批次关闭原因】 批次关闭原因
+ * MERCHANT_REVOCATION: 商户主动撤销
+ * OVERDUE_CLOSE: 系统超时关闭
+ */
+ @SerializedName("close_reason")
+ private String closeReason;
+
+ /**
+ * 【是否需要查询明细】
+ */
+ @SerializedName("need_query_detail")
+ private Boolean needQueryDetail;
+
+ /**
+ * 【转账明细列表】
+ */
+ @SerializedName("transfer_detail_list")
+ private List transferDetailList;
+
+ /**
+ * 转账明细
+ */
+ @Data
+ @NoArgsConstructor
+ public static class TransferDetail implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识
+ */
+ @SerializedName("out_detail_no")
+ private String outDetailNo;
+
+ /**
+ * 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识
+ */
+ @SerializedName("transfer_bill_no")
+ private String transferBillNo;
+
+ /**
+ * 【明细状态】
+ * PROCESSING: 转账处理中
+ * SUCCESS: 转账成功
+ * FAIL: 转账失败
+ */
+ @SerializedName("detail_state")
+ private String detailState;
+ }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java
new file mode 100644
index 0000000000..82e3d6f328
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java
@@ -0,0 +1,148 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.github.binarywang.wxpay.v3.SpecEncrypt;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ *
+ * 批量预约商家转账请求参数
+ * 商户可以通过批量预约接口一次发起批量转账请求,最多可以同时向50个用户发起转账。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ *
+ *
+ * @author wanggang
+ * created on 2025/11/28
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class ReservationTransferBatchRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户AppID】 商户在微信申请公众号或移动应用成功后分配的账号ID
+ */
+ @SerializedName("appid")
+ private String appid;
+
+ /**
+ * 【商户预约批次单号】 商户系统内部的商家预约批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
+ */
+ @SerializedName("out_batch_no")
+ private String outBatchNo;
+
+ /**
+ * 【转账场景ID】 商户在商户平台-产品中心-商家转账中申请的转账场景ID
+ */
+ @SerializedName("transfer_scene_id")
+ private String transferSceneId;
+
+ /**
+ * 【批次备注】 批次备注
+ */
+ @SerializedName("batch_remark")
+ private String batchRemark;
+
+ /**
+ * 【转账总金额】 转账金额单位为"分",转账总金额必须与批次内所有转账明细金额之和保持一致,否则无法发起转账操作
+ */
+ @SerializedName("total_amount")
+ private Integer totalAmount;
+
+ /**
+ * 【转账总笔数】 转账总笔数,需要与批次内所有转账明细笔数保持一致,否则无法发起转账操作
+ */
+ @SerializedName("total_num")
+ private Integer totalNum;
+
+ /**
+ * 【转账明细列表】 转账明细列表,最多50条
+ */
+ @SerializedName("transfer_detail_list")
+ private List transferDetailList;
+
+ /**
+ * 【异步回调地址】 异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数
+ */
+ @SerializedName("notify_url")
+ private String notifyUrl;
+
+ /**
+ * 转账明细
+ */
+ @Data
+ @Builder(builderMethodName = "newBuilder")
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class TransferDetail implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识,要求此参数只能由数字、大小写字母组成
+ */
+ @SerializedName("out_detail_no")
+ private String outDetailNo;
+
+ /**
+ * 【转账金额】 转账金额单位为"分"
+ */
+ @SerializedName("transfer_amount")
+ private Integer transferAmount;
+
+ /**
+ * 【转账备注】 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
+ */
+ @SerializedName("transfer_remark")
+ private String transferRemark;
+
+ /**
+ * 【收款用户OpenID】 商户AppID下,某用户的OpenID
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 【收款用户姓名】 收款方真实姓名。支持标准RSA算法和国密算法,公钥由微信侧提供
+ */
+ @SpecEncrypt
+ @SerializedName("user_name")
+ private String userName;
+
+ /**
+ * 【转账场景报备信息】
+ */
+ @SerializedName("transfer_scene_report_infos")
+ private List transferSceneReportInfos;
+ }
+
+ /**
+ * 转账场景报备信息
+ */
+ @Data
+ @Builder(builderMethodName = "newBuilder")
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class TransferSceneReportInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【信息类型】 信息类型编码
+ */
+ @SerializedName("info_type")
+ private String infoType;
+
+ /**
+ * 【信息内容】 信息内容
+ */
+ @SerializedName("info_content")
+ private String infoContent;
+ }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java
new file mode 100644
index 0000000000..ef762cee5b
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java
@@ -0,0 +1,51 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 批量预约商家转账响应结果
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ *
+ *
+ * @author wanggang
+ * created on 2025/11/28
+ */
+@Data
+@NoArgsConstructor
+public class ReservationTransferBatchResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户预约批次单号】 商户系统内部的商家预约批次单号
+ */
+ @SerializedName("out_batch_no")
+ private String outBatchNo;
+
+ /**
+ * 【微信预约批次单号】 微信预约批次单号,微信商家转账系统返回的唯一标识
+ */
+ @SerializedName("reservation_batch_no")
+ private String reservationBatchNo;
+
+ /**
+ * 【批次创建时间】 批次受理成功时返回
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ * 【批次状态】
+ * ACCEPTED: 批次已受理
+ * PROCESSING: 批次处理中
+ * FINISHED: 批次处理完成
+ * CLOSED: 批次已关闭
+ */
+ @SerializedName("batch_state")
+ private String batchState;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java
new file mode 100644
index 0000000000..438354e7bd
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java
@@ -0,0 +1,173 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
+import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ *
+ * 预约商家转账通知回调结果
+ * 预约批次单中的明细单在转账成功或转账失败时,微信会把相关结果信息发送给商户。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ *
+ *
+ * @author wanggang
+ * created on 2025/11/28
+ */
+@Data
+public class ReservationTransferNotifyResult implements Serializable, WxPayBaseNotifyV3Result {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 源数据
+ */
+ private OriginNotifyResponse rawData;
+
+ /**
+ * 解密后的数据
+ */
+ private ReservationTransferNotifyResult.DecryptNotifyResult result;
+
+ @Data
+ @NoArgsConstructor
+ public static class DecryptNotifyResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户号】 微信支付分配的商户号
+ */
+ @SerializedName("mch_id")
+ private String mchId;
+
+ /**
+ * 【商户预约批次单号】 商户系统内部的商家预约批次单号
+ */
+ @SerializedName("out_batch_no")
+ private String outBatchNo;
+
+ /**
+ * 【微信预约批次单号】 微信预约批次单号,微信商家转账系统返回的唯一标识
+ */
+ @SerializedName("reservation_batch_no")
+ private String reservationBatchNo;
+
+ /**
+ * 【批次状态】
+ * ACCEPTED: 批次已受理
+ * PROCESSING: 批次处理中
+ * FINISHED: 批次处理完成
+ * CLOSED: 批次已关闭
+ */
+ @SerializedName("batch_state")
+ private String batchState;
+
+ /**
+ * 【转账总金额】 转账金额单位为"分"
+ */
+ @SerializedName("total_amount")
+ private Integer totalAmount;
+
+ /**
+ * 【转账总笔数】 转账总笔数
+ */
+ @SerializedName("total_num")
+ private Integer totalNum;
+
+ /**
+ * 【转账成功金额】 转账成功金额单位为"分"
+ */
+ @SerializedName("success_amount")
+ private Integer successAmount;
+
+ /**
+ * 【转账成功笔数】 转账成功笔数
+ */
+ @SerializedName("success_num")
+ private Integer successNum;
+
+ /**
+ * 【转账失败金额】 转账失败金额单位为"分"
+ */
+ @SerializedName("fail_amount")
+ private Integer failAmount;
+
+ /**
+ * 【转账失败笔数】 转账失败笔数
+ */
+ @SerializedName("fail_num")
+ private Integer failNum;
+
+ /**
+ * 【批次创建时间】 批次受理成功时返回
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("create_time")
+ private String createTime;
+
+ /**
+ * 【批次更新时间】 批次最后更新时间
+ * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("update_time")
+ private String updateTime;
+
+ /**
+ * 【转账明细列表】
+ */
+ @SerializedName("transfer_detail_list")
+ private List transferDetailList;
+ }
+
+ /**
+ * 转账明细通知
+ */
+ @Data
+ @NoArgsConstructor
+ public static class TransferDetailNotify implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识
+ */
+ @SerializedName("out_detail_no")
+ private String outDetailNo;
+
+ /**
+ * 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识
+ */
+ @SerializedName("transfer_bill_no")
+ private String transferBillNo;
+
+ /**
+ * 【明细状态】
+ * PROCESSING: 转账处理中
+ * SUCCESS: 转账成功
+ * FAIL: 转账失败
+ */
+ @SerializedName("detail_state")
+ private String detailState;
+
+ /**
+ * 【收款用户OpenID】 商户AppID下,某用户的OpenID
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 【转账金额】 转账金额单位为"分"
+ */
+ @SerializedName("transfer_amount")
+ private Integer transferAmount;
+
+ /**
+ * 【失败原因】 转账失败原因
+ */
+ @SerializedName("fail_reason")
+ private String failReason;
+ }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java
new file mode 100644
index 0000000000..e0bab82150
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java
@@ -0,0 +1,63 @@
+package com.github.binarywang.wxpay.bean.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 商户查询用户授权信息接口响应结果
+ * 商户通过此接口可查询用户是否对商户的商家转账场景进行了授权。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ *
+ *
+ * @author wanggang
+ * created on 2025/11/28
+ */
+@Data
+@NoArgsConstructor
+public class UserAuthorizationStatusResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 【商户AppID】 商户在微信申请公众号或移动应用成功后分配的账号ID
+ */
+ @SerializedName("appid")
+ private String appid;
+
+ /**
+ * 【商户号】 微信支付分配的商户号
+ */
+ @SerializedName("mch_id")
+ private String mchId;
+
+ /**
+ * 【用户标识】 用户在直连商户应用下的用户标识
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 【授权状态】 用户授权状态
+ * UNAUTHORIZED: 未授权
+ * AUTHORIZED: 已授权
+ */
+ @SerializedName("authorization_state")
+ private String authorizationState;
+
+ /**
+ * 【授权时间】 用户授权时间,遵循rfc3339标准格式
+ * 格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("authorize_time")
+ private String authorizeTime;
+
+ /**
+ * 【取消授权时间】 用户取消授权时间,遵循rfc3339标准格式
+ * 格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
+ */
+ @SerializedName("deauthorize_time")
+ private String deauthorizeTime;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
index b1a57ccc0f..ec9e14ff2d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
@@ -401,6 +401,71 @@ public static class TransformBillState {
}
+
+ /**
+ * 用户授权状态
+ *
+ * @see 商户查询用户授权信息
+ */
+ @UtilityClass
+ public static class AuthorizationState {
+ /**
+ * 未授权
+ */
+ public static final String UNAUTHORIZED = "UNAUTHORIZED";
+
+ /**
+ * 已授权
+ */
+ public static final String AUTHORIZED = "AUTHORIZED";
+ }
+
+ /**
+ * 预约转账批次状态
+ *
+ * @see 批量预约商家转账
+ */
+ @UtilityClass
+ public static class ReservationBatchState {
+ /**
+ * 批次已受理
+ */
+ public static final String ACCEPTED = "ACCEPTED";
+
+ /**
+ * 批次处理中
+ */
+ public static final String PROCESSING = "PROCESSING";
+
+ /**
+ * 批次处理完成
+ */
+ public static final String FINISHED = "FINISHED";
+
+ /**
+ * 批次已关闭
+ */
+ public static final String CLOSED = "CLOSED";
+ }
+
+ /**
+ * 预约转账批次关闭原因
+ *
+ * @see 预约转账批次单号查询
+ */
+ @UtilityClass
+ public static class ReservationBatchCloseReason {
+ /**
+ * 商户主动撤销
+ */
+ public static final String MERCHANT_REVOCATION = "MERCHANT_REVOCATION";
+
+ /**
+ * 系统超时关闭
+ */
+ public static final String OVERDUE_CLOSE = "OVERDUE_CLOSE";
+ }
+
/**
* 【转账场景ID】 该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。
*/
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java
index 01113c9506..e48e327505 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java
@@ -189,4 +189,123 @@ public interface TransferService {
* @throws WxPayException the wx pay exception
*/
TransferBillsNotifyResult parseTransferBillsNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+
+ // ===================== 用户授权免确认模式相关接口 =====================
+
+ /**
+ *
+ * 商户查询用户授权信息接口
+ *
+ * 商户通过此接口可查询用户是否对商户的商家转账场景进行了授权。
+ *
+ * 请求方式:GET(HTTPS)
+ * 请求地址:请求地址
+ *
+ * 文档地址:商户查询用户授权信息
+ *
+ *
+ * @param openid 用户在直连商户应用下的用户标识
+ * @param transferSceneId 转账场景ID
+ * @return UserAuthorizationStatusResult 用户授权信息
+ * @throws WxPayException .
+ */
+ UserAuthorizationStatusResult getUserAuthorizationStatus(String openid, String transferSceneId) throws WxPayException;
+
+ /**
+ *
+ * 批量预约商家转账接口
+ *
+ * 商户可以通过批量预约接口一次发起批量转账请求,最多可以同时向50个用户发起转账。
+ * 批量预约接口适用于用户已授权免确认的场景,在转账时无需用户确认即可完成转账。
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址:请求地址
+ *
+ * 文档地址:批量预约商家转账
+ *
+ *
+ * @param request 批量预约商家转账请求参数
+ * @return ReservationTransferBatchResult 批量预约商家转账结果
+ * @throws WxPayException .
+ */
+ ReservationTransferBatchResult reservationTransferBatch(ReservationTransferBatchRequest request) throws WxPayException;
+
+ /**
+ *
+ * 商户预约批次单号查询批次单接口
+ *
+ * 通过商户预约批次单号查询批量预约商家转账批次单基本信息。
+ *
+ * 请求方式:GET(HTTPS)
+ * 请求地址:请求地址
+ *
+ * 文档地址:商户预约批次单号查询批次单
+ *
+ *
+ * @param outBatchNo 商户预约批次单号
+ * @param needQueryDetail 是否需要查询明细
+ * @param offset 分页偏移量
+ * @param limit 分页大小
+ * @param detailState 明细状态(PROCESSING/SUCCESS/FAIL)
+ * @return ReservationTransferBatchGetResult 批量预约商家转账批次查询结果
+ * @throws WxPayException .
+ */
+ ReservationTransferBatchGetResult getReservationTransferBatchByOutBatchNo(String outBatchNo, Boolean needQueryDetail,
+ Integer offset, Integer limit, String detailState) throws WxPayException;
+
+ /**
+ *
+ * 微信预约批次单号查询批次单接口
+ *
+ * 通过微信预约批次单号查询批量预约商家转账批次单基本信息。
+ *
+ * 请求方式:GET(HTTPS)
+ * 请求地址:请求地址
+ *
+ * 文档地址:微信预约批次单号查询批次单
+ *
+ *
+ * @param reservationBatchNo 微信预约批次单号
+ * @param needQueryDetail 是否需要查询明细
+ * @param offset 分页偏移量
+ * @param limit 分页大小
+ * @param detailState 明细状态(PROCESSING/SUCCESS/FAIL)
+ * @return ReservationTransferBatchGetResult 批量预约商家转账批次查询结果
+ * @throws WxPayException .
+ */
+ ReservationTransferBatchGetResult getReservationTransferBatchByReservationBatchNo(String reservationBatchNo, Boolean needQueryDetail,
+ Integer offset, Integer limit, String detailState) throws WxPayException;
+
+ /**
+ *
+ * 解析预约商家转账通知回调结果
+ *
+ * 预约批次单中的明细单在转账成功或转账失败时,微信会把相关结果信息发送给商户。
+ *
+ * 文档地址:预约商家转账通知
+ *
+ *
+ * @param notifyData 通知数据
+ * @param header 通知头部数据,不传则表示不校验头
+ * @return ReservationTransferNotifyResult 预约商家转账通知结果
+ * @throws WxPayException the wx pay exception
+ */
+ ReservationTransferNotifyResult parseReservationTransferNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+
+ /**
+ *
+ * 关闭预约商家转账批次接口
+ *
+ * 商户可以通过此接口关闭预约商家转账批次单。关闭后,该批次内所有未成功的转账将被取消。
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址:请求地址
+ *
+ * 文档地址:关闭预约商家转账批次
+ *
+ *
+ * @param outBatchNo 商户预约批次单号
+ * @throws WxPayException .
+ */
+ void closeReservationTransferBatch(String outBatchNo) throws WxPayException;
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java
index 038af32b87..fe05ab89ad 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java
@@ -124,4 +124,85 @@ public TransferBillsGetResult getBillsByTransferBillNo(String transferBillNo) th
public TransferBillsNotifyResult parseTransferBillsNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
return this.payService.baseParseOrderNotifyV3Result(notifyData, header, TransferBillsNotifyResult.class, TransferBillsNotifyResult.DecryptNotifyResult.class);
}
+
+ // ===================== 用户授权免确认模式相关接口实现 =====================
+
+ @Override
+ public UserAuthorizationStatusResult getUserAuthorizationStatus(String openid, String transferSceneId) throws WxPayException {
+ String url = String.format("%s/v3/fund-app/mch-transfer/authorization/openid/%s?transfer_scene_id=%s",
+ this.payService.getPayBaseUrl(), openid, transferSceneId);
+ String result = this.payService.getV3(url);
+ return GSON.fromJson(result, UserAuthorizationStatusResult.class);
+ }
+
+ @Override
+ public ReservationTransferBatchResult reservationTransferBatch(ReservationTransferBatchRequest request) throws WxPayException {
+ String url = String.format("%s/v3/fund-app/mch-transfer/reservation/transfer-batches", this.payService.getPayBaseUrl());
+ List transferDetailList = request.getTransferDetailList();
+ if (transferDetailList != null && !transferDetailList.isEmpty()) {
+ X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate();
+ for (ReservationTransferBatchRequest.TransferDetail detail : transferDetailList) {
+ if (detail.getUserName() != null && !detail.getUserName().isEmpty()) {
+ RsaCryptoUtil.encryptFields(detail, validCertificate);
+ }
+ }
+ }
+ String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(result, ReservationTransferBatchResult.class);
+ }
+
+ @Override
+ public ReservationTransferBatchGetResult getReservationTransferBatchByOutBatchNo(String outBatchNo, Boolean needQueryDetail,
+ Integer offset, Integer limit, String detailState) throws WxPayException {
+ String url = buildReservationBatchQueryUrl("out-batch-no", outBatchNo, needQueryDetail, offset, limit, detailState);
+ String result = this.payService.getV3(url);
+ return GSON.fromJson(result, ReservationTransferBatchGetResult.class);
+ }
+
+ @Override
+ public ReservationTransferBatchGetResult getReservationTransferBatchByReservationBatchNo(String reservationBatchNo, Boolean needQueryDetail,
+ Integer offset, Integer limit, String detailState) throws WxPayException {
+ String url = buildReservationBatchQueryUrl("reservation-batch-no", reservationBatchNo, needQueryDetail, offset, limit, detailState);
+ String result = this.payService.getV3(url);
+ return GSON.fromJson(result, ReservationTransferBatchGetResult.class);
+ }
+
+ private String buildReservationBatchQueryUrl(String batchNoType, String batchNo, Boolean needQueryDetail,
+ Integer offset, Integer limit, String detailState) {
+ StringBuilder url = new StringBuilder();
+ url.append(this.payService.getPayBaseUrl())
+ .append("/v3/fund-app/mch-transfer/reservation/transfer-batches/")
+ .append(batchNoType).append("/").append(batchNo);
+
+ boolean hasParams = false;
+ if (needQueryDetail != null) {
+ url.append("?need_query_detail=").append(needQueryDetail);
+ hasParams = true;
+ }
+ if (offset != null) {
+ url.append(hasParams ? "&" : "?").append("offset=").append(offset);
+ hasParams = true;
+ }
+ if (limit != null) {
+ url.append(hasParams ? "&" : "?").append("limit=").append(limit);
+ hasParams = true;
+ }
+ if (detailState != null && !detailState.isEmpty()) {
+ url.append(hasParams ? "&" : "?").append("detail_state=").append(detailState);
+ }
+ return url.toString();
+ }
+
+ @Override
+ public ReservationTransferNotifyResult parseReservationTransferNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+ return this.payService.baseParseOrderNotifyV3Result(notifyData, header, ReservationTransferNotifyResult.class,
+ ReservationTransferNotifyResult.DecryptNotifyResult.class);
+ }
+
+ @Override
+ public void closeReservationTransferBatch(String outBatchNo) throws WxPayException {
+ String url = String.format("%s/v3/fund-app/mch-transfer/reservation/transfer-batches/out-batch-no/%s/close",
+ this.payService.getPayBaseUrl(), outBatchNo);
+ this.payService.postV3(url, "");
+ }
}
From c4f383462e17d941147addf6f33e71b23fe7c0f1 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:26:46 +0800
Subject: [PATCH 041/111] =?UTF-8?q?:new:=20#3737=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=A2=9E=E5=8A=A0V3?=
=?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=95=86=E7=94=B3=E8=AF=B7=E9=80=80=E6=AC=BE?=
=?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/service/WxPayService.java | 26 +++++++++++++++++++
.../service/impl/BaseWxPayServiceImpl.java | 10 +++++++
2 files changed, 36 insertions(+)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index 82f05910ff..93da0d1332 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -839,6 +839,32 @@ public interface WxPayService {
*/
WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayException;
+ /**
+ *
+ * 微信支付-服务商申请退款.
+ * 应用场景
+ * 当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。
+ *
+ * 注意:
+ * 1、交易时间超过一年的订单无法提交退款
+ * 2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
+ * 3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
+ * 4、每个支付订单的部分退款次数不能超过50次
+ * 5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
+ * 6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
+ * 7、一个月之前的订单申请退款频率限制为:5000/min
+ *
+ * 详见 https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml
+ * 接口地址
+ * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
+ *
+ *
+ * @param request 请求对象
+ * @return 退款操作结果 wx pay refund result
+ * @throws WxPayException the wx pay exception
+ */
+ WxPayRefundV3Result partnerRefundV3(WxPayPartnerRefundV3Request request) throws WxPayException;
+
/**
*
* 微信支付-查询退款.
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 4c01836bbc..ba3dc37144 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -264,6 +264,16 @@ public WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayEx
return GSON.fromJson(response, WxPayRefundV3Result.class);
}
+ @Override
+ public WxPayRefundV3Result partnerRefundV3(WxPayPartnerRefundV3Request request) throws WxPayException {
+ if (StringUtils.isBlank(request.getSubMchid())) {
+ request.setSubMchid(this.getConfig().getSubMchId());
+ }
+ String url = String.format("%s/v3/refund/domestic/refunds", this.getPayBaseUrl());
+ String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(response, WxPayRefundV3Result.class);
+ }
+
@Override
public WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, String outRefundNo, String refundId)
throws WxPayException {
From 1f4ed687507ab52a34eb0843eef0da3b6f0dae61 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:29:22 +0800
Subject: [PATCH 042/111] =?UTF-8?q?:art:=20#3750=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=20V2=20?=
=?UTF-8?q?=E6=94=AF=E4=BB=98=E5=9B=9E=E8=B0=83=E7=AD=BE=E5=90=8D=E9=AA=8C?=
=?UTF-8?q?=E8=AF=81=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../bean/notify/WxPayOrderNotifyResult.java | 4 +-
.../WxPayOrderNotifyUnknownFieldTest.java | 104 ++++++++++++++++++
2 files changed, 107 insertions(+), 1 deletion(-)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
index 27e8c1e1ec..8f16e5d4e4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
@@ -342,7 +342,9 @@ public static WxPayOrderNotifyResult fromXML(String xmlString) {
@Override
public Map toMap() {
- Map resultMap = SignUtils.xmlBean2Map(this);
+ // 使用父类的 toMap() 方法,直接从原始 XML 解析所有字段,
+ // 确保包含未在 Java Bean 中定义的字段,避免签名验证失败
+ Map resultMap = super.toMap();
if (this.getCouponCount() != null && this.getCouponCount() > 0) {
for (int i = 0; i < this.getCouponCount(); i++) {
WxPayOrderNotifyCoupon coupon = couponList.get(i);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
new file mode 100644
index 0000000000..be35523ec4
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
@@ -0,0 +1,104 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.*;
+
+/**
+ * 测试当微信支付回调 XML 包含未在 Java Bean 中定义的字段时,签名验证是否正常。
+ *
+ * 问题背景:当微信返回的 XML 包含某些未在 WxPayOrderNotifyResult 中定义的字段时,
+ * 这些字段会被微信服务器用于签名计算。如果 toMap() 方法丢失了这些字段,
+ * 则签名验证会失败,抛出 "参数格式校验错误!" 异常。
+ *
+ *
+ * 解决方案:修改 WxPayOrderNotifyResult.toMap() 方法,使用父类的 toMap() 方法
+ * 直接从原始 XML 解析所有字段,而不是使用 SignUtils.xmlBean2Map(this)。
+ *
+ *
+ * @see Issue #3750
+ */
+public class WxPayOrderNotifyUnknownFieldTest {
+
+ private static final String MCH_KEY = "testmchkey1234567890123456789012";
+ private static final List NO_SIGN_PARAMS = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
+
+ @Test
+ public void testSignatureWithUnknownField() throws Exception {
+ // 创建一个测试用的 XML,包含一个未知字段 (未在 WxPayOrderNotifyResult 中定义)
+ Map params = new LinkedHashMap<>();
+ params.put("appid", "wx58ff40508696691f");
+ params.put("bank_type", "ICBC_DEBIT");
+ params.put("cash_fee", "1");
+ params.put("fee_type", "CNY");
+ params.put("is_subscribe", "N");
+ params.put("mch_id", "1545462911");
+ params.put("nonce_str", "1761723102373");
+ params.put("openid", "o1gdd16CZCi6yYvkn6j9EB_1TObM");
+ params.put("out_trade_no", "20251029153140");
+ params.put("result_code", "SUCCESS");
+ params.put("return_code", "SUCCESS");
+ params.put("time_end", "20251029153852");
+ params.put("total_fee", "1");
+ params.put("trade_type", "JSAPI");
+ params.put("transaction_id", "4200002882220251029816273963B");
+ // 添加一个未知字段
+ params.put("unknown_field", "unknown_value");
+
+ // 计算正确的签名 (包含未知字段)
+ String correctSign = createSign(params, WxPayConstants.SignType.MD5, MCH_KEY);
+ params.put("sign", correctSign);
+
+ // 创建 XML
+ StringBuilder xmlBuilder = new StringBuilder("");
+ for (Map.Entry entry : params.entrySet()) {
+ xmlBuilder.append("<").append(entry.getKey()).append(">")
+ .append(entry.getValue())
+ .append("").append(entry.getKey()).append(">");
+ }
+ xmlBuilder.append(" ");
+ String xml = xmlBuilder.toString();
+
+ System.out.println("测试 XML (包含未知字段 unknown_field):");
+ System.out.println(xml);
+ System.out.println("正确的签名 (包含未知字段计算): " + correctSign);
+
+ // 解析 XML
+ WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
+ Map beanMap = result.toMap();
+
+ System.out.println("\ntoMap() 结果:");
+ TreeMap sortedMap = new TreeMap<>(beanMap);
+ for (Map.Entry entry : sortedMap.entrySet()) {
+ System.out.println(" " + entry.getKey() + " = " + entry.getValue());
+ }
+
+ // 检查 unknown_field 是否存在
+ boolean hasUnknownField = beanMap.containsKey("unknown_field");
+ System.out.println("\ntoMap() 是否包含 unknown_field: " + hasUnknownField);
+
+ // 验证签名
+ String verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
+ System.out.println("原始签名: " + result.getSign());
+ System.out.println("计算签名: " + verifySign);
+
+ // 这个测试验证修复后 toMap() 能正确包含所有字段
+ Assert.assertTrue(hasUnknownField, "toMap() 应该包含 unknown_field");
+ Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配");
+ }
+
+ private static String createSign(Map params, String signType, String signKey) {
+ StringBuilder toSign = new StringBuilder();
+ for (String key : new TreeMap<>(params).keySet()) {
+ String value = params.get(key);
+ if (value != null && !value.isEmpty() && !NO_SIGN_PARAMS.contains(key)) {
+ toSign.append(key).append("=").append(value).append("&");
+ }
+ }
+ toSign.append("key=").append(signKey);
+ return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
+ }
+}
From 93a2fa701af7dd0af334d62ee0defb9f33f5cd27 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:46:10 +0800
Subject: [PATCH 043/111] =?UTF-8?q?:new:=20#3519=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E5=AE=9E=E7=8E=B0=E4=BA=A4=E6=98=93?=
=?UTF-8?q?=E6=8A=95=E8=AF=89=E7=9A=84=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/api/WxMaComplaintService.java | 159 +++++++++++++++++
.../wx/miniapp/api/WxMaService.java | 9 +
.../miniapp/api/impl/BaseWxMaServiceImpl.java | 6 +
.../api/impl/WxMaComplaintServiceImpl.java | 99 +++++++++++
.../complaint/WxMaComplaintDetailRequest.java | 38 ++++
.../complaint/WxMaComplaintDetailResult.java | 166 ++++++++++++++++++
.../WxMaComplaintNotifyUrlRequest.java | 38 ++++
.../WxMaComplaintNotifyUrlResult.java | 37 ++++
.../bean/complaint/WxMaComplaintRequest.java | 70 ++++++++
.../bean/complaint/WxMaComplaintResult.java | 156 ++++++++++++++++
.../bean/complaint/WxMaCompleteRequest.java | 38 ++++
.../WxMaNegotiationHistoryRequest.java | 60 +++++++
.../WxMaNegotiationHistoryResult.java | 88 ++++++++++
.../bean/complaint/WxMaResponseRequest.java | 79 +++++++++
.../miniapp/constant/WxMaApiUrlConstants.java | 30 ++++
15 files changed, 1073 insertions(+)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java
new file mode 100644
index 0000000000..bd12f60850
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java
@@ -0,0 +1,159 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.complaint.*;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 小程序交易投诉接口
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+public interface WxMaComplaintService {
+
+ /**
+ *
+ * 查询投诉单列表API
+ * 商户可通过调用此接口,查询指定时间段的所有用户投诉信息,以分页输出查询结果。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaComplaintRequest} 查询投诉单列表请求数据
+ * @return {@link WxMaComplaintResult} 微信返回的投诉单列表
+ * @throws WxErrorException the wx error exception
+ */
+ WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 查询投诉单详情API
+ * 商户可通过调用此接口,查询指定投诉单的用户投诉详情,包含投诉内容、投诉关联订单、投诉人联系方式等信息,方便商户处理投诉。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaComplaintDetailRequest} 投诉单详情请求数据
+ * @return {@link WxMaComplaintDetailResult} 微信返回的投诉单详情
+ * @throws WxErrorException the wx error exception
+ */
+ WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 查询投诉协商历史API
+ * 商户可通过调用此接口,查询指定投诉的用户商户协商历史,以分页输出查询结果,方便商户根据处理历史来制定后续处理方案。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaNegotiationHistoryRequest} 请求数据
+ * @return {@link WxMaNegotiationHistoryResult} 微信返回结果
+ * @throws WxErrorException the wx error exception
+ */
+ WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 创建投诉通知回调地址API
+ * 商户通过调用此接口创建投诉通知回调URL,当用户产生新投诉且投诉状态已变更时,微信会通过回调URL通知商户。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据
+ * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果
+ * @throws WxErrorException the wx error exception
+ */
+ WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 查询投诉通知回调地址API
+ * 商户通过调用此接口查询投诉通知的回调URL。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果
+ * @throws WxErrorException the wx error exception
+ */
+ WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException;
+
+ /**
+ *
+ * 更新投诉通知回调地址API
+ * 商户通过调用此接口更新投诉通知的回调URL。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据
+ * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果
+ * @throws WxErrorException the wx error exception
+ */
+ WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 删除投诉通知回调地址API
+ * 当商户不再需要推送通知时,可通过调用此接口删除投诉通知的回调URL,取消通知回调。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ void deleteComplaintNotifyUrl() throws WxErrorException;
+
+ /**
+ *
+ * 提交回复API
+ * 商户可通过调用此接口,提交回复内容。其中上传图片凭证需首先调用商户上传反馈图片接口,得到图片id,再将id填入请求。
+ * 回复可配置文字链,传入跳转链接文案和跳转链接字段,用户点击即可跳转对应页面
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaResponseRequest} 请求数据
+ * @throws WxErrorException the wx error exception
+ */
+ void submitResponse(WxMaResponseRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 反馈处理完成API
+ * 商户可通过调用此接口,反馈投诉单已处理完成。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param request {@link WxMaCompleteRequest} 请求数据
+ * @throws WxErrorException the wx error exception
+ */
+ void complete(WxMaCompleteRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 商户上传反馈图片API
+ * 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param imageFile 需要上传的图片文件
+ * @return String 微信返回的媒体文件标识Id
+ * @throws WxErrorException the wx error exception
+ * @throws IOException IO异常
+ */
+ String uploadResponseImage(File imageFile) throws WxErrorException, IOException;
+
+ /**
+ *
+ * 商户上传反馈图片API
+ * 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。
+ * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ *
+ * @param inputStream 需要上传的图片文件流
+ * @param fileName 需要上传的图片文件名
+ * @return String 微信返回的媒体文件标识Id
+ * @throws WxErrorException the wx error exception
+ * @throws IOException IO异常
+ */
+ String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException;
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index ef3a46bad9..1b556da81f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -605,4 +605,13 @@ WxMaApiResponse execute(
* @return 同城配送服务对象WxMaIntracityService
*/
WxMaIntracityService getIntracityService();
+
+ /**
+ * 获取交易投诉服务对象。
+ *
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ * @return 交易投诉服务对象WxMaComplaintService
+ */
+ WxMaComplaintService getComplaintService();
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
index ec33dede0c..28acb38472 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -165,6 +165,7 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH
new WxMaExpressDeliveryReturnServiceImpl(this);
private final WxMaPromotionService wxMaPromotionService = new WxMaPromotionServiceImpl(this);
private final WxMaIntracityService intracityService = new WxMaIntracityServiceImpl(this);
+ private final WxMaComplaintService complaintService = new WxMaComplaintServiceImpl(this);
private Map configMap = new HashMap<>();
private int retrySleepMillis = 1000;
@@ -1030,4 +1031,9 @@ private String aesDecodeResponse(WxMaApiResponse response, String aad, SecretKey
public WxMaIntracityService getIntracityService() {
return this.intracityService;
}
+
+ @Override
+ public WxMaComplaintService getComplaintService() {
+ return this.complaintService;
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java
new file mode 100644
index 0000000000..c534bd6938
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java
@@ -0,0 +1,99 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaComplaintService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.complaint.*;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.bean.CommonUploadParam;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Complaint.*;
+
+/**
+ * 小程序交易投诉接口实现
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@RequiredArgsConstructor
+public class WxMaComplaintServiceImpl implements WxMaComplaintService {
+ private static final String JSON_CONTENT_TYPE = "application/json";
+ private final WxMaService wxMaService;
+
+ @Override
+ public WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException {
+ String responseContent = this.wxMaService.post(QUERY_COMPLAINTS_URL, request.toJson());
+ return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintResult.class);
+ }
+
+ @Override
+ public WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException {
+ String responseContent = this.wxMaService.post(GET_COMPLAINT_URL, request.toJson());
+ return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintDetailResult.class);
+ }
+
+ @Override
+ public WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException {
+ String responseContent = this.wxMaService.post(QUERY_NEGOTIATION_HISTORY_URL, request.toJson());
+ return WxMaGsonBuilder.create().fromJson(responseContent, WxMaNegotiationHistoryResult.class);
+ }
+
+ @Override
+ public WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException {
+ String responseContent = this.wxMaService.post(ADD_COMPLAINT_NOTIFY_URL, request.toJson());
+ return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class);
+ }
+
+ @Override
+ public WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException {
+ String responseContent = this.wxMaService.get(GET_COMPLAINT_NOTIFY_URL, null);
+ return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class);
+ }
+
+ @Override
+ public WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException {
+ String responseContent = this.wxMaService.post(UPDATE_COMPLAINT_NOTIFY_URL, request.toJson());
+ return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class);
+ }
+
+ @Override
+ public void deleteComplaintNotifyUrl() throws WxErrorException {
+ this.wxMaService.post(DELETE_COMPLAINT_NOTIFY_URL, "{}");
+ }
+
+ @Override
+ public void submitResponse(WxMaResponseRequest request) throws WxErrorException {
+ this.wxMaService.post(SUBMIT_RESPONSE_URL, request.toJson());
+ }
+
+ @Override
+ public void complete(WxMaCompleteRequest request) throws WxErrorException {
+ this.wxMaService.post(COMPLETE_COMPLAINT_URL, request.toJson());
+ }
+
+ @Override
+ public String uploadResponseImage(File imageFile) throws WxErrorException, IOException {
+ String result = this.wxMaService.upload(UPLOAD_RESPONSE_IMAGE_URL,
+ CommonUploadParam.fromFile("image", imageFile));
+ JsonObject jsonResult = WxMaGsonBuilder.create().fromJson(result, JsonObject.class);
+ return jsonResult.get("media_id").getAsString();
+ }
+
+ @Override
+ public String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException {
+ try {
+ return this.uploadResponseImage(FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileName));
+ } catch (IOException e) {
+ throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java
new file mode 100644
index 0000000000..759f7392bf
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java
@@ -0,0 +1,38 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序投诉单详情请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaComplaintDetailRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java
new file mode 100644
index 0000000000..52a0be1704
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java
@@ -0,0 +1,166 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小程序投诉单详情结果
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxMaComplaintDetailResult extends WxMaBaseResponse {
+
+ /**
+ *
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ /**
+ *
+ * 字段名:投诉时间
+ * 是否必填:是
+ * 描述:用户提交投诉的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ *
+ */
+ @SerializedName("complaint_time")
+ private String complaintTime;
+
+ /**
+ *
+ * 字段名:投诉详情
+ * 是否必填:是
+ * 描述:用户提交的投诉详情
+ *
+ */
+ @SerializedName("complaint_detail")
+ private String complaintDetail;
+
+ /**
+ *
+ * 字段名:投诉状态
+ * 是否必填:是
+ * 描述:投诉单状态:WAITING_MERCHANT_RESPONSE-等待商户回复 MERCHANT_RESPONSED-商户已回复 WAITING_USER_RESPONSE-等待用户回复 USER_RESPONSED-用户已回复 COMPLAINT_COMPLETED-投诉已完成
+ *
+ */
+ @SerializedName("complaint_state")
+ private String complaintState;
+
+ /**
+ *
+ * 字段名:投诉人openid
+ * 是否必填:是
+ * 描述:投诉人在小程序的openid
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:投诉人联系方式
+ * 是否必填:否
+ * 描述:投诉人联系方式,可能为空
+ *
+ */
+ @SerializedName("phone_number")
+ private String phoneNumber;
+
+ /**
+ *
+ * 字段名:被投诉订单信息
+ * 是否必填:是
+ * 描述:被投诉的订单信息
+ *
+ */
+ @SerializedName("complaint_order_info")
+ private ComplaintOrderInfo complaintOrderInfo;
+
+ /**
+ *
+ * 字段名:投诉材料
+ * 是否必填:否
+ * 描述:用户上传的投诉相关的图片凭证
+ *
+ */
+ @SerializedName("complaint_media_list")
+ private List complaintMediaList;
+
+ /**
+ * 被投诉订单信息
+ */
+ @Data
+ public static class ComplaintOrderInfo implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 是否必填:是
+ * 描述:商户系统内部订单号
+ *
+ */
+ @SerializedName("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 字段名:微信订单号
+ * 是否必填:是
+ * 描述:微信支付系统生成的订单号
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:订单金额
+ * 是否必填:是
+ * 描述:订单金额,单位为分
+ *
+ */
+ @SerializedName("amount")
+ private Integer amount;
+ }
+
+ /**
+ * 投诉材料
+ */
+ @Data
+ public static class ComplaintMedia implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:媒体文件业务类型
+ * 是否必填:是
+ * 描述:媒体文件对应的业务类型:USER_COMPLAINT_IMAGE-用户投诉图片
+ *
+ */
+ @SerializedName("media_type")
+ private String mediaType;
+
+ /**
+ *
+ * 字段名:媒体文件请求URL
+ * 是否必填:是
+ * 描述:微信返回的媒体文件请求URL,通过该URL可以获取到对应的媒体文件(图片、视频等)
+ *
+ */
+ @SerializedName("media_url")
+ private String mediaUrl;
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java
new file mode 100644
index 0000000000..6af338c974
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java
@@ -0,0 +1,38 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序投诉通知回调URL请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaComplaintNotifyUrlRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:通知地址
+ * 是否必填:是
+ * 描述:通知地址,仅支持https
+ *
+ */
+ @SerializedName("url")
+ private String url;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java
new file mode 100644
index 0000000000..7771998b80
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java
@@ -0,0 +1,37 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 小程序投诉通知回调URL结果
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxMaComplaintNotifyUrlResult extends WxMaBaseResponse {
+
+ /**
+ *
+ * 字段名:通知地址
+ * 是否必填:是
+ * 描述:通知地址,仅支持https
+ *
+ */
+ @SerializedName("url")
+ private String url;
+
+ /**
+ *
+ * 字段名:签名串
+ * 是否必填:是
+ * 描述:用于验证通知消息的签名串
+ *
+ */
+ @SerializedName("signature")
+ private String signature;
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java
new file mode 100644
index 0000000000..9ac45e0c12
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java
@@ -0,0 +1,70 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序交易投诉查询请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaComplaintRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:开始日期
+ * 是否必填:是
+ * 描述:查询的起始时间,格式为YYYY-MM-DD,例如2021-01-01
+ *
+ */
+ @SerializedName("begin_date")
+ private String beginDate;
+
+ /**
+ *
+ * 字段名:结束日期
+ * 是否必填:是
+ * 描述:查询的结束时间,格式为YYYY-MM-DD,例如2021-01-31
+ *
+ */
+ @SerializedName("end_date")
+ private String endDate;
+
+ /**
+ *
+ * 字段名:分页大小
+ * 是否必填:否
+ * 描述:单次拉取条目,最大为50,不传默认为10
+ *
+ */
+ @SerializedName("limit")
+ @Builder.Default
+ private Integer limit = 10;
+
+ /**
+ *
+ * 字段名:分页开始位置
+ * 是否必填:否
+ * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0
+ *
+ */
+ @SerializedName("offset")
+ @Builder.Default
+ private Integer offset = 0;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java
new file mode 100644
index 0000000000..0e4208fdc1
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java
@@ -0,0 +1,156 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小程序交易投诉查询结果
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxMaComplaintResult extends WxMaBaseResponse {
+
+ /**
+ *
+ * 字段名:投诉单信息
+ * 是否必填:是
+ * 描述:查询返回的投诉单信息
+ *
+ */
+ @SerializedName("data")
+ private List data;
+
+ /**
+ *
+ * 字段名:总数
+ * 是否必填:是
+ * 描述:总投诉单条数,用于分页展示
+ *
+ */
+ @SerializedName("total_count")
+ private Integer totalCount;
+
+ /**
+ * 投诉单信息
+ */
+ @Data
+ public static class Complaint implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ /**
+ *
+ * 字段名:投诉时间
+ * 是否必填:是
+ * 描述:用户提交投诉的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ *
+ */
+ @SerializedName("complaint_time")
+ private String complaintTime;
+
+ /**
+ *
+ * 字段名:投诉详情
+ * 是否必填:是
+ * 描述:用户提交的投诉详情
+ *
+ */
+ @SerializedName("complaint_detail")
+ private String complaintDetail;
+
+ /**
+ *
+ * 字段名:投诉状态
+ * 是否必填:是
+ * 描述:投诉单状态:WAITING_MERCHANT_RESPONSE-等待商户回复 MERCHANT_RESPONSED-商户已回复 WAITING_USER_RESPONSE-等待用户回复 USER_RESPONSED-用户已回复 COMPLAINT_COMPLETED-投诉已完成
+ *
+ */
+ @SerializedName("complaint_state")
+ private String complaintState;
+
+ /**
+ *
+ * 字段名:投诉人openid
+ * 是否必填:是
+ * 描述:投诉人在小程序的openid
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:投诉人联系方式
+ * 是否必填:否
+ * 描述:投诉人联系方式,可能为空
+ *
+ */
+ @SerializedName("phone_number")
+ private String phoneNumber;
+
+ /**
+ *
+ * 字段名:被投诉订单信息
+ * 是否必填:是
+ * 描述:被投诉的订单信息
+ *
+ */
+ @SerializedName("complaint_order_info")
+ private ComplaintOrderInfo complaintOrderInfo;
+ }
+
+ /**
+ * 被投诉订单信息
+ */
+ @Data
+ public static class ComplaintOrderInfo implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:商户订单号
+ * 是否必填:是
+ * 描述:商户系统内部订单号
+ *
+ */
+ @SerializedName("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 字段名:微信订单号
+ * 是否必填:是
+ * 描述:微信支付系统生成的订单号
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:订单金额
+ * 是否必填:是
+ * 描述:订单金额,单位为分
+ *
+ */
+ @SerializedName("amount")
+ private Integer amount;
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java
new file mode 100644
index 0000000000..71d1066024
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java
@@ -0,0 +1,38 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序反馈处理完成请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaCompleteRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java
new file mode 100644
index 0000000000..a03742b6af
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java
@@ -0,0 +1,60 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序查询投诉协商历史请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaNegotiationHistoryRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ /**
+ *
+ * 字段名:分页大小
+ * 是否必填:否
+ * 描述:单次拉取条目,最大为50,不传默认为10
+ *
+ */
+ @SerializedName("limit")
+ @Builder.Default
+ private Integer limit = 10;
+
+ /**
+ *
+ * 字段名:分页开始位置
+ * 是否必填:否
+ * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0
+ *
+ */
+ @SerializedName("offset")
+ @Builder.Default
+ private Integer offset = 0;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java
new file mode 100644
index 0000000000..194daca9ff
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java
@@ -0,0 +1,88 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小程序查询投诉协商历史结果
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxMaNegotiationHistoryResult extends WxMaBaseResponse {
+
+ /**
+ *
+ * 字段名:协商历史
+ * 是否必填:是
+ * 描述:协商历史记录
+ *
+ */
+ @SerializedName("data")
+ private List data;
+
+ /**
+ *
+ * 字段名:总数
+ * 是否必填:是
+ * 描述:总协商历史条数,用于分页展示
+ *
+ */
+ @SerializedName("total_count")
+ private Integer totalCount;
+
+ /**
+ * 协商历史
+ */
+ @Data
+ public static class NegotiationHistory implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:操作时间
+ * 是否必填:是
+ * 描述:操作时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+ *
+ */
+ @SerializedName("operate_time")
+ private String operateTime;
+
+ /**
+ *
+ * 字段名:操作类型
+ * 是否必填:是
+ * 描述:操作类型:USER_CREATE_COMPLAINT-用户提交投诉 USER_CONTINUE_COMPLAINT-用户继续投诉 MERCHANT_RESPONSE-商户回复 MERCHANT_CONFIRM_COMPLETE-商户确认完成处理
+ *
+ */
+ @SerializedName("operate_type")
+ private String operateType;
+
+ /**
+ *
+ * 字段名:操作内容
+ * 是否必填:是
+ * 描述:具体的操作内容
+ *
+ */
+ @SerializedName("operate_details")
+ private String operateDetails;
+
+ /**
+ *
+ * 字段名:图片凭证
+ * 是否必填:否
+ * 描述:操作过程中上传的图片凭证
+ *
+ */
+ @SerializedName("image_list")
+ private List imageList;
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java
new file mode 100644
index 0000000000..b4033c4f31
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java
@@ -0,0 +1,79 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小程序提交回复请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaResponseRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ *
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ /**
+ *
+ * 字段名:回复内容
+ * 是否必填:是
+ * 描述:具体的回复内容,长度不超过200字符
+ *
+ */
+ @SerializedName("response_content")
+ private String responseContent;
+
+ /**
+ *
+ * 字段名:回复图片
+ * 是否必填:否
+ * 描述:回复的图片凭证,最多可传5张图片,由图片上传接口返回
+ *
+ */
+ @SerializedName("response_images")
+ private List responseImages;
+
+ /**
+ *
+ * 字段名:跳转链接
+ * 是否必填:否
+ * 描述:点击跳转链接
+ *
+ */
+ @SerializedName("jump_url")
+ private String jumpUrl;
+
+ /**
+ *
+ * 字段名:跳转链接文案
+ * 是否必填:否
+ * 描述:跳转链接文案,在response_content中展示的跳转链接文案,长度不超过10个字符
+ *
+ */
+ @SerializedName("jump_url_text")
+ private String jumpUrlText;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
index 79806dbd84..ea9b123fb9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
@@ -964,4 +964,34 @@ public interface Intracity {
String GET_CITY = "https://api.weixin.qq.com/cgi-bin/express/intracity/getcity";
}
+
+ /**
+ * 小程序交易投诉接口
+ *
+ *
+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+ *
+ */
+ public interface Complaint {
+ /** 查询投诉单列表 */
+ String QUERY_COMPLAINTS_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/list";
+ /** 查询投诉单详情 */
+ String GET_COMPLAINT_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/detail";
+ /** 查询投诉协商历史 */
+ String QUERY_NEGOTIATION_HISTORY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/negotiation/history";
+ /** 创建投诉通知回调地址 */
+ String ADD_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/add";
+ /** 查询投诉通知回调地址 */
+ String GET_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/get";
+ /** 更新投诉通知回调地址 */
+ String UPDATE_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/update";
+ /** 删除投诉通知回调地址 */
+ String DELETE_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/delete";
+ /** 提交回复 */
+ String SUBMIT_RESPONSE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/response";
+ /** 反馈处理完成 */
+ String COMPLETE_COMPLAINT_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/complete";
+ /** 上传反馈图片 */
+ String UPLOAD_RESPONSE_IMAGE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/upload";
+ }
}
From 8f20f3babac5ac22c581206abde83d17999ecccb Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:49:15 +0800
Subject: [PATCH 044/111] =?UTF-8?q?:new:=20#3678=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E6=96=B0=E5=A2=9E=E5=AE=A2=E6=9C=8D?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
MINIAPP_KEFU_SERVICE.md | 80 +++++++++++
.../wx/miniapp/api/WxMaKefuService.java | 128 ++++++++++++++++++
.../wx/miniapp/api/WxMaService.java | 7 +
.../miniapp/api/impl/BaseWxMaServiceImpl.java | 6 +
.../miniapp/api/impl/WxMaKefuServiceImpl.java | 92 +++++++++++++
.../wx/miniapp/bean/kefu/WxMaKfInfo.java | 79 +++++++++++
.../wx/miniapp/bean/kefu/WxMaKfList.java | 35 +++++
.../wx/miniapp/bean/kefu/WxMaKfSession.java | 43 ++++++
.../miniapp/bean/kefu/WxMaKfSessionList.java | 61 +++++++++
.../kefu/request/WxMaKfAccountRequest.java | 49 +++++++
.../kefu/request/WxMaKfSessionRequest.java | 43 ++++++
.../api/impl/WxMaKefuServiceImplTest.java | 60 ++++++++
.../wx/miniapp/demo/WxMaKefuServiceDemo.java | 84 ++++++++++++
13 files changed, 767 insertions(+)
create mode 100644 MINIAPP_KEFU_SERVICE.md
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java
create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java
create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java
diff --git a/MINIAPP_KEFU_SERVICE.md b/MINIAPP_KEFU_SERVICE.md
new file mode 100644
index 0000000000..96cf4c3831
--- /dev/null
+++ b/MINIAPP_KEFU_SERVICE.md
@@ -0,0 +1,80 @@
+# WeChat Mini Program Customer Service Management
+
+This document describes the new customer service management functionality added to the WxJava Mini Program SDK.
+
+## Overview
+
+Previously, the mini program module only had:
+- `WxMaCustomserviceWorkService` - For binding mini programs to enterprise WeChat customer service
+- `WxMaMsgService.sendKefuMsg()` - For sending customer service messages
+
+The new `WxMaKefuService` adds comprehensive customer service management capabilities:
+
+## Features
+
+### Customer Service Account Management
+- `kfList()` - Get list of customer service accounts
+- `kfAccountAdd()` - Add new customer service account
+- `kfAccountUpdate()` - Update customer service account
+- `kfAccountDel()` - Delete customer service account
+
+### Session Management
+- `kfSessionCreate()` - Create customer service session
+- `kfSessionClose()` - Close customer service session
+- `kfSessionGet()` - Get customer session status
+- `kfSessionList()` - Get customer service session list
+
+## Usage Example
+
+```java
+// Get the customer service management service
+WxMaKefuService kefuService = wxMaService.getKefuService();
+
+// Add a new customer service account
+WxMaKfAccountRequest request = WxMaKfAccountRequest.builder()
+ .kfAccount("service001@example")
+ .kfNick("Customer Service 001")
+ .kfPwd("password123")
+ .build();
+boolean result = kefuService.kfAccountAdd(request);
+
+// Create a session between user and customer service
+boolean sessionResult = kefuService.kfSessionCreate("user_openid", "service001@example");
+
+// Get customer service list
+WxMaKfList kfList = kefuService.kfList();
+```
+
+## Bean Classes
+
+### Request Objects
+- `WxMaKfAccountRequest` - For customer service account operations
+- `WxMaKfSessionRequest` - For session operations
+
+### Response Objects
+- `WxMaKfInfo` - Customer service account information
+- `WxMaKfList` - List of customer service accounts
+- `WxMaKfSession` - Session information
+- `WxMaKfSessionList` - List of sessions
+
+## API Endpoints
+
+The service uses the following WeChat Mini Program API endpoints:
+- `https://api.weixin.qq.com/cgi-bin/customservice/getkflist` - Get customer service list
+- `https://api.weixin.qq.com/customservice/kfaccount/add` - Add customer service account
+- `https://api.weixin.qq.com/customservice/kfaccount/update` - Update customer service account
+- `https://api.weixin.qq.com/customservice/kfaccount/del` - Delete customer service account
+- `https://api.weixin.qq.com/customservice/kfsession/create` - Create session
+- `https://api.weixin.qq.com/customservice/kfsession/close` - Close session
+- `https://api.weixin.qq.com/customservice/kfsession/getsession` - Get session
+- `https://api.weixin.qq.com/customservice/kfsession/getsessionlist` - Get session list
+
+## Integration
+
+The service is automatically available through the main `WxMaService` interface:
+
+```java
+WxMaKefuService kefuService = wxMaService.getKefuService();
+```
+
+This fills the gap mentioned in the original issue and provides full customer service management capabilities for WeChat Mini Programs.
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java
new file mode 100644
index 0000000000..b8b5a03809
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java
@@ -0,0 +1,128 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfInfo;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList;
+import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ *
+ * 小程序客服管理接口.
+ * 不同于 WxMaCustomserviceWorkService (企业微信客服绑定) 和 WxMaMsgService.sendKefuMsg (发送客服消息),
+ * 此接口专门处理小程序客服账号管理、会话管理等功能。
+ *
+ * 注意:小程序客服管理接口与公众号客服管理接口在API端点和功能上有所不同。
+ *
+ *
+ * @author Binary Wang
+ */
+public interface WxMaKefuService {
+
+ /**
+ *
+ * 获取客服基本信息
+ * 详情请见:获取客服基本信息
+ * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN
+ *
+ *
+ * @return 客服列表
+ * @throws WxErrorException 异常
+ */
+ WxMaKfList kfList() throws WxErrorException;
+
+ /**
+ *
+ * 添加客服账号
+ * 详情请见:添加客服账号
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param request 客服账号信息
+ * @return 是否成功
+ * @throws WxErrorException 异常
+ */
+ boolean kfAccountAdd(WxMaKfAccountRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 修改客服账号
+ * 详情请见:修改客服账号
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/update?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param request 客服账号信息
+ * @return 是否成功
+ * @throws WxErrorException 异常
+ */
+ boolean kfAccountUpdate(WxMaKfAccountRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 删除客服账号
+ * 详情请见:删除客服账号
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
+ *
+ *
+ * @param kfAccount 客服账号
+ * @return 是否成功
+ * @throws WxErrorException 异常
+ */
+ boolean kfAccountDel(String kfAccount) throws WxErrorException;
+
+ /**
+ *
+ * 创建会话
+ * 详情请见:创建会话
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param openid 用户openid
+ * @param kfAccount 客服账号
+ * @return 是否成功
+ * @throws WxErrorException 异常
+ */
+ boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException;
+
+ /**
+ *
+ * 关闭会话
+ * 详情请见:关闭会话
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param openid 用户openid
+ * @param kfAccount 客服账号
+ * @return 是否成功
+ * @throws WxErrorException 异常
+ */
+ boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException;
+
+ /**
+ *
+ * 获取客户的会话状态
+ * 详情请见:获取客户的会话状态
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID
+ *
+ *
+ * @param openid 用户openid
+ * @return 会话信息
+ * @throws WxErrorException 异常
+ */
+ WxMaKfSession kfSessionGet(String openid) throws WxErrorException;
+
+ /**
+ *
+ * 获取客服的会话列表
+ * 详情请见:获取客服的会话列表
+ * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
+ *
+ *
+ * @param kfAccount 客服账号
+ * @return 会话列表
+ * @throws WxErrorException 异常
+ */
+ WxMaKfSessionList kfSessionList(String kfAccount) throws WxErrorException;
+
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index 1b556da81f..26ced8bedd 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -285,6 +285,13 @@ WxMaApiResponse execute(
*/
WxMaCustomserviceWorkService getCustomserviceWorkService();
+ /**
+ * 获取小程序客服管理服务。
+ *
+ * @return 客服管理服务对象WxMaKefuService
+ */
+ WxMaKefuService getKefuService();
+
/**
* 获取jsapi操作相关服务对象。
*
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
index 28acb38472..0769eaae67 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -113,6 +113,7 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH
private final WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this);
private final WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
private final WxMaCustomserviceWorkService customserviceWorkService = new WxMaCustomserviceWorkServiceImpl(this);
+ private final WxMaKefuService maKefuService = new WxMaKefuServiceImpl(this);
private final WxMaInternetService internetService = new WxMaInternetServiceImpl(this);
private final WxMaSettingService settingService = new WxMaSettingServiceImpl(this);
private final WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this);
@@ -658,6 +659,11 @@ public WxMaCustomserviceWorkService getCustomserviceWorkService() {
return this.customserviceWorkService;
}
+ @Override
+ public WxMaKefuService getKefuService() {
+ return this.maKefuService;
+ }
+
@Override
public WxMaJsapiService getJsapiService() {
return this.jsapiService;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java
new file mode 100644
index 0000000000..2fa39603a0
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java
@@ -0,0 +1,92 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaKefuService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfInfo;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList;
+import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest;
+import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfSessionRequest;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 小程序客服管理服务实现.
+ *
+ * @author Binary Wang
+ */
+@RequiredArgsConstructor
+public class WxMaKefuServiceImpl implements WxMaKefuService {
+
+ // 小程序客服管理接口URL
+ private static final String KFLIST_GET_URL = "https://api.weixin.qq.com/cgi-bin/customservice/getkflist";
+ private static final String KFACCOUNT_ADD_URL = "https://api.weixin.qq.com/customservice/kfaccount/add";
+ private static final String KFACCOUNT_UPDATE_URL = "https://api.weixin.qq.com/customservice/kfaccount/update";
+ private static final String KFACCOUNT_DEL_URL = "https://api.weixin.qq.com/customservice/kfaccount/del?kf_account=%s";
+ private static final String KFSESSION_CREATE_URL = "https://api.weixin.qq.com/customservice/kfsession/create";
+ private static final String KFSESSION_CLOSE_URL = "https://api.weixin.qq.com/customservice/kfsession/close";
+ private static final String KFSESSION_GET_URL = "https://api.weixin.qq.com/customservice/kfsession/getsession?openid=%s";
+ private static final String KFSESSION_LIST_URL = "https://api.weixin.qq.com/customservice/kfsession/getsessionlist?kf_account=%s";
+
+ private final WxMaService service;
+
+ @Override
+ public WxMaKfList kfList() throws WxErrorException {
+ String responseContent = this.service.get(KFLIST_GET_URL, null);
+ return WxMaKfList.fromJson(responseContent);
+ }
+
+ @Override
+ public boolean kfAccountAdd(WxMaKfAccountRequest request) throws WxErrorException {
+ String responseContent = this.service.post(KFACCOUNT_ADD_URL, request.toJson());
+ return responseContent != null;
+ }
+
+ @Override
+ public boolean kfAccountUpdate(WxMaKfAccountRequest request) throws WxErrorException {
+ String responseContent = this.service.post(KFACCOUNT_UPDATE_URL, request.toJson());
+ return responseContent != null;
+ }
+
+ @Override
+ public boolean kfAccountDel(String kfAccount) throws WxErrorException {
+ String url = String.format(KFACCOUNT_DEL_URL, kfAccount);
+ String responseContent = this.service.get(url, null);
+ return responseContent != null;
+ }
+
+ @Override
+ public boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException {
+ WxMaKfSessionRequest request = WxMaKfSessionRequest.builder()
+ .kfAccount(kfAccount)
+ .openid(openid)
+ .build();
+ String responseContent = this.service.post(KFSESSION_CREATE_URL, request.toJson());
+ return responseContent != null;
+ }
+
+ @Override
+ public boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException {
+ WxMaKfSessionRequest request = WxMaKfSessionRequest.builder()
+ .kfAccount(kfAccount)
+ .openid(openid)
+ .build();
+ String responseContent = this.service.post(KFSESSION_CLOSE_URL, request.toJson());
+ return responseContent != null;
+ }
+
+ @Override
+ public WxMaKfSession kfSessionGet(String openid) throws WxErrorException {
+ String url = String.format(KFSESSION_GET_URL, openid);
+ String responseContent = this.service.get(url, null);
+ return WxMaKfSession.fromJson(responseContent);
+ }
+
+ @Override
+ public WxMaKfSessionList kfSessionList(String kfAccount) throws WxErrorException {
+ String url = String.format(KFSESSION_LIST_URL, kfAccount);
+ String responseContent = this.service.get(url, null);
+ return WxMaKfSessionList.fromJson(responseContent);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java
new file mode 100644
index 0000000000..476056d518
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java
@@ -0,0 +1,79 @@
+package cn.binarywang.wx.miniapp.bean.kefu;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序客服信息.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaKfInfo implements Serializable {
+ private static final long serialVersionUID = -7916302137791763175L;
+
+ /**
+ * 客服账号.
+ */
+ @SerializedName("kf_account")
+ private String kfAccount;
+
+ /**
+ * 客服昵称.
+ */
+ @SerializedName("kf_nick")
+ private String kfNick;
+
+ /**
+ * 客服密码.
+ */
+ @SerializedName("kf_id")
+ private String kfId;
+
+ /**
+ * 客服头像.
+ */
+ @SerializedName("kf_headimgurl")
+ private String kfHeadImgUrl;
+
+ /**
+ * 如果客服帐号已绑定了客服人员微信号,则此处显示微信号.
+ */
+ @SerializedName("kf_wx")
+ private String kfWx;
+
+ /**
+ * 如果客服帐号尚未绑定微信号,但是已经发起了一个绑定邀请,则此处显示绑定邀请的微信号.
+ */
+ @SerializedName("invite_wx")
+ private String inviteWx;
+
+ /**
+ * 邀请的状态,有等待确认"waiting",被拒绝"rejected",过期"expired".
+ */
+ @SerializedName("invite_expire_time")
+ private Long inviteExpireTime;
+
+ /**
+ * 邀请的过期时间,为unix时间戳.
+ */
+ @SerializedName("invite_status")
+ private String inviteStatus;
+
+ public static WxMaKfInfo fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaKfInfo.class);
+ }
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java
new file mode 100644
index 0000000000..8dd87a1728
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java
@@ -0,0 +1,35 @@
+package cn.binarywang.wx.miniapp.bean.kefu;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小程序客服列表.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaKfList implements Serializable {
+ private static final long serialVersionUID = 6416633293297389972L;
+
+ @SerializedName("kf_list")
+ private List kfList;
+
+ public static WxMaKfList fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaKfList.class);
+ }
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java
new file mode 100644
index 0000000000..60c010499c
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java
@@ -0,0 +1,43 @@
+package cn.binarywang.wx.miniapp.bean.kefu;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序客服会话.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaKfSession implements Serializable {
+ private static final long serialVersionUID = -6987567952389036965L;
+
+ /**
+ * 正在接待的客服,为空表示没有人在接待.
+ */
+ @SerializedName("kf_account")
+ private String kfAccount;
+
+ /**
+ * 会话接入的时间.
+ */
+ @SerializedName("createtime")
+ private Long createTime;
+
+ public static WxMaKfSession fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaKfSession.class);
+ }
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java
new file mode 100644
index 0000000000..e172e2e4e5
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java
@@ -0,0 +1,61 @@
+package cn.binarywang.wx.miniapp.bean.kefu;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小程序客服会话列表.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaKfSessionList implements Serializable {
+ private static final long serialVersionUID = -1538600729426188776L;
+
+ @SerializedName("sessionlist")
+ private List sessionList;
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class SessionInfo implements Serializable {
+ private static final long serialVersionUID = -2077261313274513580L;
+
+ /**
+ * 粉丝的openid.
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 会话创建时间,UNIX时间戳.
+ */
+ @SerializedName("createtime")
+ private Long createTime;
+
+ /**
+ * 粉丝的最后一条消息的时间,UNIX时间戳.
+ */
+ @SerializedName("latest_time")
+ private Long latestTime;
+ }
+
+ public static WxMaKfSessionList fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaKfSessionList.class);
+ }
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java
new file mode 100644
index 0000000000..0ad8913639
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java
@@ -0,0 +1,49 @@
+package cn.binarywang.wx.miniapp.bean.kefu.request;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序客服账号操作请求.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaKfAccountRequest implements Serializable {
+ private static final long serialVersionUID = -4953504451749066635L;
+
+ /**
+ * 客服账号.
+ */
+ @SerializedName("kf_account")
+ private String kfAccount;
+
+ /**
+ * 客服昵称.
+ */
+ @SerializedName("kf_nick")
+ private String kfNick;
+
+ /**
+ * 客服密码.
+ */
+ @SerializedName("kf_pwd")
+ private String kfPwd;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+
+ public static WxMaKfAccountRequest fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaKfAccountRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java
new file mode 100644
index 0000000000..f8180f926a
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java
@@ -0,0 +1,43 @@
+package cn.binarywang.wx.miniapp.bean.kefu.request;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序客服会话操作请求.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaKfSessionRequest implements Serializable {
+ private static final long serialVersionUID = -3278295399203344398L;
+
+ /**
+ * 客服账号.
+ */
+ @SerializedName("kf_account")
+ private String kfAccount;
+
+ /**
+ * 用户openid.
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+
+ public static WxMaKfSessionRequest fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaKfSessionRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java
new file mode 100644
index 0000000000..456aca93f2
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java
@@ -0,0 +1,60 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaKefuService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList;
+import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * 小程序客服管理服务测试.
+ *
+ * @author Binary Wang
+ */
+public class WxMaKefuServiceImplTest {
+
+ @Test
+ public void testKfList() throws WxErrorException {
+ WxMaService service = mock(WxMaService.class);
+ when(service.get(anyString(), any())).thenReturn("{\"kf_list\":[]}");
+
+ WxMaKefuService kefuService = new WxMaKefuServiceImpl(service);
+ WxMaKfList result = kefuService.kfList();
+
+ Assert.assertNotNull(result);
+ Assert.assertNotNull(result.getKfList());
+ Assert.assertEquals(result.getKfList().size(), 0);
+ }
+
+ @Test
+ public void testKfAccountAdd() throws WxErrorException {
+ WxMaService service = mock(WxMaService.class);
+ when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0}");
+
+ WxMaKefuService kefuService = new WxMaKefuServiceImpl(service);
+ WxMaKfAccountRequest request = WxMaKfAccountRequest.builder()
+ .kfAccount("test@kfaccount")
+ .kfNick("测试客服")
+ .kfPwd("password")
+ .build();
+
+ boolean result = kefuService.kfAccountAdd(request);
+ Assert.assertTrue(result);
+ }
+
+ @Test
+ public void testKfSessionCreate() throws WxErrorException {
+ WxMaService service = mock(WxMaService.class);
+ when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0}");
+
+ WxMaKefuService kefuService = new WxMaKefuServiceImpl(service);
+ boolean result = kefuService.kfSessionCreate("test_openid", "test@kfaccount");
+ Assert.assertTrue(result);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java
new file mode 100644
index 0000000000..1d62fd9be9
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java
@@ -0,0 +1,84 @@
+package cn.binarywang.wx.miniapp.demo;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession;
+import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList;
+import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 小程序客服管理功能使用示例.
+ *
+ * @author Binary Wang
+ */
+public class WxMaKefuServiceDemo {
+
+ private final WxMaService wxMaService;
+
+ public WxMaKefuServiceDemo(WxMaService wxMaService) {
+ this.wxMaService = wxMaService;
+ }
+
+ /**
+ * 演示客服账号管理功能
+ */
+ public void demonstrateCustomerServiceManagement() throws WxErrorException {
+ // 1. 获取客服列表
+ WxMaKfList kfList = wxMaService.getKefuService().kfList();
+ System.out.println("当前客服数量: " + kfList.getKfList().size());
+
+ // 2. 添加新客服账号
+ WxMaKfAccountRequest addRequest = WxMaKfAccountRequest.builder()
+ .kfAccount("service001@example")
+ .kfNick("客服001")
+ .kfPwd("password123")
+ .build();
+
+ boolean addResult = wxMaService.getKefuService().kfAccountAdd(addRequest);
+ System.out.println("添加客服账号结果: " + addResult);
+
+ // 3. 更新客服账号信息
+ WxMaKfAccountRequest updateRequest = WxMaKfAccountRequest.builder()
+ .kfAccount("service001@example")
+ .kfNick("高级客服001")
+ .build();
+
+ boolean updateResult = wxMaService.getKefuService().kfAccountUpdate(updateRequest);
+ System.out.println("更新客服账号结果: " + updateResult);
+ }
+
+ /**
+ * 演示客服会话管理功能
+ */
+ public void demonstrateSessionManagement() throws WxErrorException {
+ String userOpenid = "user_openid_example";
+ String kfAccount = "service001@example";
+
+ // 1. 创建客服会话
+ boolean createResult = wxMaService.getKefuService().kfSessionCreate(userOpenid, kfAccount);
+ System.out.println("创建会话结果: " + createResult);
+
+ // 2. 获取用户会话状态
+ WxMaKfSession session = wxMaService.getKefuService().kfSessionGet(userOpenid);
+ System.out.println("用户当前会话客服: " + session.getKfAccount());
+
+ // 3. 获取客服的会话列表
+ WxMaKfSessionList sessionList = wxMaService.getKefuService().kfSessionList(kfAccount);
+ System.out.println("客服当前会话数量: " + sessionList.getSessionList().size());
+
+ // 4. 关闭客服会话
+ boolean closeResult = wxMaService.getKefuService().kfSessionClose(userOpenid, kfAccount);
+ System.out.println("关闭会话结果: " + closeResult);
+ }
+
+ /**
+ * 演示客服账号删除功能
+ */
+ public void demonstrateAccountDeletion() throws WxErrorException {
+ String kfAccount = "service001@example";
+
+ boolean deleteResult = wxMaService.getKefuService().kfAccountDel(kfAccount);
+ System.out.println("删除客服账号结果: " + deleteResult);
+ }
+}
\ No newline at end of file
From 23a2e18f1cdcd3d3ca89e76cfbcf0497b66d2386 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:50:50 +0800
Subject: [PATCH 045/111] =?UTF-8?q?:new:=20#3682=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E6=96=B0=E5=A2=9E=E6=99=BA?=
=?UTF-8?q?=E8=83=BD=E6=9C=BA=E5=99=A8=E4=BA=BA=E7=9A=84=E6=8E=A5=E5=8F=A3?=
=?UTF-8?q?=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
weixin-java-cp/INTELLIGENT_ROBOT.md | 107 ++++++++++++++++++
.../cp/api/WxCpIntelligentRobotService.java | 67 +++++++++++
.../me/chanjar/weixin/cp/api/WxCpService.java | 7 ++
.../cp/api/impl/BaseWxCpServiceImpl.java | 6 +
.../impl/WxCpIntelligentRobotServiceImpl.java | 64 +++++++++++
.../WxCpIntelligentRobot.java | 77 +++++++++++++
.../WxCpIntelligentRobotChatRequest.java | 49 ++++++++
.../WxCpIntelligentRobotChatResponse.java | 46 ++++++++
.../WxCpIntelligentRobotCreateRequest.java | 43 +++++++
.../WxCpIntelligentRobotCreateResponse.java | 34 ++++++
.../WxCpIntelligentRobotUpdateRequest.java | 55 +++++++++
.../weixin/cp/constant/WxCpApiPathConsts.java | 36 ++++++
.../WxCpIntelligentRobotServiceImplTest.java | 88 ++++++++++++++
13 files changed, 679 insertions(+)
create mode 100644 weixin-java-cp/INTELLIGENT_ROBOT.md
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImplTest.java
diff --git a/weixin-java-cp/INTELLIGENT_ROBOT.md b/weixin-java-cp/INTELLIGENT_ROBOT.md
new file mode 100644
index 0000000000..f2641bd6b4
--- /dev/null
+++ b/weixin-java-cp/INTELLIGENT_ROBOT.md
@@ -0,0 +1,107 @@
+# 企业微信智能机器人接口
+
+本模块提供企业微信智能机器人相关的API接口实现。
+
+## 官方文档
+
+- [企业微信智能机器人接口](https://developer.work.weixin.qq.com/document/path/101039)
+
+## 接口说明
+
+### 获取服务实例
+
+```java
+WxCpService wxCpService = ...; // 初始化企业微信服务
+WxCpIntelligentRobotService robotService = wxCpService.getIntelligentRobotService();
+```
+
+### 创建智能机器人
+
+```java
+WxCpIntelligentRobotCreateRequest request = new WxCpIntelligentRobotCreateRequest();
+request.setName("我的智能机器人");
+request.setDescription("这是一个智能客服机器人");
+request.setAvatar("http://example.com/avatar.jpg");
+
+WxCpIntelligentRobotCreateResponse response = robotService.createRobot(request);
+String robotId = response.getRobotId();
+```
+
+### 更新智能机器人
+
+```java
+WxCpIntelligentRobotUpdateRequest request = new WxCpIntelligentRobotUpdateRequest();
+request.setRobotId("robot_id_here");
+request.setName("更新后的机器人名称");
+request.setDescription("更新后的描述");
+request.setStatus(1); // 1:启用, 0:停用
+
+robotService.updateRobot(request);
+```
+
+### 查询智能机器人
+
+```java
+String robotId = "robot_id_here";
+WxCpIntelligentRobot robot = robotService.getRobot(robotId);
+
+System.out.println("机器人名称: " + robot.getName());
+System.out.println("机器人状态: " + robot.getStatus());
+```
+
+### 智能对话
+
+```java
+WxCpIntelligentRobotChatRequest request = new WxCpIntelligentRobotChatRequest();
+request.setRobotId("robot_id_here");
+request.setUserid("user123");
+request.setMessage("你好,请问如何使用这个功能?");
+request.setSessionId("session123"); // 可选,用于保持会话连续性
+
+WxCpIntelligentRobotChatResponse response = robotService.chat(request);
+String reply = response.getReply();
+String sessionId = response.getSessionId();
+```
+
+### 重置会话
+
+```java
+String robotId = "robot_id_here";
+String userid = "user123";
+String sessionId = "session123";
+
+robotService.resetSession(robotId, userid, sessionId);
+```
+
+### 删除智能机器人
+
+```java
+String robotId = "robot_id_here";
+robotService.deleteRobot(robotId);
+```
+
+## 主要类说明
+
+### 请求类
+
+- `WxCpIntelligentRobotCreateRequest`: 创建机器人请求
+- `WxCpIntelligentRobotUpdateRequest`: 更新机器人请求
+- `WxCpIntelligentRobotChatRequest`: 智能对话请求
+
+### 响应类
+
+- `WxCpIntelligentRobotCreateResponse`: 创建机器人响应
+- `WxCpIntelligentRobotChatResponse`: 智能对话响应
+- `WxCpIntelligentRobot`: 机器人信息实体
+
+### 服务接口
+
+- `WxCpIntelligentRobotService`: 智能机器人服务接口
+- `WxCpIntelligentRobotServiceImpl`: 智能机器人服务实现
+
+## 注意事项
+
+1. 需要确保企业微信应用具有智能机器人相关权限
+2. 智能机器人功能可能需要特定的企业微信版本支持
+3. 会话ID可以用于保持对话的连续性,提升用户体验
+4. 机器人状态: 0表示停用,1表示启用
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java
new file mode 100644
index 0000000000..f68092918f
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java
@@ -0,0 +1,67 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.intelligentrobot.*;
+
+/**
+ * 企业微信智能机器人接口
+ * 官方文档: https://developer.work.weixin.qq.com/document/path/101039
+ *
+ * @author Binary Wang
+ */
+public interface WxCpIntelligentRobotService {
+
+ /**
+ * 创建智能机器人
+ *
+ * @param request 创建请求参数
+ * @return 创建结果
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException;
+
+ /**
+ * 删除智能机器人
+ *
+ * @param robotId 机器人ID
+ * @throws WxErrorException 微信接口异常
+ */
+ void deleteRobot(String robotId) throws WxErrorException;
+
+ /**
+ * 更新智能机器人
+ *
+ * @param request 更新请求参数
+ * @throws WxErrorException 微信接口异常
+ */
+ void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException;
+
+ /**
+ * 查询智能机器人
+ *
+ * @param robotId 机器人ID
+ * @return 机器人信息
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException;
+
+ /**
+ * 智能机器人会话
+ *
+ * @param request 聊天请求参数
+ * @return 聊天响应
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException;
+
+ /**
+ * 重置智能机器人会话
+ *
+ * @param robotId 机器人ID
+ * @param userid 用户ID
+ * @param sessionId 会话ID
+ * @throws WxErrorException 微信接口异常
+ */
+ void resetSession(String robotId, String userid, String sessionId) throws WxErrorException;
+
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
index 9bcb161534..0b601ca502 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
@@ -587,4 +587,11 @@ public interface WxCpService extends WxService {
* @return
*/
WxCpCorpGroupService getCorpGroupService();
+
+ /**
+ * 获取智能机器人服务
+ *
+ * @return 智能机器人服务 intelligent robot service
+ */
+ WxCpIntelligentRobotService getIntelligentRobotService();
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
index d0b7441d90..bc18c9bc7a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
@@ -74,6 +74,7 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH
private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this);
private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this);
+ private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
@@ -702,4 +703,9 @@ public WxCpMeetingService getMeetingService() {
public WxCpCorpGroupService getCorpGroupService() {
return corpGroupService;
}
+
+ @Override
+ public WxCpIntelligentRobotService getIntelligentRobotService() {
+ return this.intelligentRobotService;
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java
new file mode 100644
index 0000000000..c3bb23b38f
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java
@@ -0,0 +1,64 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.WxCpIntelligentRobotService;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.intelligentrobot.*;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.IntelligentRobot.*;
+
+/**
+ * 企业微信智能机器人接口实现
+ *
+ * @author Binary Wang
+ */
+@RequiredArgsConstructor
+public class WxCpIntelligentRobotServiceImpl implements WxCpIntelligentRobotService {
+
+ private final WxCpService cpService;
+
+ @Override
+ public WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException {
+ String responseText = this.cpService.post(CREATE_ROBOT, request.toJson());
+ return WxCpIntelligentRobotCreateResponse.fromJson(responseText);
+ }
+
+ @Override
+ public void deleteRobot(String robotId) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("robot_id", robotId);
+ this.cpService.post(DELETE_ROBOT, jsonObject.toString());
+ }
+
+ @Override
+ public void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException {
+ this.cpService.post(UPDATE_ROBOT, request.toJson());
+ }
+
+ @Override
+ public WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("robot_id", robotId);
+ String responseText = this.cpService.post(GET_ROBOT, jsonObject.toString());
+ return WxCpIntelligentRobot.fromJson(responseText);
+ }
+
+ @Override
+ public WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException {
+ String responseText = this.cpService.post(CHAT, request.toJson());
+ return WxCpIntelligentRobotChatResponse.fromJson(responseText);
+ }
+
+ @Override
+ public void resetSession(String robotId, String userid, String sessionId) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("robot_id", robotId);
+ jsonObject.addProperty("userid", userid);
+ jsonObject.addProperty("session_id", sessionId);
+ this.cpService.post(RESET_SESSION, jsonObject.toString());
+ }
+
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
new file mode 100644
index 0000000000..60d518cac3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人信息
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobot extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 机器人名称
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 机器人描述
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 机器人头像
+ */
+ @SerializedName("avatar")
+ private String avatar;
+
+ /**
+ * 机器人状态 0:停用 1:启用
+ */
+ @SerializedName("status")
+ private Integer status;
+
+ /**
+ * 创建时间
+ */
+ @SerializedName("create_time")
+ private Long createTime;
+
+ /**
+ * 更新时间
+ */
+ @SerializedName("update_time")
+ private Long updateTime;
+
+ /**
+ * From json wx cp intelligent robot.
+ *
+ * @param json the json
+ * @return the wx cp intelligent robot
+ */
+ public static WxCpIntelligentRobot fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobot.class);
+ }
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
new file mode 100644
index 0000000000..d94ea93623
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
@@ -0,0 +1,49 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人聊天请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotChatRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 用户ID
+ */
+ @SerializedName("userid")
+ private String userid;
+
+ /**
+ * 消息内容
+ */
+ @SerializedName("message")
+ private String message;
+
+ /**
+ * 会话ID,可选,用于保持会话连续性
+ */
+ @SerializedName("session_id")
+ private String sessionId;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotChatRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
new file mode 100644
index 0000000000..c7e56a4d03
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
@@ -0,0 +1,46 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人聊天响应
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotChatResponse extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人回复内容
+ */
+ @SerializedName("reply")
+ private String reply;
+
+ /**
+ * 会话ID
+ */
+ @SerializedName("session_id")
+ private String sessionId;
+
+ /**
+ * 消息ID
+ */
+ @SerializedName("msg_id")
+ private String msgId;
+
+ public static WxCpIntelligentRobotChatResponse fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatResponse.class);
+ }
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
new file mode 100644
index 0000000000..46dd5f609b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
@@ -0,0 +1,43 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 创建智能机器人请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotCreateRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人名称
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 机器人描述
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 机器人头像
+ */
+ @SerializedName("avatar")
+ private String avatar;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotCreateRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
new file mode 100644
index 0000000000..449d91f7d3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
@@ -0,0 +1,34 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 创建智能机器人响应
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotCreateResponse extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ public static WxCpIntelligentRobotCreateResponse fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateResponse.class);
+ }
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
new file mode 100644
index 0000000000..027812a7e9
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
@@ -0,0 +1,55 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 更新智能机器人请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotUpdateRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 机器人名称
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 机器人描述
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 机器人头像
+ */
+ @SerializedName("avatar")
+ private String avatar;
+
+ /**
+ * 机器人状态 0:停用 1:启用
+ */
+ @SerializedName("status")
+ private Integer status;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotUpdateRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotUpdateRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index fb6f8ef686..91314e5872 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -1631,4 +1631,40 @@ interface IdConvert {
*/
String CONVERT_TMP_EXTERNAL_USER_ID = "/cgi-bin/idconvert/convert_tmp_external_userid";
}
+
+ /**
+ * 智能机器人相关接口
+ * 官方文档: https://developer.work.weixin.qq.com/document/path/101039
+ */
+ interface IntelligentRobot {
+ /**
+ * 创建智能机器人
+ */
+ String CREATE_ROBOT = "/cgi-bin/intelligent_robot/create";
+
+ /**
+ * 删除智能机器人
+ */
+ String DELETE_ROBOT = "/cgi-bin/intelligent_robot/delete";
+
+ /**
+ * 更新智能机器人
+ */
+ String UPDATE_ROBOT = "/cgi-bin/intelligent_robot/update";
+
+ /**
+ * 查询智能机器人
+ */
+ String GET_ROBOT = "/cgi-bin/intelligent_robot/get";
+
+ /**
+ * 智能机器人会话
+ */
+ String CHAT = "/cgi-bin/intelligent_robot/chat";
+
+ /**
+ * 重置智能机器人会话
+ */
+ String RESET_SESSION = "/cgi-bin/intelligent_robot/reset_session";
+ }
}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImplTest.java
new file mode 100644
index 0000000000..2765b49916
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImplTest.java
@@ -0,0 +1,88 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.bean.intelligentrobot.*;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import javax.inject.Inject;
+
+/**
+ * 智能机器人接口测试
+ *
+ * @author Binary Wang
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxCpIntelligentRobotServiceImplTest {
+
+ @Inject
+ private WxCpService wxCpService;
+
+ @Test
+ public void testCreateRobot() {
+ // 测试创建智能机器人请求对象创建
+ WxCpIntelligentRobotCreateRequest request = new WxCpIntelligentRobotCreateRequest();
+ request.setName("测试机器人");
+ request.setDescription("这是一个测试的智能机器人");
+ request.setAvatar("avatar_url");
+
+ // 验证JSON序列化
+ String json = request.toJson();
+ assert json.contains("测试机器人");
+ assert json.contains("这是一个测试的智能机器人");
+
+ // 验证反序列化
+ WxCpIntelligentRobotCreateRequest fromJson = WxCpIntelligentRobotCreateRequest.fromJson(json);
+ assert fromJson.getName().equals("测试机器人");
+ }
+
+ @Test
+ public void testChatRequest() {
+ // 测试聊天请求对象创建
+ WxCpIntelligentRobotChatRequest request = new WxCpIntelligentRobotChatRequest();
+ request.setRobotId("robot123");
+ request.setUserid("user123");
+ request.setMessage("你好,机器人");
+ request.setSessionId("session123");
+
+ // 验证JSON序列化
+ String json = request.toJson();
+ assert json.contains("robot123");
+ assert json.contains("你好,机器人");
+
+ // 验证反序列化
+ WxCpIntelligentRobotChatRequest fromJson = WxCpIntelligentRobotChatRequest.fromJson(json);
+ assert fromJson.getRobotId().equals("robot123");
+ assert fromJson.getMessage().equals("你好,机器人");
+ }
+
+ @Test
+ public void testUpdateRequest() {
+ // 测试更新请求对象创建
+ WxCpIntelligentRobotUpdateRequest request = new WxCpIntelligentRobotUpdateRequest();
+ request.setRobotId("robot123");
+ request.setName("更新后的机器人");
+ request.setDescription("更新后的描述");
+ request.setStatus(1);
+
+ // 验证JSON序列化
+ String json = request.toJson();
+ assert json.contains("robot123");
+ assert json.contains("更新后的机器人");
+
+ // 验证反序列化
+ WxCpIntelligentRobotUpdateRequest fromJson = WxCpIntelligentRobotUpdateRequest.fromJson(json);
+ assert fromJson.getRobotId().equals("robot123");
+ assert fromJson.getName().equals("更新后的机器人");
+ assert fromJson.getStatus().equals(1);
+ }
+
+ @Test
+ public void testServiceIntegration() {
+ // 验证服务可以正确获取
+ assert this.wxCpService.getIntelligentRobotService() != null;
+ assert this.wxCpService.getIntelligentRobotService() instanceof WxCpIntelligentRobotServiceImpl;
+ }
+}
\ No newline at end of file
From 26f5887e22360f7cadcfc9bb9cbd17b0b7bd822c Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 11:52:33 +0800
Subject: [PATCH 046/111] =?UTF-8?q?:memo:=20=E4=B8=BA=E7=8E=B0=E6=9C=89?=
=?UTF-8?q?=E7=9A=84=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E6=B5=81=E7=A8=8B?=
=?UTF-8?q?=E5=AE=A1=E6=89=B9=E5=8A=9F=E8=83=BD=E6=B7=BB=E5=8A=A0=E5=AE=8C?=
=?UTF-8?q?=E6=95=B4=E7=9A=84=E4=BD=BF=E7=94=A8=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md | 141 +++++++++++++
.../cp/demo/WxCpApprovalWorkflowDemo.java | 189 ++++++++++++++++++
2 files changed, 330 insertions(+)
create mode 100644 weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
diff --git a/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md b/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md
new file mode 100644
index 0000000000..d2c533b5e5
--- /dev/null
+++ b/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md
@@ -0,0 +1,141 @@
+# WeChat Enterprise Workflow Approval Guide
+# 企业微信流程审批功能使用指南
+
+## Overview / 概述
+
+WxJava SDK provides comprehensive support for WeChat Enterprise workflow approval (企业微信流程审批), including both traditional OA approval and the approval process engine.
+
+WxJava SDK 提供全面的企业微信流程审批支持,包括传统OA审批和审批流程引擎。
+
+## Current Implementation Status / 当前实现状态
+
+### ✅ Fully Implemented APIs / 已完整实现的API
+
+1. **Submit Approval Application / 提交审批申请**
+ - Endpoint: `/cgi-bin/oa/applyevent`
+ - Documentation: [91853](https://work.weixin.qq.com/api/doc/90000/90135/91853)
+ - Implementation: `WxCpOaService.apply(WxCpOaApplyEventRequest)`
+
+2. **Get Approval Details / 获取审批申请详情**
+ - Endpoint: `/cgi-bin/oa/getapprovaldetail`
+ - Implementation: `WxCpOaService.getApprovalDetail(String spNo)`
+
+3. **Batch Get Approval Numbers / 批量获取审批单号**
+ - Endpoint: `/cgi-bin/oa/getapprovalinfo`
+ - Implementation: `WxCpOaService.getApprovalInfo(...)`
+
+4. **Approval Process Engine / 审批流程引擎**
+ - Endpoint: `/cgi-bin/corp/getopenapprovaldata`
+ - Implementation: `WxCpOaAgentService.getOpenApprovalData(String thirdNo)`
+
+5. **Template Management / 模板管理**
+ - Create: `WxCpOaService.createOaApprovalTemplate(...)`
+ - Update: `WxCpOaService.updateOaApprovalTemplate(...)`
+ - Get Details: `WxCpOaService.getTemplateDetail(...)`
+
+## Usage Examples / 使用示例
+
+### 1. Submit Approval Application / 提交审批申请
+
+```java
+// Create approval request
+WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest()
+ .setCreatorUserId("userId")
+ .setTemplateId("templateId")
+ .setUseTemplateApprover(0)
+ .setApprovers(Arrays.asList(
+ new WxCpOaApplyEventRequest.Approver()
+ .setAttr(2)
+ .setUserIds(new String[]{"approver1", "approver2"})
+ ))
+ .setNotifiers(new String[]{"notifier1", "notifier2"})
+ .setNotifyType(1)
+ .setApplyData(new WxCpOaApplyEventRequest.ApplyData()
+ .setContents(Arrays.asList(
+ new ApplyDataContent()
+ .setControl("Text")
+ .setId("Text-1234567890")
+ .setValue(new ContentValue().setText("Approval content"))
+ ))
+ );
+
+// Submit approval
+String spNo = wxCpService.getOaService().apply(request);
+```
+
+### 2. Get Approval Details / 获取审批详情
+
+```java
+// Get approval details by approval number
+WxCpApprovalDetailResult result = wxCpService.getOaService()
+ .getApprovalDetail("approval_number");
+
+WxCpApprovalDetailResult.WxCpApprovalDetail detail = result.getInfo();
+System.out.println("Approval Status: " + detail.getSpStatus());
+System.out.println("Approval Name: " + detail.getSpName());
+```
+
+### 3. Batch Get Approval Information / 批量获取审批信息
+
+```java
+// Get approval info with filters
+Date startTime = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000);
+Date endTime = new Date();
+
+WxCpApprovalInfo approvalInfo = wxCpService.getOaService()
+ .getApprovalInfo(startTime, endTime, "0", 100, null);
+
+List spNumbers = approvalInfo.getSpNoList();
+```
+
+### 4. Third-Party Application Support / 第三方应用支持
+
+```java
+// For third-party applications
+WxCpTpOAService tpOaService = wxCpTpService.getOaService();
+
+// Submit approval for specific corp
+String spNo = tpOaService.apply(request, "corpId");
+
+// Get approval details for specific corp
+WxCpApprovalDetailResult detail = tpOaService.getApprovalDetail("spNo", "corpId");
+```
+
+## Multi-Account Configuration / 多账号配置支持
+
+WxJava supports multi-account configurations for enterprise scenarios:
+
+```java
+// Spring Boot configuration example
+@Autowired
+private WxCpMultiServices wxCpMultiServices;
+
+// Get service for specific corp
+WxCpService wxCpService = wxCpMultiServices.getWxCpService("corpId");
+WxCpOaService oaService = wxCpService.getOaService();
+```
+
+## Available Data Models / 可用数据模型
+
+- `WxCpOaApplyEventRequest` - Approval application request
+- `WxCpApprovalDetailResult` - Approval details response
+- `WxCpApprovalInfo` - Batch approval information
+- `WxCpXmlApprovalInfo` - XML approval message handling
+- `WxCpOaApprovalTemplate` - Approval template management
+
+## Documentation References / 文档参考
+
+- [Submit Approval Application (91853)](https://work.weixin.qq.com/api/doc/90000/90135/91853)
+- [Get Approval Details (91983)](https://work.weixin.qq.com/api/doc/90000/90135/91983)
+- [Batch Get Approval Numbers (91816)](https://work.weixin.qq.com/api/doc/90000/90135/91816)
+- [Approval Process Engine (90269)](https://developer.work.weixin.qq.com/document/path/90269)
+
+## Conclusion / 结论
+
+WxJava already provides comprehensive support for WeChat Enterprise workflow approval. The "new version" (新版) approval functionality referenced in issue requests is **already fully implemented** and available for use.
+
+WxJava 已经提供了企业微信流程审批的全面支持。问题中提到的"新版"流程审批功能**已经完全实现**并可使用。
+
+For questions about specific usage, please refer to the test cases in `WxCpOaServiceImplTest` and the comprehensive API documentation.
+
+有关具体使用问题,请参考 `WxCpOaServiceImplTest` 中的测试用例和全面的API文档。
\ No newline at end of file
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
new file mode 100644
index 0000000000..66c303a79d
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
@@ -0,0 +1,189 @@
+package me.chanjar.weixin.cp.demo;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult;
+import me.chanjar.weixin.cp.bean.oa.WxCpApprovalInfo;
+import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest;
+import me.chanjar.weixin.cp.bean.oa.applydata.ApplyDataContent;
+import me.chanjar.weixin.cp.bean.oa.applydata.ContentValue;
+
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * 企业微信流程审批功能演示代码
+ * WeChat Enterprise Workflow Approval Demo
+ *
+ * 演示如何使用WxJava SDK中已实现的完整审批流程功能
+ * Demonstrates how to use the comprehensive approval workflow features already implemented in WxJava SDK
+ *
+ * 文档参考 Documentation Reference: https://work.weixin.qq.com/api/doc/90000/90135/91853
+ */
+public class WxCpApprovalWorkflowDemo {
+
+ private WxCpService wxCpService;
+
+ public WxCpApprovalWorkflowDemo(WxCpService wxCpService) {
+ this.wxCpService = wxCpService;
+ }
+
+ /**
+ * 示例1: 提交审批申请
+ * Example 1: Submit Approval Application
+ * API: /cgi-bin/oa/applyevent (Document 91853)
+ */
+ public String submitApprovalApplication() throws Exception {
+ // 构建审批申请请求
+ WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest()
+ .setCreatorUserId("creator_user_id") // 申请人userid
+ .setTemplateId("3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS") // 模板id
+ .setUseTemplateApprover(0) // 不使用模板中的审批流
+ .setApprovers(Arrays.asList(
+ new WxCpOaApplyEventRequest.Approver()
+ .setAttr(2) // 审批类型: 或签
+ .setUserIds(new String[]{"approver1", "approver2"})
+ ))
+ .setNotifiers(new String[]{"notifier1", "notifier2"}) // 抄送人
+ .setNotifyType(1) // 抄送方式: 提单时抄送
+ .setApplyData(new WxCpOaApplyEventRequest.ApplyData()
+ .setContents(Arrays.asList(
+ // 文本控件
+ new ApplyDataContent()
+ .setControl("Text")
+ .setId("Text-1234567890")
+ .setValue(new ContentValue().setText("这是一个审批申请的文本内容")),
+
+ // 数字控件
+ new ApplyDataContent()
+ .setControl("Number")
+ .setId("Number-1234567890")
+ .setValue(new ContentValue().setNewNumber("1000")),
+
+ // 金额控件
+ new ApplyDataContent()
+ .setControl("Money")
+ .setId("Money-1234567890")
+ .setValue(new ContentValue().setNewMoney("10000"))
+ ))
+ );
+
+ // 提交审批申请
+ String spNo = wxCpService.getOaService().apply(request);
+ System.out.println("审批申请提交成功,审批单号: " + spNo);
+
+ return spNo;
+ }
+
+ /**
+ * 示例2: 获取审批申请详情
+ * Example 2: Get Approval Application Details
+ * API: /cgi-bin/oa/getapprovaldetail
+ */
+ public void getApprovalDetails(String spNo) throws Exception {
+ WxCpApprovalDetailResult result = wxCpService.getOaService().getApprovalDetail(spNo);
+
+ WxCpApprovalDetailResult.WxCpApprovalDetail detail = result.getInfo();
+
+ System.out.println("审批单号: " + detail.getSpNo());
+ System.out.println("审批名称: " + detail.getSpName());
+ System.out.println("审批状态: " + detail.getSpStatus().getCode());
+ System.out.println("申请人: " + detail.getApplyer().getUserId());
+ System.out.println("申请时间: " + detail.getApplyTime());
+
+ // 打印审批记录
+ if (detail.getSpRecord() != null) {
+ detail.getSpRecord().forEach(record -> {
+ System.out.println("审批节点状态: " + record.getSpStatus());
+ System.out.println("审批人: " + record.getDetails());
+ });
+ }
+ }
+
+ /**
+ * 示例3: 批量获取审批单号
+ * Example 3: Batch Get Approval Numbers
+ * API: /cgi-bin/oa/getapprovalinfo
+ */
+ public void batchGetApprovalInfo() throws Exception {
+ // 获取最近7天的审批单
+ Date startTime = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L);
+ Date endTime = new Date();
+
+ WxCpApprovalInfo approvalInfo = wxCpService.getOaService()
+ .getApprovalInfo(startTime, endTime, "0", 100, null);
+
+ System.out.println("获取到的审批单数量: " + approvalInfo.getCount());
+
+ // 遍历审批单号
+ if (approvalInfo.getSpNoList() != null) {
+ approvalInfo.getSpNoList().forEach(spNo -> {
+ System.out.println("审批单号: " + spNo);
+ // 可以进一步调用 getApprovalDetails 获取详情
+ });
+ }
+ }
+
+ /**
+ * 示例4: 模板管理
+ * Example 4: Template Management
+ */
+ public void templateManagement() throws Exception {
+ // 获取模板详情
+ String templateId = "3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS";
+ var templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
+
+ System.out.println("模板名称: " + templateResult.getTemplateNames());
+ System.out.println("模板内容: " + templateResult.getTemplateContent());
+ }
+
+ /**
+ * 完整的审批流程演示
+ * Complete Approval Workflow Demo
+ */
+ public void completeWorkflowDemo() {
+ try {
+ System.out.println("=== 企业微信流程审批完整演示 ===");
+
+ // 1. 提交审批申请
+ System.out.println("\n1. 提交审批申请...");
+ String spNo = submitApprovalApplication();
+
+ // 2. 获取审批详情
+ System.out.println("\n2. 获取审批详情...");
+ getApprovalDetails(spNo);
+
+ // 3. 批量获取审批信息
+ System.out.println("\n3. 批量获取审批信息...");
+ batchGetApprovalInfo();
+
+ // 4. 模板管理
+ System.out.println("\n4. 模板管理...");
+ templateManagement();
+
+ System.out.println("\n=== 演示完成 ===");
+ System.out.println("WxJava SDK 已经完整支持企业微信流程审批功能!");
+
+ } catch (Exception e) {
+ System.err.println("演示过程中发生错误: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) {
+ // 注意: 这里需要配置真实的企业微信服务
+ // Note: You need to configure real WeChat Enterprise service here
+
+ System.out.println("企业微信流程审批功能演示");
+ System.out.println("该演示代码展示了WxJava SDK中已经完整实现的审批流程功能");
+ System.out.println("包括文档91853中描述的所有核心功能");
+ System.out.println("");
+ System.out.println("主要功能:");
+ System.out.println("- 提交审批申请 (/cgi-bin/oa/applyevent)");
+ System.out.println("- 获取审批详情 (/cgi-bin/oa/getapprovaldetail)");
+ System.out.println("- 批量获取审批单号 (/cgi-bin/oa/getapprovalinfo)");
+ System.out.println("- 模板管理功能");
+ System.out.println("- 审批流程引擎支持");
+ System.out.println("");
+ System.out.println("如需运行演示,请配置正确的企业微信服务参数。");
+ }
+}
\ No newline at end of file
From 155f3f778ff133769593137738b246d6fde9271d Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 21:38:57 +0800
Subject: [PATCH 047/111] =?UTF-8?q?:art:=20#3570=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1?=
=?UTF-8?q?=E4=BA=91=E6=89=98=E7=AE=A1=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../miniapp/api/impl/BaseWxMaServiceImpl.java | 5 +-
.../api/impl/WxMaServiceHttpClientImpl.java | 14 +++---
.../api/impl/WxMaServiceJoddHttpImpl.java | 13 +++---
.../api/impl/WxMaServiceOkHttpImpl.java | 13 +++---
.../wx/miniapp/config/WxMaConfig.java | 46 +++++++++++++++++++
.../config/impl/WxMaDefaultConfigImpl.java | 14 ++++++
6 files changed, 82 insertions(+), 23 deletions(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
index 0769eaae67..a5d479b65a 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -427,8 +427,9 @@ private R executeInternal(
}
String accessToken = getAccessToken(false);
- if (StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl())) {
- uri = uri.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl());
+ String effectiveApiHostUrl = this.getWxMaConfig().getEffectiveApiHostUrl();
+ if (!WxMaConfig.DEFAULT_API_HOST_URL.equals(effectiveApiHostUrl)) {
+ uri = uri.replace(WxMaConfig.DEFAULT_API_HOST_URL, effectiveApiHostUrl);
}
String uriWithAccessToken =
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
index 9734e25933..73b4994347 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
@@ -64,12 +64,10 @@ public HttpClientType getRequestType() {
@Override
protected String doGetAccessTokenRequest() throws IOException {
-
String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
- this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
- WxMaService.GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
- WxMaService.GET_ACCESS_TOKEN_URL;
-
+ this.getWxMaConfig().getAccessTokenUrl() :
+ WxMaService.GET_ACCESS_TOKEN_URL.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
@@ -84,9 +82,9 @@ protected String doGetAccessTokenRequest() throws IOException {
@Override
protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
- this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
- GET_STABLE_ACCESS_TOKEN.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
- GET_STABLE_ACCESS_TOKEN;
+ this.getWxMaConfig().getAccessTokenUrl() :
+ GET_STABLE_ACCESS_TOKEN.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
HttpPost httpPost = new HttpPost(url);
if (this.getRequestHttpProxy() != null) {
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
index d23d865cf9..94806121ab 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
@@ -50,9 +50,9 @@ public HttpClientType getRequestType() {
@Override
protected String doGetAccessTokenRequest() throws IOException {
String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
- this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
- WxMaService.GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
- WxMaService.GET_ACCESS_TOKEN_URL;
+ this.getWxMaConfig().getAccessTokenUrl() :
+ WxMaService.GET_ACCESS_TOKEN_URL.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
HttpRequest request = HttpRequest.get(url);
@@ -67,11 +67,10 @@ protected String doGetAccessTokenRequest() throws IOException {
@Override
protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
-
String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
- this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
- GET_STABLE_ACCESS_TOKEN.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
- GET_STABLE_ACCESS_TOKEN;
+ this.getWxMaConfig().getAccessTokenUrl() :
+ GET_STABLE_ACCESS_TOKEN.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
WxMaStableAccessTokenRequest wxMaAccessTokenRequest = new WxMaStableAccessTokenRequest();
wxMaAccessTokenRequest.setAppid(this.getWxMaConfig().getAppid());
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
index 1053b809e9..8811028fee 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
@@ -65,9 +65,9 @@ public HttpClientType getRequestType() {
@Override
protected String doGetAccessTokenRequest() throws IOException {
String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
- this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
- WxMaService.GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
- WxMaService.GET_ACCESS_TOKEN_URL;
+ this.getWxMaConfig().getAccessTokenUrl() :
+ WxMaService.GET_ACCESS_TOKEN_URL.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
Request request = new Request.Builder().url(url).get().build();
@@ -79,9 +79,10 @@ protected String doGetAccessTokenRequest() throws IOException {
@Override
protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
- this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
- GET_STABLE_ACCESS_TOKEN.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
- GET_STABLE_ACCESS_TOKEN;
+ this.getWxMaConfig().getAccessTokenUrl() :
+ GET_STABLE_ACCESS_TOKEN.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
+
WxMaStableAccessTokenRequest wxMaAccessTokenRequest = new WxMaStableAccessTokenRequest();
wxMaAccessTokenRequest.setAppid(this.getWxMaConfig().getAppid());
wxMaAccessTokenRequest.setSecret(this.getWxMaConfig().getSecret());
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
index 59652ed0a9..8164d48346 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
@@ -319,4 +319,50 @@ default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {}
/** 密钥对应的小程序 ID(普通小程序为 appId,托管第三方平台为 componentAppId) */
String getWechatMpAppid();
+
+ /** 微信 API 默认主机地址 */
+ String DEFAULT_API_HOST_URL = "https://api.weixin.qq.com";
+ /** 微信云托管使用的 HTTP 协议主机地址 */
+ String CLOUD_RUN_API_HOST_URL = "http://api.weixin.qq.com";
+
+ /**
+ * 是否使用微信云托管内网模式
+ * 当部署在微信云托管环境时,api.weixin.qq.com 会被解析为内网地址,此时需要使用 HTTP 协议访问
+ * 开启此配置后,SDK 会自动将 https://api.weixin.qq.com 替换为 http://api.weixin.qq.com
+ *
+ * @see 微信云托管内网调用微信接口
+ * @return 是否使用微信云托管模式
+ */
+ default boolean isUseWxCloudRun() {
+ return false;
+ }
+
+ /**
+ * 设置是否使用微信云托管内网模式
+ * 当部署在微信云托管环境时,api.weixin.qq.com 会被解析为内网地址,此时需要使用 HTTP 协议访问
+ * 开启此配置后,SDK 会自动将 https://api.weixin.qq.com 替换为 http://api.weixin.qq.com
+ *
+ * @see 微信云托管内网调用微信接口
+ * @param useWxCloudRun 是否使用微信云托管模式
+ */
+ default void setUseWxCloudRun(boolean useWxCloudRun) {
+ // 默认空实现
+ }
+
+ /**
+ * 根据配置获取实际应使用的 API 主机地址
+ * 优先级:自定义 apiHostUrl > 微信云托管模式 > 默认 HTTPS 地址
+ *
+ * @return 实际应使用的 API 主机地址
+ */
+ default String getEffectiveApiHostUrl() {
+ String apiHostUrl = getApiHostUrl();
+ if (apiHostUrl != null && !apiHostUrl.isEmpty()) {
+ return apiHostUrl;
+ }
+ if (isUseWxCloudRun()) {
+ return CLOUD_RUN_API_HOST_URL;
+ }
+ return DEFAULT_API_HOST_URL;
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
index ab82d6209e..07dfaefcc9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
@@ -69,6 +69,10 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
private String apiHostUrl;
private String accessTokenUrl;
+ /** 是否使用微信云托管模式(使用 HTTP 协议访问内网地址) */
+ @Getter(AccessLevel.NONE)
+ private boolean useWxCloudRun = false;
+
/** 自定义配置token的消费者 */
@Setter private Consumer updateAccessTokenBefore;
@@ -388,6 +392,16 @@ public void setAccessTokenUrl(String accessTokenUrl) {
this.accessTokenUrl = accessTokenUrl;
}
+ @Override
+ public boolean isUseWxCloudRun() {
+ return this.useWxCloudRun;
+ }
+
+ @Override
+ public void setUseWxCloudRun(boolean useWxCloudRun) {
+ this.useWxCloudRun = useWxCloudRun;
+ }
+
@Override
public String getAppid() {
return appid;
From 84e70febdff39ca999f506c5b5752d0620ef1424 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Fri, 28 Nov 2025 21:54:48 +0800
Subject: [PATCH 048/111] update agent description
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
更新代理名称和描述为中文,并强调提交信息也需使用中文。
---
.github/agents/my-agent.agent.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/agents/my-agent.agent.md b/.github/agents/my-agent.agent.md
index c51fbf9d6d..dcce85fd88 100644
--- a/.github/agents/my-agent.agent.md
+++ b/.github/agents/my-agent.agent.md
@@ -4,10 +4,10 @@
# To make this agent available, merge this file into the default repository branch.
# For format details, see: https://gh.io/customagents/config
-name: 自定义的
-description: 需要用中文
+name: 全部用中文
+description: 需要用中文,包括PR标题和分析总结过程
---
# My Agent
-请使用中文输出思考过程和总结,提交commit信息也要使用中文
+请使用中文输出思考过程和总结,包括PR标题,提交commit信息也要使用中文
From fd3bbde725476ccaeef3a8c3033c7cf0d0e1b7bd Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Fri, 28 Nov 2025 22:36:12 +0800
Subject: [PATCH 049/111] =?UTF-8?q?:bookmark:=20=E5=8F=91=E5=B8=83=204.7.9?=
=?UTF-8?q?.B=20=E6=B5=8B=E8=AF=95=E7=89=88=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 1 -
MINIAPP_KEFU_SERVICE.md => docs/MINIAPP_KEFU_SERVICE.md | 0
NEW_TRANSFER_API_SUPPORT.md => docs/NEW_TRANSFER_API_SUPPORT.md | 0
NEW_TRANSFER_API_USAGE.md => docs/NEW_TRANSFER_API_USAGE.md | 0
QUARKUS_SUPPORT.md => docs/QUARKUS_SUPPORT.md | 0
pom.xml | 2 +-
solon-plugins/pom.xml | 2 +-
solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-channel-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-cp-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-miniapp-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-mp-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-open-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-pay-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-qidian-solon-plugin/pom.xml | 2 +-
spring-boot-starters/pom.xml | 2 +-
.../wx-java-channel-multi-spring-boot-starter/pom.xml | 2 +-
.../wx-java-channel-spring-boot-starter/pom.xml | 2 +-
.../wx-java-cp-multi-spring-boot-starter/pom.xml | 2 +-
spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml | 2 +-
.../wx-java-cp-tp-multi-spring-boot-starter/pom.xml | 2 +-
.../wx-java-miniapp-multi-spring-boot-starter/pom.xml | 2 +-
.../wx-java-miniapp-spring-boot-starter/pom.xml | 2 +-
.../wx-java-mp-multi-spring-boot-starter/pom.xml | 2 +-
spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml | 2 +-
spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml | 2 +-
spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml | 2 +-
spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml | 2 +-
weixin-graal/pom.xml | 2 +-
weixin-java-channel/pom.xml | 2 +-
weixin-java-common/pom.xml | 2 +-
weixin-java-cp/pom.xml | 2 +-
weixin-java-miniapp/pom.xml | 2 +-
weixin-java-mp/pom.xml | 2 +-
weixin-java-open/pom.xml | 2 +-
weixin-java-pay/pom.xml | 2 +-
weixin-java-qidian/pom.xml | 2 +-
40 files changed, 35 insertions(+), 36 deletions(-)
rename MINIAPP_KEFU_SERVICE.md => docs/MINIAPP_KEFU_SERVICE.md (100%)
rename NEW_TRANSFER_API_SUPPORT.md => docs/NEW_TRANSFER_API_SUPPORT.md (100%)
rename NEW_TRANSFER_API_USAGE.md => docs/NEW_TRANSFER_API_USAGE.md (100%)
rename QUARKUS_SUPPORT.md => docs/QUARKUS_SUPPORT.md (100%)
diff --git a/README.md b/README.md
index 0b86319b66..f1cccac4b3 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,6 @@
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。
2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。
3. **2024-12-30 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**!
-4. **从 4.7.8.B 版本开始支持 Quarkus/GraalVM Native Image,详见 [【Quarkus 支持文档】](QUARKUS_SUPPORT.md)**。
5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
diff --git a/MINIAPP_KEFU_SERVICE.md b/docs/MINIAPP_KEFU_SERVICE.md
similarity index 100%
rename from MINIAPP_KEFU_SERVICE.md
rename to docs/MINIAPP_KEFU_SERVICE.md
diff --git a/NEW_TRANSFER_API_SUPPORT.md b/docs/NEW_TRANSFER_API_SUPPORT.md
similarity index 100%
rename from NEW_TRANSFER_API_SUPPORT.md
rename to docs/NEW_TRANSFER_API_SUPPORT.md
diff --git a/NEW_TRANSFER_API_USAGE.md b/docs/NEW_TRANSFER_API_USAGE.md
similarity index 100%
rename from NEW_TRANSFER_API_USAGE.md
rename to docs/NEW_TRANSFER_API_USAGE.md
diff --git a/QUARKUS_SUPPORT.md b/docs/QUARKUS_SUPPORT.md
similarity index 100%
rename from QUARKUS_SUPPORT.md
rename to docs/QUARKUS_SUPPORT.md
diff --git a/pom.xml b/pom.xml
index 0b164611ca..eba8e083a7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
pom
WxJava - Weixin/Wechat Java SDK
微信开发Java SDK
diff --git a/solon-plugins/pom.xml b/solon-plugins/pom.xml
index 5a4dd8728f..971fc184ab 100644
--- a/solon-plugins/pom.xml
+++ b/solon-plugins/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
pom
wx-java-solon-plugins
diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml
index 983e6ef38e..df721c03a3 100644
--- a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-channel-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-solon-plugin/pom.xml
index 778b34cad0..5e497a4c46 100644
--- a/solon-plugins/wx-java-channel-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-channel-solon-plugin/pom.xml
@@ -3,7 +3,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml
index 28f4006b53..c39b84dc23 100644
--- a/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml
@@ -4,7 +4,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-cp-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-solon-plugin/pom.xml
index 5f224f4f99..412db6ea98 100644
--- a/solon-plugins/wx-java-cp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-cp-solon-plugin/pom.xml
@@ -4,7 +4,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml
index 0e9e015ff3..a1cab06d60 100644
--- a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml
index ab9f3aaef3..513de54cd7 100644
--- a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml
@@ -4,7 +4,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml
index 405a2cf52a..b65c3e4945 100644
--- a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-mp-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-solon-plugin/pom.xml
index 43609279e1..b504caf7d8 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-mp-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-open-solon-plugin/pom.xml b/solon-plugins/wx-java-open-solon-plugin/pom.xml
index 49de3701a3..368d4a6258 100644
--- a/solon-plugins/wx-java-open-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-open-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-pay-solon-plugin/pom.xml b/solon-plugins/wx-java-pay-solon-plugin/pom.xml
index dc32f659d8..baace7b37b 100644
--- a/solon-plugins/wx-java-pay-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-pay-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml
index a577e2cc79..f1c7c2f26b 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml
@@ -3,7 +3,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml
index 70e8c33395..fda5172752 100644
--- a/spring-boot-starters/pom.xml
+++ b/spring-boot-starters/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
pom
wx-java-spring-boot-starters
diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml
index f28a510d51..e0a53f8313 100644
--- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml
index c8ced3b3b1..63b74d4763 100644
--- a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
index cdf607ed30..b1806a3476 100644
--- a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml
index b0044365e8..5b8419bf23 100644
--- a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml
index 766021b7e8..097fc7e07a 100644
--- a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml
index 3142e70f90..e265218a37 100644
--- a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
index b5eb39a7e5..a6f0fc2a38 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
index 70cb5df930..fe10f8332f 100644
--- a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
index e69261574b..06dfe0d511 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
index 4529d80b64..8afd1b83a9 100644
--- a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
index 82125a8e68..ff1d6b84b1 100644
--- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml
index a35a311fd7..2257f8253e 100644
--- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml
index 1255aaa93e..8e91201f38 100644
--- a/weixin-graal/pom.xml
+++ b/weixin-graal/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-graal
diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml
index 2b6893d2b8..b18759d728 100644
--- a/weixin-java-channel/pom.xml
+++ b/weixin-java-channel/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-channel
diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml
index d176ee7576..d3496f923a 100644
--- a/weixin-java-common/pom.xml
+++ b/weixin-java-common/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-common
diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml
index 6ee31e759b..00a6b2d06c 100644
--- a/weixin-java-cp/pom.xml
+++ b/weixin-java-cp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-cp
diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml
index 8a9cb02dc0..ac308a4164 100644
--- a/weixin-java-miniapp/pom.xml
+++ b/weixin-java-miniapp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-miniapp
diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml
index 1fb7271d37..2b2e8e4210 100644
--- a/weixin-java-mp/pom.xml
+++ b/weixin-java-mp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-mp
diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml
index 0e815f1076..f354cb16f8 100644
--- a/weixin-java-open/pom.xml
+++ b/weixin-java-open/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-open
diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml
index f2d03845b0..1902f01892 100644
--- a/weixin-java-pay/pom.xml
+++ b/weixin-java-pay/pom.xml
@@ -5,7 +5,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
4.0.0
diff --git a/weixin-java-qidian/pom.xml b/weixin-java-qidian/pom.xml
index 62734353df..5135dea3c8 100644
--- a/weixin-java-qidian/pom.xml
+++ b/weixin-java-qidian/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.8.B
+ 4.7.9.B
weixin-java-qidian
From 4d0617f7bf53e9581f4ebee15e6ebf1941780994 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 2 Dec 2025 11:24:40 +0800
Subject: [PATCH 050/111] =?UTF-8?q?:bug:=20#3515=20=E4=BF=AE=E5=A4=8DOkHtt?=
=?UTF-8?q?p=E8=AF=B7=E6=B1=82=E6=96=B9=E5=BC=8F=E6=97=B6=E4=BB=A3?=
=?UTF-8?q?=E7=90=86=E8=AE=A4=E8=AF=81=E5=A4=B4=E8=AE=BE=E7=BD=AE=E9=94=99?=
=?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java | 4 ++--
.../me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java | 4 ++--
.../weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java | 4 ++--
.../binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java | 4 ++--
.../weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java
index 6d109be70d..f7b0ccc65b 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java
@@ -41,11 +41,11 @@ public void initHttp() {
this.httpProxy = OkHttpProxyInfo.httpProxy(this.config.getHttpProxyHost(), this.config.getHttpProxyPort(), this.config.getHttpProxyUsername(), this.config.getHttpProxyPassword());
okhttp3.OkHttpClient.Builder clientBuilder = new okhttp3.OkHttpClient.Builder();
clientBuilder.proxy(this.getRequestHttpProxy().getProxy());
- clientBuilder.authenticator(new Authenticator() {
+ clientBuilder.proxyAuthenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(WxChannelServiceOkHttpImpl.this.httpProxy.getProxyUsername(), WxChannelServiceOkHttpImpl.this.httpProxy.getProxyPassword());
- return response.request().newBuilder().header("Authorization", credential).build();
+ return response.request().newBuilder().header("Proxy-Authorization", credential).build();
}
});
this.httpClient = clientBuilder.build();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
index af6a7e1408..511c440e64 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
@@ -86,12 +86,12 @@ public void initHttp() {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.proxy(getRequestHttpProxy().getProxy());
//设置授权
- clientBuilder.authenticator(new Authenticator() {
+ clientBuilder.proxyAuthenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
return response.request().newBuilder()
- .header("Authorization", credential)
+ .header("Proxy-Authorization", credential)
.build();
}
});
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
index 63d5e95bae..c4dab9cf20 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
@@ -108,12 +108,12 @@ public void initHttp() {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.proxy(getRequestHttpProxy().getProxy());
//设置授权
- clientBuilder.authenticator(new Authenticator() {
+ clientBuilder.proxyAuthenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
return response.request().newBuilder()
- .header("Authorization", credential)
+ .header("Proxy-Authorization", credential)
.build();
}
});
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
index 8811028fee..096af76caa 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
@@ -32,12 +32,12 @@ public void initHttp() {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.proxy(getRequestHttpProxy().getProxy());
//设置授权
- clientBuilder.authenticator(new Authenticator() {
+ clientBuilder.proxyAuthenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
return response.request().newBuilder()
- .header("Authorization", credential)
+ .header("Proxy-Authorization", credential)
.build();
}
});
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java
index 5ff6734ccb..8c2d69896e 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java
@@ -80,11 +80,11 @@ public void initHttp() {
clientBuilder.proxy(getRequestHttpProxy().getProxy());
// 设置授权
- clientBuilder.authenticator(new Authenticator() {
+ clientBuilder.proxyAuthenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
- return response.request().newBuilder().header("Authorization", credential).build();
+ return response.request().newBuilder().header("Proxy-Authorization", credential).build();
}
});
httpClient = clientBuilder.build();
From 3bf3595dc1518bf20f0d7889a8e84d47187426e0 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 2 Dec 2025 12:03:03 +0800
Subject: [PATCH 051/111] =?UTF-8?q?:art:=20=E4=BF=AE=E5=A4=8D=20WxCpApprov?=
=?UTF-8?q?alWorkflowDemo.java=20=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7?=
=?UTF-8?q?=E5=92=8C=E7=BC=96=E8=AF=91=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/cp/demo/WxCpApprovalWorkflowDemo.java | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
index 66c303a79d..07aed001de 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
@@ -4,6 +4,7 @@
import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult;
import me.chanjar.weixin.cp.bean.oa.WxCpApprovalInfo;
import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest;
+import me.chanjar.weixin.cp.bean.oa.WxCpOaApprovalTemplateResult;
import me.chanjar.weixin.cp.bean.oa.applydata.ApplyDataContent;
import me.chanjar.weixin.cp.bean.oa.applydata.ContentValue;
@@ -86,14 +87,14 @@ public void getApprovalDetails(String spNo) throws Exception {
System.out.println("审批单号: " + detail.getSpNo());
System.out.println("审批名称: " + detail.getSpName());
- System.out.println("审批状态: " + detail.getSpStatus().getCode());
- System.out.println("申请人: " + detail.getApplyer().getUserId());
+ System.out.println("审批状态: " + detail.getSpStatus());
+ System.out.println("申请人: " + detail.getApplier().getUserId());
System.out.println("申请时间: " + detail.getApplyTime());
// 打印审批记录
- if (detail.getSpRecord() != null) {
- detail.getSpRecord().forEach(record -> {
- System.out.println("审批节点状态: " + record.getSpStatus());
+ if (detail.getSpRecords() != null) {
+ Arrays.stream(detail.getSpRecords()).forEach(record -> {
+ System.out.println("审批节点状态: " + record.getStatus());
System.out.println("审批人: " + record.getDetails());
});
}
@@ -112,7 +113,7 @@ public void batchGetApprovalInfo() throws Exception {
WxCpApprovalInfo approvalInfo = wxCpService.getOaService()
.getApprovalInfo(startTime, endTime, "0", 100, null);
- System.out.println("获取到的审批单数量: " + approvalInfo.getCount());
+ System.out.println("获取到的审批单数量: " + (approvalInfo.getSpNoList() != null ? approvalInfo.getSpNoList().size() : 0));
// 遍历审批单号
if (approvalInfo.getSpNoList() != null) {
@@ -130,7 +131,7 @@ public void batchGetApprovalInfo() throws Exception {
public void templateManagement() throws Exception {
// 获取模板详情
String templateId = "3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS";
- var templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
+ WxCpOaApprovalTemplateResult templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
System.out.println("模板名称: " + templateResult.getTemplateNames());
System.out.println("模板内容: " + templateResult.getTemplateContent());
From f7a196c1290fdaae8f32081018eb6c24f86e8034 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 2 Dec 2025 12:08:01 +0800
Subject: [PATCH 052/111] =?UTF-8?q?:art:=20=E4=BF=AE=E5=A4=8D=E6=94=AF?=
=?UTF-8?q?=E4=BB=98=E5=85=AC=E9=92=A5=E6=A8=A1=E5=BC=8F=E4=B8=8B=E5=9B=9E?=
=?UTF-8?q?=E8=B0=83=E9=AA=8C=E8=AF=81serialNumber=E7=A9=BA=E6=8C=87?=
=?UTF-8?q?=E9=92=88=E5=BC=82=E5=B8=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/v3/auth/PublicCertificateVerifier.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
index ac1dfbca6b..62ad61ce19 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
@@ -24,8 +24,8 @@ public void setOtherVerifier(Verifier verifier) {
@Override
public boolean verify(String serialNumber, byte[] message, String signature) {
- // 如果序列号不包含"PUB_KEY_ID"且有证书验证器,先尝试证书验证
- if (!serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) {
+ // 如果序列号不为空且不包含"PUB_KEY_ID"且有证书验证器,先尝试证书验证
+ if (serialNumber != null && !serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) {
try {
if (this.certificateVerifier.verify(serialNumber, message, signature)) {
return true;
From 524762704c2ba1436959c04ea908b3bf03e90b69 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:25:53 +0800
Subject: [PATCH 053/111] =?UTF-8?q?:new:=20#3524=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E6=B7=BB=E5=8A=A0=E5=A4=9A=E7=AB=AF?=
=?UTF-8?q?=E7=99=BB=E5=BD=95=20code2VerifyInfo=20=E6=8E=A5=E5=8F=A3?=
=?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/api/WxMaUserService.java | 15 +++++++
.../miniapp/api/impl/WxMaUserServiceImpl.java | 11 +++++
.../bean/WxMaCode2VerifyInfoResult.java | 44 +++++++++++++++++++
.../miniapp/constant/WxMaApiUrlConstants.java | 2 +
4 files changed, 72 insertions(+)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaCode2VerifyInfoResult.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java
index 8c6a8ef871..21696701d2 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java
@@ -1,5 +1,6 @@
package cn.binarywang.wx.miniapp.api;
+import cn.binarywang.wx.miniapp.bean.WxMaCode2VerifyInfoResult;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
@@ -87,4 +88,18 @@ public interface WxMaUserService {
* @return .
*/
boolean checkUserInfo(String sessionKey, String rawData, String signature);
+
+ /**
+ * 多端登录验证接口.
+ *
+ * 通过 code 换取用户登录态信息,用于多端登录场景(如手表端)。
+ *
+ * 文档地址:多端登录
+ *
+ * @param code 登录时获取的 code
+ * @param checkcode 手表授权页面返回的 checkcode
+ * @return 登录验证结果,包含 session_key、openid、unionid 和 is_limit 字段
+ * @throws WxErrorException 调用微信接口失败时抛出
+ */
+ WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException;
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
index c9f5c2e335..5c850ee8a2 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
@@ -2,6 +2,7 @@
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
+import cn.binarywang.wx.miniapp.bean.WxMaCode2VerifyInfoResult;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
@@ -18,6 +19,7 @@
import java.util.Map;
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.CODE_2_VERIFY_INFO_URL;
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.GET_PHONE_NUMBER_URL;
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.SET_USER_STORAGE;
@@ -86,4 +88,13 @@ public boolean checkUserInfo(String sessionKey, String rawData, String signature
return generatedSignature.equals(signature);
}
+ @Override
+ public WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException {
+ JsonObject param = new JsonObject();
+ param.addProperty("code", code);
+ param.addProperty("checkcode", checkcode);
+ String responseContent = this.service.post(CODE_2_VERIFY_INFO_URL, param.toString());
+ return WxMaCode2VerifyInfoResult.fromJson(responseContent);
+ }
+
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaCode2VerifyInfoResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaCode2VerifyInfoResult.java
new file mode 100644
index 0000000000..b36a3a9d86
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaCode2VerifyInfoResult.java
@@ -0,0 +1,44 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 多端登录验证接口的响应
+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/miniapp/openapi/code2Verifyinfo.html
+ *
+ * 微信返回报文:{"errcode": 0, "errmsg": "ok", "session_key":"xxx", "openid":"xxx", "unionid":"xxx", "is_limit": false}
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class WxMaCode2VerifyInfoResult implements Serializable {
+ private static final long serialVersionUID = -2468325025088437364L;
+
+ @SerializedName("session_key")
+ private String sessionKey;
+
+ @SerializedName("openid")
+ private String openid;
+
+ @SerializedName("unionid")
+ private String unionid;
+
+ /**
+ * 是否为受限用户
+ */
+ @SerializedName("is_limit")
+ private Boolean isLimit;
+
+ public static WxMaCode2VerifyInfoResult fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaCode2VerifyInfoResult.class);
+ }
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
index ea9b123fb9..45e1219659 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
@@ -364,6 +364,8 @@ public interface User {
String SET_USER_STORAGE =
"https://api.weixin.qq.com/wxa/set_user_storage?appid=%s&signature=%s&openid=%s&sig_method=%s";
String GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
+ /** 多端登录验证接口 */
+ String CODE_2_VERIFY_INFO_URL = "https://api.weixin.qq.com/wxa/sec/checkcode2verifyinfo";
}
public interface Ocr {
From 085125960b78c8ea14a909788b550dba6b903df6 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:27:24 +0800
Subject: [PATCH 054/111] =?UTF-8?q?:art:=20#3608=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=20fullPu?=
=?UTF-8?q?blicKeyModel=20=E9=85=8D=E7=BD=AE=E5=9C=A8=20Spring=20Boot=20St?=
=?UTF-8?q?arter=20=E5=92=8C=20Solon=20=E6=8F=92=E4=BB=B6=E4=B8=AD?=
=?UTF-8?q?=E6=97=A0=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../pay/config/WxPayAutoConfiguration.java | 7 ++++
.../pay/properties/WxPayProperties.java | 36 +++++++++++++++++++
.../pay/config/WxPayAutoConfiguration.java | 2 ++
.../pay/properties/WxPayProperties.java | 10 ++++++
4 files changed, 55 insertions(+)
diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java
index 1957e4157e..94112c7d9d 100644
--- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java
+++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java
@@ -46,13 +46,20 @@ public WxPayService wxPayService() {
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv());
+ payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
//以下是apiv3以及支付分相关
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
+ payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(this.properties.getPayScorePermissionNotifyUrl()));
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
+ payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
+ payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
+ payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
+ payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
+ payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
wxPayService.setConfig(payConfig);
return wxPayService;
diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java
index a882f6ac31..0b035e983e 100644
--- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java
+++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java
@@ -82,4 +82,40 @@ public class WxPayProperties {
*/
private boolean useSandboxEnv;
+ /**
+ * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数
+ */
+ private String notifyUrl;
+
+ /**
+ * 微信支付分授权回调地址
+ */
+ private String payScorePermissionNotifyUrl;
+
+ /**
+ * 公钥ID
+ */
+ private String publicKeyId;
+
+ /**
+ * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+ */
+ private String publicKeyPath;
+
+ /**
+ * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com
+ * 例如:http://proxy.company.com:8080
+ */
+ private String apiHostUrl;
+
+ /**
+ * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加
+ */
+ private boolean strictlyNeedWechatPaySerial = false;
+
+ /**
+ * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用
+ */
+ private boolean fullPublicKeyModel = false;
+
}
diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
index 451cfbe4d0..5a794de7e8 100644
--- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
+++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
@@ -62,6 +62,8 @@ public WxPayService wxPayService() {
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
+ payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
+ payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
wxPayService.setConfig(payConfig);
return wxPayService;
diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
index 143b7deefa..8212e3b013 100644
--- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
+++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
@@ -106,4 +106,14 @@ public class WxPayProperties {
*/
private String apiHostUrl;
+ /**
+ * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加
+ */
+ private boolean strictlyNeedWechatPaySerial = false;
+
+ /**
+ * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用
+ */
+ private boolean fullPublicKeyModel = false;
+
}
From cd4317ab3e78e3ba1233c6f3d2610afa07538270 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 4 Dec 2025 19:38:10 +0800
Subject: [PATCH 055/111] =?UTF-8?q?:art:=20#3384=20=E3=80=90=E5=85=AC?=
=?UTF-8?q?=E4=BC=97=E5=8F=B7=E3=80=91=E4=B8=BA=20starter=20=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=20HttpComponents=20(httpclient5)=20=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx-java-mp-multi-spring-boot-starter/pom.xml | 5 +++++
.../wx-java-mp-spring-boot-starter/pom.xml | 5 +++++
.../wxjava/mp/config/WxMpServiceAutoConfiguration.java | 8 ++++++++
.../spring/starter/wxjava/mp/enums/HttpClientType.java | 4 ++++
4 files changed, 22 insertions(+)
diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
index fe10f8332f..1ded1a4e16 100644
--- a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
@@ -44,6 +44,11 @@
okhttp
provided
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ provided
+
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
index 06dfe0d511..2b83d966ac 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
@@ -39,6 +39,11 @@
okhttp
provided
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ provided
+
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java
index 3b8733c286..dc6dcafb82 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java
@@ -4,6 +4,7 @@
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
@@ -35,6 +36,9 @@ public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties w
case HttpClient:
wxMpService = newWxMpServiceHttpClientImpl();
break;
+ case HttpComponents:
+ wxMpService = newWxMpServiceHttpComponentsImpl();
+ break;
default:
wxMpService = newWxMpServiceImpl();
break;
@@ -60,4 +64,8 @@ private WxMpService newWxMpServiceJoddHttpImpl() {
return new WxMpServiceJoddHttpImpl();
}
+ private WxMpService newWxMpServiceHttpComponentsImpl() {
+ return new WxMpServiceHttpComponentsImpl();
+ }
+
}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java
index f67ef97c2e..0bf034417f 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents (Apache HttpClient 5.x).
+ */
+ HttpComponents,
}
From 219a8f4f3657cba0ebcb524e0f3fb444c9668e3e Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 4 Dec 2025 19:40:03 +0800
Subject: [PATCH 056/111] =?UTF-8?q?:art:=20#3620=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E4=BF=AE=E5=A4=8D=E5=90=8C=E5=9F=8E?=
=?UTF-8?q?=E9=85=8D=E9=80=81API=E7=AD=BE=E5=90=8D=E9=94=99=E8=AF=AF?=
=?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=88=E6=B7=BB=E5=8A=A0RSA=E7=A7=81?=
=?UTF-8?q?=E9=92=A5=E5=BA=8F=E5=88=97=E5=8F=B7=E5=88=B0=E7=AD=BE=E5=90=8D?=
=?UTF-8?q?payload=E5=92=8C=E8=AF=B7=E6=B1=82=E5=A4=B4=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/api/impl/BaseWxMaServiceImpl.java | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
index a5d479b65a..93bb2656e6 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -912,6 +912,10 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro
String rndStr = UUID.randomUUID().toString().replace("-", "").substring(0, 30);
String aesKey = this.getWxMaConfig().getApiSignatureAesKey();
String aesKeySn = this.getWxMaConfig().getApiSignatureAesKeySn();
+ String rsaKeySn = this.getWxMaConfig().getApiSignatureRsaPrivateKeySn();
+ if (rsaKeySn == null || rsaKeySn.isEmpty()) {
+ throw new SecurityException("ApiSignatureRsaPrivateKeySn不能为空,请检查配置");
+ }
jsonObject.addProperty("_n", rndStr);
jsonObject.addProperty("_appid", appId);
@@ -956,7 +960,7 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro
String requestJson = reqData.toString();
// 计算签名 RSA
- String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson;
+ String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + rsaKeySn + "\n" + requestJson;
byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8);
RSAPrivateKey priKey;
try {
@@ -985,6 +989,7 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro
header.put("Wechatmp-Signature", signatureString);
header.put("Wechatmp-Appid", appId);
header.put("Wechatmp-TimeStamp", String.valueOf(timestamp));
+ header.put("Wechatmp-Serial", rsaKeySn);
log.debug("发送请求uri:{}, headers:{}, postData:{}", url, header, requestJson);
WxMaApiResponse response =
this.execute(ApiSignaturePostRequestExecutor.create(this), url, header, requestJson);
From 4e46486f2c13f536603eaa1bd403e67173f03081 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 4 Dec 2025 19:43:15 +0800
Subject: [PATCH 057/111] =?UTF-8?q?:art:=20#3598=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=20=E4=BC=9A=E8=AF=9D?=
=?UTF-8?q?=E5=AD=98=E6=A1=A3=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E5=AF=B9?=
=?UTF-8?q?=E9=9F=B3=E8=A7=86=E9=A2=91=E9=80=9A=E8=AF=9D(voiptext)?=
=?UTF-8?q?=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/bean/msgaudit/WxCpChatModel.java | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java
index c88cb7b9be..21a29abf8f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java
@@ -202,6 +202,12 @@ public class WxCpChatModel implements Serializable {
@SerializedName("sphfeed")
private SphFeed sphFeed;
+ /**
+ * 音视频通话消息
+ */
+ @SerializedName("voiptext")
+ private VoipText voipText;
+
/**
* From json wx cp chat model.
*
@@ -1333,4 +1339,40 @@ public String toJson() {
}
+ /**
+ * 音视频通话消息
+ */
+ @Getter
+ @Setter
+ public static class VoipText implements Serializable {
+ private static final long serialVersionUID = -5028321625140879571L;
+
+ @SerializedName("callduration")
+ private Integer callDuration;
+
+ @SerializedName("invitetype")
+ private Integer inviteType;
+
+ /**
+ * From json voip text.
+ *
+ * @param json the json
+ * @return the voip text
+ */
+ public static VoipText fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, VoipText.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ }
+
+
}
From ddcdf658cf56d6a67cecc9dfacbeca2d5c974757 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Dec 2025 16:19:06 +0800
Subject: [PATCH 058/111] =?UTF-8?q?:bug:=20#3797=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E5=95=86?=
=?UTF-8?q?=E5=AE=B6=E8=BD=AC=E8=B4=A6API=E8=B7=AF=E5=BE=84=E9=94=99?=
=?UTF-8?q?=E8=AF=AF=EF=BC=8C=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99=E7=9A=84?=
=?UTF-8?q?operation=E9=83=A8=E5=88=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/service/BusinessOperationTransferService.java | 8 ++++----
.../impl/BusinessOperationTransferServiceImpl.java | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java
index 740c2af83f..195d3a8409 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java
@@ -21,7 +21,7 @@ public interface BusinessOperationTransferService {
* 发起运营工具商家转账
*
* 请求方式:POST(HTTPS)
- * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills
*
* 文档地址:运营工具-商家转账API
*
@@ -37,7 +37,7 @@ public interface BusinessOperationTransferService {
* 查询运营工具转账结果
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
*
* 文档地址:运营工具-商家转账API
*
@@ -53,7 +53,7 @@ public interface BusinessOperationTransferService {
* 通过商户单号查询运营工具转账结果
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
*
* 文档地址:运营工具-商家转账API
*
@@ -69,7 +69,7 @@ public interface BusinessOperationTransferService {
* 通过微信转账单号查询运营工具转账结果
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}
+ * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}
*
* 文档地址:运营工具-商家转账API
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java
index 5e74bdbaab..098db127e3 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java
@@ -33,7 +33,7 @@ public BusinessOperationTransferResult createOperationTransfer(BusinessOperation
request.setAppid(this.wxPayService.getConfig().getAppId());
}
- String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills", this.wxPayService.getPayBaseUrl());
+ String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills", this.wxPayService.getPayBaseUrl());
// 如果传入了用户姓名,需要进行RSA加密
if (StringUtils.isNotEmpty(request.getUserName())) {
@@ -58,7 +58,7 @@ public BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOpera
@Override
public BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException {
- String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/%s",
+ String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s",
this.wxPayService.getPayBaseUrl(), outBillNo);
String response = wxPayService.getV3(url);
return GSON.fromJson(response, BusinessOperationTransferQueryResult.class);
@@ -66,7 +66,7 @@ public BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(St
@Override
public BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException {
- String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills/transfer-bill-no/%s",
+ String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/%s",
this.wxPayService.getPayBaseUrl(), transferBillNo);
String response = wxPayService.getV3(url);
return GSON.fromJson(response, BusinessOperationTransferQueryResult.class);
From 85bd27424d3eacde81ff91c5aa28828de6ad30ee Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Dec 2025 16:20:18 +0800
Subject: [PATCH 059/111] =?UTF-8?q?:art:=20#3795=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E5=AE=A2=E6=9C=8D=E6=B6=88=E6=81=AF?=
=?UTF-8?q?API=E6=96=B0=E5=A2=9E=E4=BA=86=20aimsgcontext=20=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5=EF=BC=8C=E7=94=A8=E4=BA=8EAI=E6=B6=88=E6=81=AF?=
=?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E5=85=B3=E8=81=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wx/miniapp/bean/WxMaKefuMessage.java | 13 +++++++++++++
.../binarywang/wx/miniapp/builder/BaseBuilder.java | 10 ++++++++++
.../wx/miniapp/bean/WxMaKefuMessageTest.java | 10 ++++++++++
3 files changed, 33 insertions(+)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
index 0cd77c7937..cdce681175 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
@@ -40,6 +40,9 @@ public class WxMaKefuMessage implements Serializable {
@SerializedName("miniprogrampage")
private KfMaPage maPage;
+ @SerializedName("aimsgcontext")
+ private AiMsgContext aiMsgContext;
+
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -90,6 +93,16 @@ public static class KfMaPage implements Serializable {
private String thumbMediaId;
}
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class AiMsgContext implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @SerializedName("msgid")
+ private String msgId;
+ }
+
/**
* 获得文本消息builder.
*/
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java
index c353534c3f..71f49ee2d3 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java
@@ -8,6 +8,7 @@
public class BaseBuilder+ * 从业机构调用该接口向微信医保后台下单 + * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012503131 + * @author xgl + * @date 2025/12/19 14:37 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class MedInsOrdersRequest { + + /** + *
+ * 字段名:混合支付类型 + * 变量名:mix_pay_type + * 必填:是 + * 类型:string + * 描述: + * 混合支付类型可选取值: + * - UNKNOWN_MIX_PAY_TYPE: 未知的混合支付类型,会被拦截 + * - CASH_ONLY: 只向微信支付下单,没有向医保局下单 + * - INSURANCE_ONLY: 只向医保局下单,没有向微信支付下单 + * - CASH_AND_INSURANCE: 向医保局下单,也向微信支付下单 + *+ */ + @SerializedName("mix_pay_type") + public MixPayTypeEnum mixPayType; + + /** + *
+ * 字段名:订单类型 + * 变量名:order_type + * 必填:是 + * 类型:string + * 描述: + * 订单类型可选取值: + * - UNKNOWN_ORDER_TYPE: 未知类型,会被拦截 + * - REG_PAY: 挂号支付 + * - DIAG_PAY: 诊间支付 + * - COVID_EXAM_PAY: 新冠检测费用(核酸) + * - IN_HOSP_PAY: 住院费支付 + * - PHARMACY_PAY: 药店支付 + * - INSURANCE_PAY: 保险费支付 + * - INT_REG_PAY: 互联网医院挂号支付 + * - INT_RE_DIAG_PAY: 互联网医院复诊支付 + * - INT_RX_PAY: 互联网医院处方支付 + * - COVID_ANTIGEN_PAY: 新冠抗原检测 + * - MED_PAY: 药费支付 + *+ */ + @SerializedName("order_type") + public OrderTypeEnum orderType; + + /** + *
+ * 字段名:从业机构/服务商的公众号ID + * 变量名:appid + * 必填:是 + * 类型:string(32) + * 描述:从业机构/服务商的公众号ID + *+ */ + @SerializedName("appid") + public String appid; + + /** + *
+ * 字段名:医疗机构的公众号ID + * 变量名:sub_appid + * 必填:是 + * 类型:string(32) + * 描述:医疗机构的公众号ID + *+ */ + @SerializedName("sub_appid") + public String subAppid; + + /** + *
+ * 字段名:医疗机构的商户号 + * 变量名:sub_mchid + * 必填:是 + * 类型:string(32) + * 描述:医疗机构的商户号 + *+ */ + @SerializedName("sub_mchid") + public String subMchid; + + /** + *
+ * 字段名:用户在appid下的唯一标识 + * 变量名:openid + * 必填:否 + * 类型:string(128) + * 描述:openid与sub_openid二选一,传入openid时需要使用appid调起医保自费混合支付 + *+ */ + @SerializedName("openid") + public String openid; + + /** + *
+ * 字段名:用户在sub_appid下的唯一标识 + * 变量名:sub_openid + * 必填:否 + * 类型:string(128) + * 描述:openid与sub_openid二选一,传入sub_openid时需要使用sub_appid调起医保自费混合支付 + *+ */ + @SerializedName("sub_openid") + public String subOpenid; + + /** + *
+ * 字段名:支付人身份信息 + * 变量名:payer + * 必填:是 + * 类型:object + * 描述:支付人身份信息 + *+ */ + @SerializedName("payer") + @SpecEncrypt + public PersonIdentification payer; + + /** + *
+ * 字段名:是否代亲属支付 + * 变量名:pay_for_relatives + * 必填:否 + * 类型:boolean + * 描述:不传默认替本人支付 + * - true: 代亲属支付 + * - false: 本人支付 + *+ */ + @SerializedName("pay_for_relatives") + public Boolean payForRelatives; + + /** + *
+ * 字段名:亲属身份信息 + * 变量名:relative + * 必填:否 + * 类型:object + * 描述:pay_for_relatives为true时,该字段必填 + *+ */ + @SerializedName("relative") + @SpecEncrypt + public PersonIdentification relative; + + /** + *
+ * 字段名:从业机构订单号 + * 变量名:out_trade_no + * 必填:是 + * 类型:string(64) + * 描述:从业机构/服务商需要调两次接口:从业机构/服务商向微信支付下单获取微信支付凭证,请求中会带上out_trade_no。下单成功后,从业机构/服务商调用混合下单的接口(即该接口),请求中也会带上out_trade_no。 + *+ */ + @SerializedName("out_trade_no") + public String outTradeNo; + + /** + *
+ * 字段名:医疗机构订单号 + * 变量名:serial_no + * 必填:是 + * 类型:string(40) + * 描述:例如医院HIS系统订单号。传与费用明细上传中medOrgOrd字段一样的值,局端会校验,不一致将会返回错误 + *+ */ + @SerializedName("serial_no") + public String serialNo; + + /** + *
+ * 字段名:支付订单号 + * 变量名:pay_order_id + * 必填:否 + * 类型:string + * 描述:支付订单号 + *+ */ + @SerializedName("pay_order_id") + public String payOrderId; + + /** + *
+ * 字段名:支付授权号 + * 变量名:pay_auth_no + * 必填:否 + * 类型:string + * 描述:支付授权号 + *+ */ + @SerializedName("pay_auth_no") + public String payAuthNo; + + /** + *
+ * 字段名:地理位置 + * 变量名:geo_location + * 必填:否 + * 类型:string + * 描述:地理位置 + *+ */ + @SerializedName("geo_location") + public String geoLocation; + + /** + *
+ * 字段名:城市ID + * 变量名:city_id + * 必填:否 + * 类型:string + * 描述:城市ID + *+ */ + @SerializedName("city_id") + public String cityId; + + /** + *
+ * 字段名:医疗机构名称 + * 变量名:med_inst_name + * 必填:否 + * 类型:string + * 描述:医疗机构名称 + *+ */ + @SerializedName("med_inst_name") + public String medInstName; + + /** + *
+ * 字段名:医疗机构编号 + * 变量名:med_inst_no + * 必填:否 + * 类型:string + * 描述:医疗机构编号 + *+ */ + @SerializedName("med_inst_no") + public String medInstNo; + + /** + *
+ * 字段名:医保订单创建时间 + * 变量名:med_ins_order_create_time + * 必填:否 + * 类型:string + * 描述:医保订单创建时间 + *+ */ + @SerializedName("med_ins_order_create_time") + public String medInsOrderCreateTime; + + /** + *
+ * 字段名:总金额 + * 变量名:total_fee + * 必填:否 + * 类型:long + * 描述:总金额 + *+ */ + @SerializedName("total_fee") + public Long totalFee; + + /** + *
+ * 字段名:医保统筹基金支付金额 + * 变量名:med_ins_gov_fee + * 必填:否 + * 类型:long + * 描述:医保统筹基金支付金额 + *+ */ + @SerializedName("med_ins_gov_fee") + public Long medInsGovFee; + + /** + *
+ * 字段名:医保个人账户支付金额 + * 变量名:med_ins_self_fee + * 必填:否 + * 类型:long + * 描述:医保个人账户支付金额 + *+ */ + @SerializedName("med_ins_self_fee") + public Long medInsSelfFee; + + /** + *
+ * 字段名:医保其他基金支付金额 + * 变量名:med_ins_other_fee + * 必填:否 + * 类型:long + * 描述:医保其他基金支付金额 + *+ */ + @SerializedName("med_ins_other_fee") + public Long medInsOtherFee; + + /** + *
+ * 字段名:医保现金支付金额 + * 变量名:med_ins_cash_fee + * 必填:否 + * 类型:long + * 描述:医保现金支付金额 + *+ */ + @SerializedName("med_ins_cash_fee") + public Long medInsCashFee; + + /** + *
+ * 字段名:微信支付现金支付金额 + * 变量名:wechat_pay_cash_fee + * 必填:否 + * 类型:long + * 描述:微信支付现金支付金额 + *+ */ + @SerializedName("wechat_pay_cash_fee") + public Long wechatPayCashFee; + + /** + *
+ * 字段名:现金增加明细 + * 变量名:cash_add_detail + * 必填:否 + * 类型:list + * 描述:现金增加明细 + *+ */ + @SerializedName("cash_add_detail") + public List
+ * 字段名:现金减少明细 + * 变量名:cash_reduce_detail + * 必填:否 + * 类型:list + * 描述:现金减少明细 + *+ */ + @SerializedName("cash_reduce_detail") + public List
+ * 字段名:回调URL + * 变量名:callback_url + * 必填:否 + * 类型:string + * 描述:回调URL + *+ */ + @SerializedName("callback_url") + public String callbackUrl; + + /** + *
+ * 字段名:预支付交易会话标识 + * 变量名:prepay_id + * 必填:否 + * 类型:string + * 描述:预支付交易会话标识 + *+ */ + @SerializedName("prepay_id") + public String prepayId; + + /** + *
+ * 字段名:透传请求内容 + * 变量名:passthrough_request_content + * 必填:否 + * 类型:string + * 描述:透传请求内容 + *+ */ + @SerializedName("passthrough_request_content") + public String passthroughRequestContent; + + /** + *
+ * 字段名:扩展字段 + * 变量名:extends + * 必填:否 + * 类型:string + * 描述:扩展字段 + *+ */ + @SerializedName("extends") + public String _extends; + + /** + *
+ * 字段名:附加数据 + * 变量名:attach + * 必填:否 + * 类型:string + * 描述:附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + *+ */ + @SerializedName("attach") + public String attach; + + /** + *
+ * 字段名:渠道编号 + * 变量名:channel_no + * 必填:否 + * 类型:string + * 描述:渠道编号 + *+ */ + @SerializedName("channel_no") + public String channelNo; + + /** + *
+ * 字段名:医保测试环境标识 + * 变量名:med_ins_test_env + * 必填:否 + * 类型:boolean + * 描述:医保测试环境标识 + *+ */ + @SerializedName("med_ins_test_env") + public Boolean medInsTestEnv; + + /** + *
+ * 支付人身份信息 + *+ */ + public static class PersonIdentification { + /** + *
+ * 字段名:姓名 + * 变量名:name + * 必填:是 + * 类型:string + * 描述:姓名,需加密 + *+ */ + @SerializedName("name") + @SpecEncrypt + public String name; + + /** + *
+ * 字段名:身份证摘要 + * 变量名:id_digest + * 必填:是 + * 类型:string + * 描述:身份证摘要,需加密 + *+ */ + @SerializedName("id_digest") + @SpecEncrypt + public String idDigest; + + /** + *
+ * 字段名:证件类型 + * 变量名:card_type + * 必填:是 + * 类型:string + * 描述:证件类型 + *+ */ + @SerializedName("card_type") + public UserCardTypeEnum cardType; + } + + /** + *
+ * 现金增加明细实体 + *+ */ + public static class CashAddEntity { + /** + *
+ * 字段名:现金增加金额 + * 变量名:cash_add_fee + * 必填:是 + * 类型:long + * 描述:现金增加金额 + *+ */ + @SerializedName("cash_add_fee") + public Long cashAddFee; + + /** + *
+ * 字段名:现金增加类型 + * 变量名:cash_add_type + * 必填:是 + * 类型:string + * 描述:现金增加类型 + *+ */ + @SerializedName("cash_add_type") + public CashAddTypeEnum cashAddType; + } + + /** + *
+ * 现金减少明细实体 + *+ */ + public static class CashReduceEntity { + /** + *
+ * 字段名:现金减少金额 + * 变量名:cash_reduce_fee + * 必填:是 + * 类型:long + * 描述:现金减少金额 + *+ */ + @SerializedName("cash_reduce_fee") + public Long cashReduceFee; + + /** + *
+ * 字段名:现金减少类型 + * 变量名:cash_reduce_type + * 必填:是 + * 类型:string + * 描述:现金减少类型 + *+ */ + @SerializedName("cash_reduce_type") + public CashReduceTypeEnum cashReduceType; + } + + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java new file mode 100644 index 0000000000..4fc68e279f --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java @@ -0,0 +1,497 @@ +package com.github.binarywang.wxpay.bean.mipay; + +import com.github.binarywang.wxpay.bean.mipay.enums.MedInsPayStatusEnum; +import com.github.binarywang.wxpay.bean.mipay.enums.MixPayStatusEnum; +import com.github.binarywang.wxpay.bean.mipay.enums.MixPayTypeEnum; +import com.github.binarywang.wxpay.bean.mipay.enums.OrderTypeEnum; +import com.github.binarywang.wxpay.bean.mipay.enums.SelfPayStatusEnum; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import lombok.Data; + +/** + * 医保自费混合收款下单响应 + *
+ * 从业机构调用医保自费混合收款下单接口后返回的结果 + * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012503131 + * @author xgl + * @date 2025/12/19 14:37 + */ +@Data +public class MedInsOrdersResult { + /** + *
+ * 字段名:混合交易订单号 + * 变量名:mix_trade_no + * 必填:是 + * 类型:string + * 描述:微信支付生成的混合交易订单号 + *+ */ + @SerializedName("mix_trade_no") + public String mixTradeNo; + + /** + *
+ * 字段名:混合支付状态 + * 变量名:mix_pay_status + * 必填:是 + * 类型:string + * 描述:混合支付整体状态 + *+ */ + @SerializedName("mix_pay_status") + public MixPayStatusEnum mixPayStatus; + + /** + *
+ * 字段名:自费支付状态 + * 变量名:self_pay_status + * 必填:是 + * 类型:string + * 描述:自费部分支付状态 + *+ */ + @SerializedName("self_pay_status") + public SelfPayStatusEnum selfPayStatus; + + /** + *
+ * 字段名:医保支付状态 + * 变量名:med_ins_pay_status + * 必填:是 + * 类型:string + * 描述:医保部分支付状态 + *+ */ + @SerializedName("med_ins_pay_status") + public MedInsPayStatusEnum medInsPayStatusEnum; + + /** + *
+ * 字段名:支付完成时间 + * 变量名:paid_time + * 必填:否 + * 类型:string + * 描述:支付完成时间,格式为yyyyMMddHHmmss + *+ */ + @SerializedName("paid_time") + public String paidTime; + + /** + *
+ * 字段名:透传响应内容 + * 变量名:passthrough_response_content + * 必填:否 + * 类型:string + * 描述:透传响应内容 + *+ */ + @SerializedName("passthrough_response_content") + public String passthroughResponseContent; + + /** + *
+ * 字段名:混合支付类型 + * 变量名:mix_pay_type + * 必填:是 + * 类型:string + * 描述: + * 混合支付类型可选取值: + * - UNKNOWN_MIX_PAY_TYPE: 未知的混合支付类型,会被拦截 + * - CASH_ONLY: 只向微信支付下单,没有向医保局下单 + * - INSURANCE_ONLY: 只向医保局下单,没有向微信支付下单 + * - CASH_AND_INSURANCE: 向医保局下单,也向微信支付下单 + *+ */ + @SerializedName("mix_pay_type") + public MixPayTypeEnum mixPayType; + + /** + *
+ * 字段名:订单类型 + * 变量名:order_type + * 必填:是 + * 类型:string + * 描述: + * 订单类型可选取值: + * - UNKNOWN_ORDER_TYPE: 未知类型,会被拦截 + * - REG_PAY: 挂号支付 + * - DIAG_PAY: 诊间支付 + * - COVID_EXAM_PAY: 新冠检测费用(核酸) + * - IN_HOSP_PAY: 住院费支付 + * - PHARMACY_PAY: 药店支付 + * - INSURANCE_PAY: 保险费支付 + * - INT_REG_PAY: 互联网医院挂号支付 + * - INT_RE_DIAG_PAY: 互联网医院复诊支付 + * - INT_RX_PAY: 互联网医院处方支付 + * - COVID_ANTIGEN_PAY: 新冠抗原检测 + * - MED_PAY: 药费支付 + *+ */ + @SerializedName("order_type") + public OrderTypeEnum orderType; + + /** + *
+ * 字段名:从业机构/服务商的公众号ID + * 变量名:appid + * 必填:是 + * 类型:string(32) + * 描述:从业机构/服务商的公众号ID + *+ */ + @SerializedName("appid") + public String appid; + + /** + *
+ * 字段名:医疗机构的公众号ID + * 变量名:sub_appid + * 必填:是 + * 类型:string(32) + * 描述:医疗机构的公众号ID + *+ */ + @SerializedName("sub_appid") + public String subAppid; + + /** + *
+ * 字段名:医疗机构的商户号 + * 变量名:sub_mchid + * 必填:是 + * 类型:string(32) + * 描述:医疗机构的商户号 + *+ */ + @SerializedName("sub_mchid") + public String subMchid; + + /** + *
+ * 字段名:用户在appid下的唯一标识 + * 变量名:openid + * 必填:否 + * 类型:string(128) + * 描述:openid与sub_openid二选一,传入openid时需要使用appid调起医保自费混合支付 + *+ */ + @SerializedName("openid") + public String openid; + + /** + *
+ * 字段名:用户在sub_appid下的唯一标识 + * 变量名:sub_openid + * 必填:否 + * 类型:string(128) + * 描述:openid与sub_openid二选一,传入sub_openid时需要使用sub_appid调起医保自费混合支付 + *+ */ + @SerializedName("sub_openid") + public String subOpenid; + + /** + *
+ * 字段名:是否代亲属支付 + * 变量名:pay_for_relatives + * 必填:否 + * 类型:boolean + * 描述:不传默认替本人支付 + * - true: 代亲属支付 + * - false: 本人支付 + *+ */ + @SerializedName("pay_for_relatives") + public Boolean payForRelatives; + + /** + *
+ * 字段名:从业机构订单号 + * 变量名:out_trade_no + * 必填:是 + * 类型:string(64) + * 描述:从业机构/服务商需要调两次接口:从业机构/服务商向微信支付下单获取微信支付凭证,请求中会带上out_trade_no。下单成功后,从业机构/服务商调用混合下单的接口(即该接口),请求中也会带上out_trade_no。 + *+ */ + @SerializedName("out_trade_no") + public String outTradeNo; + + /** + *
+ * 字段名:医疗机构订单号 + * 变量名:serial_no + * 必填:是 + * 类型:string(40) + * 描述:例如医院HIS系统订单号。传与费用明细上传中medOrgOrd字段一样的值,局端会校验,不一致将会返回错误 + *+ */ + @SerializedName("serial_no") + public String serialNo; + + /** + *
+ * 字段名:支付订单号 + * 变量名:pay_order_id + * 必填:否 + * 类型:string + * 描述:支付订单号 + *+ */ + @SerializedName("pay_order_id") + public String payOrderId; + + /** + *
+ * 字段名:支付授权号 + * 变量名:pay_auth_no + * 必填:否 + * 类型:string + * 描述:支付授权号 + *+ */ + @SerializedName("pay_auth_no") + public String payAuthNo; + + /** + *
+ * 字段名:地理位置 + * 变量名:geo_location + * 必填:否 + * 类型:string + * 描述:地理位置 + *+ */ + @SerializedName("geo_location") + public String geoLocation; + + /** + *
+ * 字段名:城市ID + * 变量名:city_id + * 必填:否 + * 类型:string + * 描述:城市ID + *+ */ + @SerializedName("city_id") + public String cityId; + + /** + *
+ * 字段名:医疗机构名称 + * 变量名:med_inst_name + * 必填:否 + * 类型:string + * 描述:医疗机构名称 + *+ */ + @SerializedName("med_inst_name") + public String medInstName; + + /** + *
+ * 字段名:医疗机构编号 + * 变量名:med_inst_no + * 必填:否 + * 类型:string + * 描述:医疗机构编号 + *+ */ + @SerializedName("med_inst_no") + public String medInstNo; + + /** + *
+ * 字段名:医保订单创建时间 + * 变量名:med_ins_order_create_time + * 必填:否 + * 类型:string + * 描述:医保订单创建时间 + *+ */ + @SerializedName("med_ins_order_create_time") + public String medInsOrderCreateTime; + + /** + *
+ * 字段名:总金额 + * 变量名:total_fee + * 必填:否 + * 类型:long + * 描述:总金额 + *+ */ + @SerializedName("total_fee") + public Long totalFee; + + /** + *
+ * 字段名:医保统筹基金支付金额 + * 变量名:med_ins_gov_fee + * 必填:否 + * 类型:long + * 描述:医保统筹基金支付金额 + *+ */ + @SerializedName("med_ins_gov_fee") + public Long medInsGovFee; + + /** + *
+ * 字段名:医保个人账户支付金额 + * 变量名:med_ins_self_fee + * 必填:否 + * 类型:long + * 描述:医保个人账户支付金额 + *+ */ + @SerializedName("med_ins_self_fee") + public Long medInsSelfFee; + + /** + *
+ * 字段名:医保其他基金支付金额 + * 变量名:med_ins_other_fee + * 必填:否 + * 类型:long + * 描述:医保其他基金支付金额 + *+ */ + @SerializedName("med_ins_other_fee") + public Long medInsOtherFee; + + /** + *
+ * 字段名:医保现金支付金额 + * 变量名:med_ins_cash_fee + * 必填:否 + * 类型:long + * 描述:医保现金支付金额 + *+ */ + @SerializedName("med_ins_cash_fee") + public Long medInsCashFee; + + /** + *
+ * 字段名:微信支付现金支付金额 + * 变量名:wechat_pay_cash_fee + * 必填:否 + * 类型:long + * 描述:微信支付现金支付金额 + *+ */ + @SerializedName("wechat_pay_cash_fee") + public Long wechatPayCashFee; + + /** + *
+ * 字段名:现金增加明细 + * 变量名:cash_add_detail + * 必填:否 + * 类型:list + * 描述:现金增加明细 + *+ */ + @SerializedName("cash_add_detail") + public List
+ * 字段名:现金减少明细 + * 变量名:cash_reduce_detail + * 必填:否 + * 类型:list + * 描述:现金减少明细 + *+ */ + @SerializedName("cash_reduce_detail") + public List
+ * 字段名:回调URL + * 变量名:callback_url + * 必填:否 + * 类型:string + * 描述:回调URL + *+ */ + @SerializedName("callback_url") + public String callbackUrl; + + /** + *
+ * 字段名:预支付交易会话标识 + * 变量名:prepay_id + * 必填:否 + * 类型:string + * 描述:预支付交易会话标识 + *+ */ + @SerializedName("prepay_id") + public String prepayId; + + /** + *
+ * 字段名:透传请求内容 + * 变量名:passthrough_request_content + * 必填:否 + * 类型:string + * 描述:透传请求内容 + *+ */ + @SerializedName("passthrough_request_content") + public String passthroughRequestContent; + + /** + *
+ * 字段名:扩展字段 + * 变量名:extends + * 必填:否 + * 类型:string + * 描述:扩展字段 + *+ */ + @SerializedName("extends") + public String _extends; + + /** + *
+ * 字段名:附加数据 + * 变量名:attach + * 必填:否 + * 类型:string + * 描述:附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + *+ */ + @SerializedName("attach") + public String attach; + + /** + *
+ * 字段名:渠道编号 + * 变量名:channel_no + * 必填:否 + * 类型:string + * 描述:渠道编号 + *+ */ + @SerializedName("channel_no") + public String channelNo; + + /** + *
+ * 字段名:医保测试环境标识 + * 变量名:med_ins_test_env + * 必填:否 + * 类型:boolean + * 描述:医保测试环境标识 + *+ */ + @SerializedName("med_ins_test_env") + public Boolean medInsTestEnv; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java new file mode 100644 index 0000000000..b6e15a3644 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java @@ -0,0 +1,116 @@ +package com.github.binarywang.wxpay.bean.mipay; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 医保退款通知请求 + *
+ * 从业机构调用该接口向微信医保后台通知医保订单的退款成功结果 + * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012166534 + * @author xgl + * @date 2025/12/20 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class MedInsRefundNotifyRequest { + + /** + *
+ * 字段名:医保自费混合订单号 + * 必填:是 + * 类型:string(32) + * 描述:医保自费混合订单号 + *+ */ + private String mixTradeNo; + + /** + *
+ * 字段名:医疗机构的商户号 + * 变量名:sub_mchid + * 必填:是 + * 类型:string(32) + * 描述:医疗机构的商户号 + *+ */ + @SerializedName("sub_mchid") + private String subMchid; + + /** + *
+ * 字段名:医保退款的总金额 + * 变量名:med_refund_total_fee + * 必填:是 + * 类型:integer + * 描述:单位分,医保退款的总金额。 + *+ */ + @SerializedName("med_refund_total_fee") + private Integer medRefundTotalFee; + + /** + *
+ * 字段名:医保统筹退款金额 + * 变量名:med_refund_gov_fee + * 必填:是 + * 类型:integer + * 描述:单位分,医保统筹退款金额。 + *+ */ + @SerializedName("med_refund_gov_fee") + private Integer medRefundGovFee; + + /** + *
+ * 字段名:医保个账退款金额 + * 变量名:med_refund_self_fee + * 必填:是 + * 类型:integer + * 描述:单位分,医保个账退款金额。 + *+ */ + @SerializedName("med_refund_self_fee") + private Integer medRefundSelfFee; + + /** + *
+ * 字段名:医保其他退款金额 + * 变量名:med_refund_other_fee + * 必填:是 + * 类型:integer + * 描述:单位分,医保其他退款金额。 + *+ */ + @SerializedName("med_refund_other_fee") + private Integer medRefundOtherFee; + + /** + *
+ * 字段名:医保退款成功时间 + * 变量名:refund_time + * 必填:是 + * 类型:string(64) + * 描述:遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE。 + *+ */ + @SerializedName("refund_time") + private String refundTime; + + /** + *
+ * 字段名:从业机构\服务商退款单号 + * 变量名:out_refund_no + * 必填:是 + * 类型:string(64) + * 描述:有自费单时,从业机构\服务商应填与自费退款申请处一致的out_refund_no。否则从业机构透传医疗机构退款单号即可。 + *+ */ + @SerializedName("out_refund_no") + private String outRefundNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/CashAddTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/CashAddTypeEnum.java new file mode 100644 index 0000000000..b935f20410 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/CashAddTypeEnum.java @@ -0,0 +1,29 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 现金增加类型枚举 + *
+ * 描述医保自费混合支付中现金增加的类型 + * + * @author xgl + * @date 2025/12/20 + */ +public enum CashAddTypeEnum { + /** + * 默认增加类型 + */ + @SerializedName("DEFAULT_ADD_TYPE") + DEFAULT_ADD_TYPE, + /** + * 运费 + */ + @SerializedName("FREIGHT") + FREIGHT, + /** + * 其他医疗费用 + */ + @SerializedName("OTHER_MEDICAL_EXPENSES") + OTHER_MEDICAL_EXPENSES +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/CashReduceTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/CashReduceTypeEnum.java new file mode 100644 index 0000000000..4f90b8500a --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/CashReduceTypeEnum.java @@ -0,0 +1,44 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 现金减少类型枚举 + *
+ * 描述医保自费混合支付中现金减少的类型 + * + * @author xgl + * @date 2025/12/20 + */ +public enum CashReduceTypeEnum { + /** + * 默认减少类型 + */ + @SerializedName("DEFAULT_REDUCE_TYPE") + DEFAULT_REDUCE_TYPE, + /** + * 医院减免 + */ + @SerializedName("HOSPITAL_REDUCE") + HOSPITAL_REDUCE, + /** + * 药店折扣 + */ + @SerializedName("PHARMACY_DISCOUNT") + PHARMACY_DISCOUNT, + /** + * 折扣优惠 + */ + @SerializedName("DISCOUNT") + DISCOUNT, + /** + * 预付费抵扣 + */ + @SerializedName("PRE_PAYMENT") + PRE_PAYMENT, + /** + * 押金扣除 + */ + @SerializedName("DEPOSIT_DEDUCTION") + DEPOSIT_DEDUCTION +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MedInsPayStatusEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MedInsPayStatusEnum.java new file mode 100644 index 0000000000..324530f0ff --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MedInsPayStatusEnum.java @@ -0,0 +1,44 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 医保支付状态枚举 + *
+ * 描述医保自费混合支付中医保部分的支付状态 + * + * @author xgl + * @date 2025/12/20 + */ +public enum MedInsPayStatusEnum { + /** + * 未知的医保支付状态 + */ + @SerializedName("UNKNOWN_MED_INS_PAY_STATUS") + UNKNOWN_MED_INS_PAY_STATUS, + /** + * 医保支付已创建 + */ + @SerializedName("MED_INS_PAY_CREATED") + MED_INS_PAY_CREATED, + /** + * 医保支付成功 + */ + @SerializedName("MED_INS_PAY_SUCCESS") + MED_INS_PAY_SUCCESS, + /** + * 医保支付已退款 + */ + @SerializedName("MED_INS_PAY_REFUND") + MED_INS_PAY_REFUND, + /** + * 医保支付失败 + */ + @SerializedName("MED_INS_PAY_FAIL") + MED_INS_PAY_FAIL, + /** + * 无需医保支付 + */ + @SerializedName("NO_MED_INS_PAY") + NO_MED_INS_PAY +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MixPayStatusEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MixPayStatusEnum.java new file mode 100644 index 0000000000..7360704986 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MixPayStatusEnum.java @@ -0,0 +1,39 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 混合支付状态枚举 + *
+ * 描述医保自费混合支付的整体状态 + * + * @author xgl + * @date 2025/12/20 + */ +public enum MixPayStatusEnum { + /** + * 未知的混合支付状态 + */ + @SerializedName("UNKNOWN_MIX_PAY_STATUS") + UNKNOWN_MIX_PAY_STATUS, + /** + * 混合支付已创建 + */ + @SerializedName("MIX_PAY_CREATED") + MIX_PAY_CREATED, + /** + * 混合支付成功 + */ + @SerializedName("MIX_PAY_SUCCESS") + MIX_PAY_SUCCESS, + /** + * 混合支付已退款 + */ + @SerializedName("MIX_PAY_REFUND") + MIX_PAY_REFUND, + /** + * 混合支付失败 + */ + @SerializedName("MIX_PAY_FAIL") + MIX_PAY_FAIL +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MixPayTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MixPayTypeEnum.java new file mode 100644 index 0000000000..ad62d50a66 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/MixPayTypeEnum.java @@ -0,0 +1,41 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 混合支付类型枚举 + *
+ * 描述医保自费混合支付的类型 + * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012503131 + * + * @author xgl + * @date 2025/12/20 09:21 + */ +public enum MixPayTypeEnum { + + /** + * 未知的混合支付类型,会被拦截。 + */ + @SerializedName("UNKNOWN_MIX_PAY_TYPE") + UNKNOWN_MIX_PAY_TYPE, + + /** + * 只向微信支付下单,没有向医保局下单。包括没有向医保局上传费用明细、预结算。 + */ + @SerializedName("CASH_ONLY") + CASH_ONLY, + + /** + * 只向医保局下单,没有向微信支付下单。如果医保局分账结果中有自费部份,但由于有减免抵扣,没有向微信支付下单,也是纯医保。 + */ + @SerializedName("INSURANCE_ONLY") + INSURANCE_ONLY, + + /** + * 向医保局下单,也向微信支付下单。如果医保预结算全部需自费,也属于混合类型。 + */ + @SerializedName("CASH_AND_INSURANCE") + CASH_AND_INSURANCE + + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/OrderTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/OrderTypeEnum.java new file mode 100644 index 0000000000..749b1276e7 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/OrderTypeEnum.java @@ -0,0 +1,87 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 订单类型枚举 + *
+ * 描述医保自费混合支付的订单类型 + * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012503131 + * + * @author xgl + * @date 2025/12/20 + */ +public enum OrderTypeEnum { + + /** + * 未知类型,会被拦截 + */ + @SerializedName("UNKNOWN_ORDER_TYPE") + UNKNOWN_ORDER_TYPE, + + /** + * 挂号支付 + */ + @SerializedName("REG_PAY") + REG_PAY, + + /** + * 诊间支付 + */ + @SerializedName("DIAG_PAY") + DIAG_PAY, + + /** + * 新冠检测费用(核酸) + */ + @SerializedName("COVID_EXAM_PAY") + COVID_EXAM_PAY, + + /** + * 住院费支付 + */ + @SerializedName("IN_HOSP_PAY") + IN_HOSP_PAY, + + /** + * 药店支付 + */ + @SerializedName("PHARMACY_PAY") + PHARMACY_PAY, + + /** + * 保险费支付 + */ + @SerializedName("INSURANCE_PAY") + INSURANCE_PAY, + + /** + * 互联网医院挂号支付 + */ + @SerializedName("INT_REG_PAY") + INT_REG_PAY, + + /** + * 互联网医院复诊支付 + */ + @SerializedName("INT_RE_DIAG_PAY") + INT_RE_DIAG_PAY, + + /** + * 互联网医院处方支付 + */ + @SerializedName("INT_RX_PAY") + INT_RX_PAY, + + /** + * 新冠抗原检测 + */ + @SerializedName("COVID_ANTIGEN_PAY") + COVID_ANTIGEN_PAY, + + /** + * 药费支付 + */ + @SerializedName("MED_PAY") + MED_PAY +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/SelfPayStatusEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/SelfPayStatusEnum.java new file mode 100644 index 0000000000..a7014b9e13 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/SelfPayStatusEnum.java @@ -0,0 +1,44 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 自费支付状态枚举 + *
+ * 描述医保自费混合支付中自费部分的支付状态 + * + * @author xgl + * @date 2025/12/20 + */ +public enum SelfPayStatusEnum { + /** + * 未知的自费支付状态 + */ + @SerializedName("UNKNOWN_SELF_PAY_STATUS") + UNKNOWN_SELF_PAY_STATUS, + /** + * 自费支付已创建 + */ + @SerializedName("SELF_PAY_CREATED") + SELF_PAY_CREATED, + /** + * 自费支付成功 + */ + @SerializedName("SELF_PAY_SUCCESS") + SELF_PAY_SUCCESS, + /** + * 自费支付已退款 + */ + @SerializedName("SELF_PAY_REFUND") + SELF_PAY_REFUND, + /** + * 自费支付失败 + */ + @SerializedName("SELF_PAY_FAIL") + SELF_PAY_FAIL, + /** + * 无需自费支付 + */ + @SerializedName("NO_SELF_PAY") + NO_SELF_PAY +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/UserCardTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/UserCardTypeEnum.java new file mode 100644 index 0000000000..1bf97b7628 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/enums/UserCardTypeEnum.java @@ -0,0 +1,54 @@ +package com.github.binarywang.wxpay.bean.mipay.enums; + +import com.google.gson.annotations.SerializedName; + +/** + * 用户证件类型枚举 + *
+ * 描述医保自费混合支付中用户的证件类型 + * + * @author xgl + * @date 2025/12/20 + */ +public enum UserCardTypeEnum { + /** + * 未知的用户证件类型 + */ + @SerializedName("UNKNOWN_USER_CARD_TYPE") + UNKNOWN_USER_CARD_TYPE, + /** + * 居民身份证 + */ + @SerializedName("ID_CARD") + ID_CARD, + /** + * 户口本 + */ + @SerializedName("HOUSEHOLD_REGISTRATION") + HOUSEHOLD_REGISTRATION, + /** + * 外国护照 + */ + @SerializedName("FOREIGNER_PASSPORT") + FOREIGNER_PASSPORT, + /** + * 台湾居民来往大陆通行证 + */ + @SerializedName("MAINLAND_TRAVEL_PERMIT_FOR_TW") + MAINLAND_TRAVEL_PERMIT_FOR_TW, + /** + * 澳门居民来往大陆通行证 + */ + @SerializedName("MAINLAND_TRAVEL_PERMIT_FOR_MO") + MAINLAND_TRAVEL_PERMIT_FOR_MO, + /** + * 香港居民来往大陆通行证 + */ + @SerializedName("MAINLAND_TRAVEL_PERMIT_FOR_HK") + MAINLAND_TRAVEL_PERMIT_FOR_HK, + /** + * 外国人永久居留身份证 + */ + @SerializedName("FOREIGN_PERMANENT_RESIDENT") + FOREIGN_PERMANENT_RESIDENT +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java new file mode 100644 index 0000000000..a0641379fb --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java @@ -0,0 +1,265 @@ +package com.github.binarywang.wxpay.bean.notify; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ * 医保混合收款成功通知结果 + * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012165722 + *+ * + * @author xgl + * @date 2025/12/20 + */ +@Data +@NoArgsConstructor +public class MiPayNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result
+ * 字段名:应用ID + * 变量名:appid + * 是否必填:是 + * 类型:string(32) + * 描述: + * 从业机构/服务商的公众号ID + *+ */ + @SerializedName(value = "appid") + private String appid; + + /** + *
+ * 字段名:医疗机构的公众号ID + * 变量名:sub_appid + * 是否必填:是 + * 类型:string(32) + * 描述: + * 医疗机构的公众号ID + *+ */ + @SerializedName(value = "sub_appid") + private String subAppid; + + /** + *
+ * 字段名:医疗机构的商户号 + * 变量名:sub_mchid + * 是否必填:是 + * 类型:string(32) + * 描述: + * 医疗机构的商户号 + *+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+ * 字段名:从业机构订单号 + * 变量名:out_trade_no + * 是否必填:是 + * 类型:string(64) + * 描述: + * 从业机构/服务商订单号 + *+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+ * 字段名:医保自费混合订单号 + * 变量名:mix_trade_no + * 是否必填:是 + * 类型:string(32) + * 描述: + * 微信支付系统生成的医保自费混合订单号 + *+ */ + @SerializedName(value = "mix_trade_no") + private String mixTradeNo; + + /** + *
+ * 字段名:微信支付订单号 + * 变量名:transaction_id + * 是否必填:是 + * 类型:string(32) + * 描述: + * 微信支付系统生成的订单号 + *+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+ * 字段名:医保订单创建时间 + * 变量名:med_ins_order_create_time + * 是否必填:是 + * 类型:string(64) + * 描述: + * 医保订单创建时间,遵循rfc3339标准格式 + *+ */ + @SerializedName(value = "med_ins_order_create_time") + private String medInsOrderCreateTime; + + /** + *
+ * 字段名:医保订单完成时间 + * 变量名:med_ins_order_finish_time + * 是否必填:是 + * 类型:string(64) + * 描述: + * 医保订单完成时间,遵循rfc3339标准格式 + *+ */ + @SerializedName(value = "med_ins_order_finish_time") + private String medInsOrderFinishTime; + + /** + *
+ * 字段名:总金额 + * 变量名:total_fee + * 是否必填:否 + * 类型:long + * 描述: + * 总金额,单位为分 + *+ */ + @SerializedName(value = "total_fee") + private Long totalFee; + + /** + *
+ * 字段名:医保统筹基金支付金额 + * 变量名:med_ins_gov_fee + * 是否必填:否 + * 类型:long + * 描述: + * 医保统筹基金支付金额,单位为分 + *+ */ + @SerializedName(value = "med_ins_gov_fee") + private Long medInsGovFee; + + /** + *
+ * 字段名:医保个人账户支付金额 + * 变量名:med_ins_self_fee + * 是否必填:否 + * 类型:long + * 描述: + * 医保个人账户支付金额,单位为分 + *+ */ + @SerializedName(value = "med_ins_self_fee") + private Long medInsSelfFee; + + /** + *
+ * 字段名:医保其他基金支付金额 + * 变量名:med_ins_other_fee + * 是否必填:否 + * 类型:long + * 描述: + * 医保其他基金支付金额,单位为分 + *+ */ + @SerializedName(value = "med_ins_other_fee") + private Long medInsOtherFee; + + /** + *
+ * 字段名:医保现金支付金额 + * 变量名:med_ins_cash_fee + * 是否必填:否 + * 类型:long + * 描述: + * 医保现金支付金额,单位为分 + *+ */ + @SerializedName(value = "med_ins_cash_fee") + private Long medInsCashFee; + + /** + *
+ * 字段名:微信支付现金支付金额 + * 变量名:wechat_pay_cash_fee + * 是否必填:否 + * 类型:long + * 描述: + * 微信支付现金支付金额,单位为分 + *+ */ + @SerializedName(value = "wechat_pay_cash_fee") + private Long wechatPayCashFee; + + /** + *
+ * 字段名:附加数据 + * 变量名:attach + * 是否必填:否 + * 类型:string(128) + * 描述: + * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + *+ */ + @SerializedName(value = "attach") + private String attach; + + /** + *
+ * 字段名:支付状态 + * 变量名:trade_state + * 是否必填:是 + * 类型:string(32) + * 描述: + * 交易状态,枚举值: + * SUCCESS:支付成功 + * REFUND:转入退款 + * NOTPAY:未支付 + * CLOSED:已关闭 + * REVOKED:已撤销 + * USERPAYING:用户支付中 + * PAYERROR:支付失败 + *+ */ + @SerializedName(value = "trade_state") + private String tradeState; + + /** + *
+ * 字段名:支付状态描述 + * 变量名:trade_state_desc + * 是否必填:是 + * 类型:string(256) + * 描述: + * 交易状态描述 + *+ */ + @SerializedName(value = "trade_state_desc") + private String tradeStateDesc; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java new file mode 100644 index 0000000000..83b75ad40c --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java @@ -0,0 +1,94 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersRequest; +import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersResult; +import com.github.binarywang.wxpay.bean.mipay.MedInsRefundNotifyRequest; +import com.github.binarywang.wxpay.bean.notify.MiPayNotifyV3Result; +import com.github.binarywang.wxpay.bean.notify.SignatureHeader; +import com.github.binarywang.wxpay.exception.WxPayException; + +/** + * 医保相关接口 + * 医保相关接口 + * @author xgl + * @date 2025/12/20 + */ +public interface MiPayService { + + /** + *
+ * 医保自费混合收款下单 + * + * 从业机构调用该接口向微信医保后台下单 + * + * 文档地址:医保自费混合收款下单 + *+ * + * @param request 下单参数 + * @return ReservationTransferNotifyResult 下单结果 + * @throws WxPayException the wx pay exception + */ + MedInsOrdersResult medInsOrders(MedInsOrdersRequest request) throws WxPayException; + + /** + *
+ * 使用医保自费混合订单号查看下单结果 + * + * 从业机构使用混合下单订单号,通过该接口主动查询订单状态,完成下一步的业务逻辑。 + * + * 文档地址:使用医保自费混合订单号查看下单结果 + *+ * + * @param mixTradeNo 医保自费混合订单号 + * @param subMchid 医疗机构的商户号 + * @return MedInsOrdersResult 下单结果 + * @throws WxPayException the wx pay exception + */ + MedInsOrdersResult getMedInsOrderByMixTradeNo(String mixTradeNo, String subMchid) throws WxPayException; + + /** + *
+ * 使用从业机构订单号查看下单结果 + * + * 从业机构使用从业机构订单号、医疗机构商户号,通过该接口主动查询订单状态,完成下一步的业务逻辑。 + * + * 文档地址:使用从业机构订单号查看下单结果 + *+ * + * @param outTradeNo 从业机构订单号 + * @param subMchid 医疗机构的商户号 + * @return MedInsOrdersResult 下单结果 + * @throws WxPayException the wx pay exception + */ + MedInsOrdersResult getMedInsOrderByOutTradeNo(String outTradeNo, String subMchid) throws WxPayException; + + /** + *
+ * 解析医保混合收款成功通知 + * + * 微信支付会通过POST请求向商户设置的回调URL推送医保混合收款成功通知,商户需要接收处理该消息,并返回应答。 + * + * 文档地址:医保混合收款成功通知 + *+ * + * @param notifyData 通知数据字符串 + * @return MiPayNotifyV3Result 医保混合收款成功通知结果 + * @throws WxPayException the wx pay exception + */ + MiPayNotifyV3Result parseMiPayNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException; + + /** + *
+ * 医保退款通知 + * + * 从业机构调用该接口向微信医保后台通知医保订单的退款成功结果 + * + * 文档地址:医保退款通知 + *+ * + * @param request 医保退款通知请求参数 + * @throws WxPayException the wx pay exception + */ + void medInsRefundNotify(MedInsRefundNotifyRequest request) throws WxPayException; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 93da0d1332..cfb2479ae7 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -1706,4 +1706,12 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri * @return the partner pay score sign plan service */ PartnerPayScoreSignPlanService getPartnerPayScoreSignPlanService(); + + /** + * 获取医保支付服务类 + * + * @return the merchant transfer service + */ + MiPayService getMiPayService(); + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index ba3dc37144..f2e343df22 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -1,17 +1,21 @@ package com.github.binarywang.wxpay.service.impl; -import com.github.binarywang.utils.qrcode.QrcodeUtils; -import com.github.binarywang.wxpay.bean.WxPayApiData; +import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT; +import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType; import com.github.binarywang.wxpay.bean.coupon.*; import com.github.binarywang.wxpay.bean.notify.*; +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.service.*; +import java.util.*; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.utils.qrcode.QrcodeUtils; +import com.github.binarywang.wxpay.bean.WxPayApiData; import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; -import com.github.binarywang.wxpay.bean.request.*; -import com.github.binarywang.wxpay.bean.result.*; -import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; -import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.config.WxPayConfigHolder; @@ -19,7 +23,6 @@ import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.exception.WxSignTestException; -import com.github.binarywang.wxpay.service.*; import com.github.binarywang.wxpay.util.SignUtils; import com.github.binarywang.wxpay.util.XmlConfig; import com.github.binarywang.wxpay.util.ZipUtils; @@ -29,14 +32,6 @@ import com.google.common.collect.Maps; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxRuntimeException; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.reflect.ConstructorUtils; -import org.apache.http.entity.ContentType; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -45,12 +40,15 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; -import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.zip.ZipException; - -import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT; -import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxRuntimeException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.ConstructorUtils; +import org.apache.http.entity.ContentType; /** *
@@ -139,6 +137,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this);
+ @Getter
+ private final MiPayService miPayService = new MiPayServiceImpl(this);
+
protected Map configMap = new ConcurrentHashMap<>();
@Override
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java
new file mode 100644
index 0000000000..3063d7731e
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java
@@ -0,0 +1,68 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersRequest;
+import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersResult;
+import com.github.binarywang.wxpay.bean.mipay.MedInsRefundNotifyRequest;
+import com.github.binarywang.wxpay.bean.notify.MiPayNotifyV3Result;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.MiPayService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.security.cert.X509Certificate;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 医保相关接口
+ * 医保相关接口
+ * @author xgl
+ * @date 2025/12/20
+ */
+@RequiredArgsConstructor
+public class MiPayServiceImpl implements MiPayService {
+
+ private final WxPayService payService;
+ private static final Gson GSON = new GsonBuilder().create();
+
+
+ @Override
+ public MedInsOrdersResult medInsOrders(MedInsOrdersRequest request) throws WxPayException {
+
+ String url = String.format("%s/v3/med-ins/orders", this.payService.getPayBaseUrl());
+ X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate();
+
+ RsaCryptoUtil.encryptFields(request, validCertificate);
+
+ String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(result, MedInsOrdersResult.class);
+ }
+
+ @Override
+ public MedInsOrdersResult getMedInsOrderByMixTradeNo(String mixTradeNo, String subMchid) throws WxPayException {
+ String url = String.format("%s/v3/med-ins/orders/mix-trade-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), mixTradeNo, subMchid);
+ String result = this.payService.getV3(url);
+ return GSON.fromJson(result, MedInsOrdersResult.class);
+ }
+
+ @Override
+ public MedInsOrdersResult getMedInsOrderByOutTradeNo(String outTradeNo, String subMchid) throws WxPayException {
+ String url = String.format("%s/v3/med-ins/orders/out-trade-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), outTradeNo, subMchid);
+ String result = this.payService.getV3(url);
+ return GSON.fromJson(result, MedInsOrdersResult.class);
+ }
+
+ @Override
+ public MiPayNotifyV3Result parseMiPayNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
+ return this.payService.baseParseOrderNotifyV3Result(notifyData, header, MiPayNotifyV3Result.class, MiPayNotifyV3Result.DecryptNotifyResult.class);
+ }
+
+ @Override
+ public void medInsRefundNotify(MedInsRefundNotifyRequest request) throws WxPayException {
+ String url = String.format("%s/v3/med-ins/refunds/notify?mix_trade_no=%s", this.payService.getPayBaseUrl(), request.getMixTradeNo());
+ this.payService.postV3(url, GSON.toJson(request));
+ }
+
+
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java
new file mode 100644
index 0000000000..23c3c56816
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java
@@ -0,0 +1,148 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersRequest;
+import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersResult;
+import com.github.binarywang.wxpay.bean.mipay.MedInsRefundNotifyRequest;
+import com.github.binarywang.wxpay.bean.notify.MiPayNotifyV3Result;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.MiPayService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+/**
+ * 医保接口测试
+ * @author xgl
+ * @date 2025/12/20 10:04
+ */
+@Slf4j
+@Test
+@Guice(modules = ApiTestModule.class)
+public class MiPayServiceImplTest {
+
+ @Inject
+ private WxPayService wxPayService;
+
+ private static final Gson GSON = new GsonBuilder().create();
+
+
+ /**
+ * 医保自费混合收款下单测试
+ * @throws WxPayException
+ */
+ @Test
+ public void medInsOrders() throws WxPayException {
+ String requestParamStr = "{\"mix_pay_type\":\"CASH_AND_INSURANCE\",\"order_type\":\"REG_PAY\",\"appid\":\"wxdace645e0bc2cXXX\",\"sub_appid\":\"wxdace645e0bc2cXXX\",\"sub_mchid\":\"1900008XXX\",\"openid\":\"o4GgauInH_RCEdvrrNGrntXDuXXX\",\"sub_openid\":\"o4GgauInH_RCEdvrrNGrntXDuXXX\",\"payer\":{\"name\":\"张三\",\"id_digest\":\"09eb26e839ff3a2e3980352ae45ef09e\",\"card_type\":\"ID_CARD\"},\"pay_for_relatives\":false,\"relative\":{\"name\":\"张三\",\"id_digest\":\"09eb26e839ff3a2e3980352ae45ef09e\",\"card_type\":\"ID_CARD\"},\"out_trade_no\":\"202204022005169952975171534816\",\"serial_no\":\"1217752501201\",\"pay_order_id\":\"ORD530100202204022006350000021\",\"pay_auth_no\":\"AUTH530100202204022006310000034\",\"geo_location\":\"102.682296,25.054260\",\"city_id\":\"530100\",\"med_inst_name\":\"北大医院\",\"med_inst_no\":\"1217752501201407033233368318\",\"med_ins_order_create_time\":\"2015-05-20T13:29:35+08:00\",\"total_fee\":202000,\"med_ins_gov_fee\":100000,\"med_ins_self_fee\":45000,\"med_ins_other_fee\":5000,\"med_ins_cash_fee\":50000,\"wechat_pay_cash_fee\":42000,\"cash_add_detail\":[{\"cash_add_fee\":2000,\"cash_add_type\":\"FREIGHT\"}],\"cash_reduce_detail\":[{\"cash_reduce_fee\":10000,\"cash_reduce_type\":\"DEFAULT_REDUCE_TYPE\"}],\"callback_url\":\"https://www.weixin.qq.com/wxpay/pay.php\",\"prepay_id\":\"wx201410272009395522657a690389285100\",\"passthrough_request_content\":\"{\\\"payAuthNo\\\":\\\"AUTH****\\\",\\\"payOrdId\\\":\\\"ORD****\\\",\\\"setlLatlnt\\\":\\\"118.096435,24.485407\\\"}\",\"extends\":\"{}\",\"attach\":\"{}\",\"channel_no\":\"AAGN9uhZc5EGyRdairKW7Qnu\",\"med_ins_test_env\":false}";
+
+ MedInsOrdersRequest request = GSON.fromJson(requestParamStr, MedInsOrdersRequest.class);
+
+ MiPayService miPayService = wxPayService.getMiPayService();
+
+ MedInsOrdersResult result = miPayService.medInsOrders(request);
+
+ log.info(result.toString());
+ }
+
+ /**
+ * 使用医保自费混合订单号查看下单结果测试
+ * @throws WxPayException
+ */
+ @Test
+ public void getMedInsOrderByMixTradeNo() throws WxPayException {
+ // 测试用的医保自费混合订单号和医疗机构商户号
+ String mixTradeNo = "202204022005169952975171534816";
+ String subMchid = "1900000109";
+
+ MiPayService miPayService = wxPayService.getMiPayService();
+
+ MedInsOrdersResult result = miPayService.getMedInsOrderByMixTradeNo(mixTradeNo, subMchid);
+
+ log.info(result.toString());
+ }
+
+ /**
+ * 使用从业机构订单号查看下单结果测试
+ * @throws WxPayException
+ */
+ @Test
+ public void getMedInsOrderByOutTradeNo() throws WxPayException {
+ // 测试用的从业机构订单号和医疗机构商户号
+ String outTradeNo = "202204022005169952975171534816";
+ String subMchid = "1900000109";
+
+ MiPayService miPayService = wxPayService.getMiPayService();
+
+ MedInsOrdersResult result = miPayService.getMedInsOrderByOutTradeNo(outTradeNo, subMchid);
+
+ log.info(result.toString());
+ }
+
+ /**
+ * 解析医保混合收款成功通知测试
+ * @throws WxPayException
+ */
+ @Test
+ public void parseMiPayNotifyV3Result() throws WxPayException {
+ // 模拟的医保混合收款成功通知数据
+ String notifyData = "{\"id\":\"EV-202401011234567890\",\"create_time\":\"2024-01-01T12:34:56+08:00\",\"event_type\":\"MEDICAL_INSURANCE.SUCCESS\",\"summary\":\"医保混合收款成功\",\"resource_type\":\"encrypt-resource\",\"resource\":{\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"encrypted_data\",\"associated_data\":\"\",\"nonce\":\"random_string\"}}";
+
+ // 模拟的签名信息
+ String signature = "test_signature";
+ String timestamp = "1234567890";
+ String nonce = "test_nonce";
+ String serial = "test_serial";
+
+ MiPayService miPayService = wxPayService.getMiPayService();
+
+ SignatureHeader header = SignatureHeader.builder()
+ .signature(signature)
+ .timeStamp(timestamp)
+ .nonce(nonce)
+ .serial(serial)
+ .build();
+
+ try {
+ // 调用解析方法,预期会失败,因为是模拟数据
+ MiPayNotifyV3Result result = miPayService.parseMiPayNotifyV3Result(notifyData, header);
+ log.info("解析结果:{}", result);
+ } catch (WxPayException e) {
+ // 预期会抛出异常,因为是模拟数据,签名验证和解密都会失败
+ log.info("预期的异常:{}", e.getMessage());
+ }
+ }
+
+ /**
+ * 医保退款通知测试
+ * @throws WxPayException
+ */
+ @Test
+ public void medInsRefundNotify() throws WxPayException {
+ // 测试用的医保自费混合订单号
+ String mixTradeNo = "202204022005169952975171534816";
+
+ // 模拟的医保退款通知请求数据
+ String requestParamStr = "{\"sub_mchid\":\"1900008XXX\",\"med_refund_total_fee\":45000,\"med_refund_gov_fee\":45000,\"med_refund_self_fee\":45000,\"med_refund_other_fee\":45000,\"refund_time\":\"2015-05-20T13:29:35+08:00\",\"out_refund_no\":\"R202204022005169952975171534816\"}";
+
+ // 解析请求参数
+ MedInsRefundNotifyRequest request = GSON.fromJson(requestParamStr, MedInsRefundNotifyRequest.class);
+ request.setMixTradeNo(mixTradeNo);
+
+ MiPayService miPayService = wxPayService.getMiPayService();
+
+ try {
+ // 调用医保退款通知方法,预期会失败,因为是模拟数据
+ miPayService.medInsRefundNotify(request);
+ log.info("医保退款通知调用成功");
+ } catch (WxPayException e) {
+ // 预期会抛出异常,因为是模拟数据
+ log.info("预期的异常:{}", e.getMessage());
+ }
+ }
+
+}
From 3b8d0a8814e6e6891f82831393ce07f60d03f2ea Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 17:23:55 +0800
Subject: [PATCH 068/111] =?UTF-8?q?:new:=20#3814=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=A2=9E=E5=8A=A0=E5=AE=9E?=
=?UTF-8?q?=E5=90=8D=E9=AA=8C=E8=AF=81=E7=9A=84=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
weixin-java-pay/REAL_NAME_USAGE.md | 143 ++++++++++++++++++
.../wxpay/bean/realname/RealNameRequest.java | 50 ++++++
.../wxpay/bean/realname/RealNameResult.java | 91 +++++++++++
.../wxpay/service/RealNameService.java | 43 ++++++
.../wxpay/service/WxPayService.java | 7 +
.../service/impl/BaseWxPayServiceImpl.java | 3 +
.../service/impl/RealNameServiceImpl.java | 41 +++++
.../service/impl/RealNameServiceImplTest.java | 54 +++++++
8 files changed, 432 insertions(+)
create mode 100644 weixin-java-pay/REAL_NAME_USAGE.md
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameRequest.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameResult.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/RealNameService.java
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImpl.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImplTest.java
diff --git a/weixin-java-pay/REAL_NAME_USAGE.md b/weixin-java-pay/REAL_NAME_USAGE.md
new file mode 100644
index 0000000000..867bca9ce2
--- /dev/null
+++ b/weixin-java-pay/REAL_NAME_USAGE.md
@@ -0,0 +1,143 @@
+# 微信支付实名验证接口使用说明
+
+## 概述
+
+微信支付实名验证接口允许商户查询用户的实名认证状态,如果用户未实名认证,接口会返回引导用户进行实名认证的URL。
+
+## 官方文档
+
+https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+
+## 接口说明
+
+### 查询用户实名认证信息
+
+- **接口地址**:`https://api.mch.weixin.qq.com/userinfo/realnameauth/query`
+- **请求方式**:POST(需要使用商户证书)
+- **请求参数**:
+ - `appid`:公众账号ID(自动填充)
+ - `mch_id`:商户号(自动填充)
+ - `openid`:用户在商户appid下的唯一标识
+ - `nonce_str`:随机字符串(自动生成)
+ - `sign`:签名(自动生成)
+
+- **返回参数**:
+ - `return_code`:返回状态码
+ - `return_msg`:返回信息
+ - `result_code`:业务结果
+ - `openid`:用户标识
+ - `is_certified`:实名认证状态(Y-已实名认证,N-未实名认证)
+ - `cert_info`:实名认证信息(加密,仅已实名时返回)
+ - `guide_url`:引导用户进行实名认证的URL(仅未实名时返回)
+
+## 使用示例
+
+### 1. 获取实名验证服务
+
+```java
+// 获取WxPayService实例
+WxPayService wxPayService = ... // 根据你的配置初始化
+
+// 获取实名验证服务
+RealNameService realNameService = wxPayService.getRealNameService();
+```
+
+### 2. 查询用户实名认证状态(完整方式)
+
+```java
+import com.github.binarywang.wxpay.bean.realname.RealNameRequest;
+import com.github.binarywang.wxpay.bean.realname.RealNameResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+try {
+ // 构建请求对象
+ RealNameRequest request = RealNameRequest.newBuilder()
+ .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") // 用户的openid
+ .build();
+
+ // 调用查询接口
+ RealNameResult result = realNameService.queryRealName(request);
+
+ // 处理返回结果
+ if ("Y".equals(result.getIsCertified())) {
+ System.out.println("用户已实名认证");
+ System.out.println("认证信息:" + result.getCertInfo());
+ } else {
+ System.out.println("用户未实名认证");
+ System.out.println("引导链接:" + result.getGuideUrl());
+ // 可以将guide_url提供给用户,引导其完成实名认证
+ }
+} catch (WxPayException e) {
+ System.err.println("查询失败:" + e.getMessage());
+}
+```
+
+### 3. 查询用户实名认证状态(简化方式)
+
+```java
+import com.github.binarywang.wxpay.bean.realname.RealNameResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+try {
+ // 直接传入openid进行查询
+ RealNameResult result = realNameService.queryRealName("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
+
+ // 处理返回结果
+ if ("Y".equals(result.getIsCertified())) {
+ System.out.println("用户已实名认证");
+ } else {
+ System.out.println("用户未实名认证,引导链接:" + result.getGuideUrl());
+ }
+} catch (WxPayException e) {
+ System.err.println("查询失败:" + e.getMessage());
+}
+```
+
+## 注意事项
+
+1. **证书要求**:本接口需要使用商户证书进行请求,请确保已正确配置商户证书。
+
+2. **OPENID获取**:openid是用户在商户appid下的唯一标识,需要通过微信公众平台或小程序获取。
+
+3. **认证信息**:`cert_info`字段返回的信息是加密的,需要使用相应的解密方法才能获取明文信息。
+
+4. **引导链接**:当用户未实名时,返回的`guide_url`可以用于引导用户完成实名认证,建议在小程序或H5页面中使用。
+
+5. **频率限制**:请注意接口调用频率限制,避免频繁查询同一用户的实名状态。
+
+## 业务场景
+
+- **转账前校验**:在进行企业付款到零钱等操作前,可以先查询用户的实名认证状态
+- **风控审核**:作为业务风控的一部分,确认用户已完成实名认证
+- **用户引导**:发现用户未实名时,引导用户完成实名认证以使用相关功能
+
+## 错误处理
+
+```java
+try {
+ RealNameResult result = realNameService.queryRealName(openid);
+ // 处理结果...
+} catch (WxPayException e) {
+ // 处理异常
+ String errorCode = e.getErrCode();
+ String errorMsg = e.getErrCodeDes();
+
+ // 根据错误码进行相应处理
+ switch (errorCode) {
+ case "SYSTEMERROR":
+ // 系统错误,建议稍后重试
+ break;
+ case "PARAM_ERROR":
+ // 参数错误,检查openid是否正确
+ break;
+ default:
+ // 其他错误
+ break;
+ }
+}
+```
+
+## 相关链接
+
+- [微信支付官方文档](https://pay.wechatpay.cn/doc/v2/merchant/4011987607)
+- [WxJava项目主页](https://github.com/binarywang/WxJava)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameRequest.java
new file mode 100644
index 0000000000..a06318d613
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameRequest.java
@@ -0,0 +1,50 @@
+package com.github.binarywang.wxpay.bean.realname;
+
+import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.*;
+import me.chanjar.weixin.common.annotation.Required;
+
+import java.util.Map;
+
+/**
+ *
+ * 微信支付实名验证请求对象.
+ * 详见文档:https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+@XStreamAlias("xml")
+public class RealNameRequest extends BaseWxPayRequest {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:是
+ * 类型:String(128)
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ * 描述:用户在商户appid下的唯一标识
+ *
+ */
+ @Required
+ @XStreamAlias("openid")
+ private String openid;
+
+ @Override
+ protected void checkConstraints() {
+ //do nothing
+ }
+
+ @Override
+ protected void storeMap(Map map) {
+ map.put("openid", openid);
+ }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameResult.java
new file mode 100644
index 0000000000..0300cd88be
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/realname/RealNameResult.java
@@ -0,0 +1,91 @@
+package com.github.binarywang.wxpay.bean.realname;
+
+import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Document;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 微信支付实名验证返回结果.
+ * 详见文档:https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@XStreamAlias("xml")
+public class RealNameResult extends BaseWxPayResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:否
+ * 类型:String(128)
+ * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+ * 描述:用户在商户appid下的唯一标识
+ *
+ */
+ @XStreamAlias("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:实名认证状态
+ * 变量名:is_certified
+ * 是否必填:是
+ * 类型:String(1)
+ * 示例值:Y
+ * 描述:Y-已实名认证 N-未实名认证
+ *
+ */
+ @XStreamAlias("is_certified")
+ private String isCertified;
+
+ /**
+ *
+ * 字段名:实名认证信息
+ * 变量名:cert_info
+ * 是否必填:否
+ * 类型:String(256)
+ * 示例值:
+ * 描述:实名认证的相关信息,如姓名等(加密)
+ *
+ */
+ @XStreamAlias("cert_info")
+ private String certInfo;
+
+ /**
+ *
+ * 字段名:引导链接
+ * 变量名:guide_url
+ * 是否必填:否
+ * 类型:String(256)
+ * 示例值:
+ * 描述:未实名时,引导用户进行实名认证的URL
+ *
+ */
+ @XStreamAlias("guide_url")
+ private String guideUrl;
+
+ /**
+ * 从XML结构中加载额外的属性
+ *
+ * @param d Document
+ */
+ @Override
+ protected void loadXml(Document d) {
+ openid = readXmlString(d, "openid");
+ isCertified = readXmlString(d, "is_certified");
+ certInfo = readXmlString(d, "cert_info");
+ guideUrl = readXmlString(d, "guide_url");
+ }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/RealNameService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/RealNameService.java
new file mode 100644
index 0000000000..d69bda7d33
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/RealNameService.java
@@ -0,0 +1,43 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.realname.RealNameRequest;
+import com.github.binarywang.wxpay.bean.realname.RealNameResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ *
+ * 微信支付实名验证相关服务类.
+ * 详见文档:https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+ *
+ *
+ * @author Binary Wang
+ */
+public interface RealNameService {
+ /**
+ *
+ * 获取用户实名认证信息API.
+ * 用于商户查询用户的实名认证状态,如果用户未实名认证,会返回引导用户实名认证的URL
+ * 文档详见:https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+ * 接口链接:https://api.mch.weixin.qq.com/userinfo/realnameauth/query
+ *
+ *
+ * @param request 请求对象
+ * @return 实名认证查询结果
+ * @throws WxPayException the wx pay exception
+ */
+ RealNameResult queryRealName(RealNameRequest request) throws WxPayException;
+
+ /**
+ *
+ * 获取用户实名认证信息API(简化方法).
+ * 用于商户查询用户的实名认证状态,如果用户未实名认证,会返回引导用户实名认证的URL
+ * 文档详见:https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+ * 接口链接:https://api.mch.weixin.qq.com/userinfo/realnameauth/query
+ *
+ *
+ * @param openid 用户openid
+ * @return 实名认证查询结果
+ * @throws WxPayException the wx pay exception
+ */
+ RealNameResult queryRealName(String openid) throws WxPayException;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index cfb2479ae7..dab89a0142 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -1707,6 +1707,13 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
*/
PartnerPayScoreSignPlanService getPartnerPayScoreSignPlanService();
+ /**
+ * 获取实名验证服务类
+ *
+ * @return the real name service
+ */
+ RealNameService getRealNameService();
+
/**
* 获取医保支付服务类
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index f2e343df22..b4c2b919a4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -137,6 +137,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this);
+ @Getter
+ private final RealNameService realNameService = new RealNameServiceImpl(this);
+
@Getter
private final MiPayService miPayService = new MiPayServiceImpl(this);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImpl.java
new file mode 100644
index 0000000000..9a1c57fe0f
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImpl.java
@@ -0,0 +1,41 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.realname.RealNameRequest;
+import com.github.binarywang.wxpay.bean.realname.RealNameResult;
+import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.RealNameService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * 微信支付实名验证相关服务实现类.
+ * 详见文档:https://pay.wechatpay.cn/doc/v2/merchant/4011987607
+ *
+ *
+ * @author Binary Wang
+ */
+@RequiredArgsConstructor
+public class RealNameServiceImpl implements RealNameService {
+ private final WxPayService payService;
+
+ @Override
+ public RealNameResult queryRealName(RealNameRequest request) throws WxPayException {
+ request.checkAndSign(this.payService.getConfig());
+ String url = this.payService.getPayBaseUrl() + "/userinfo/realnameauth/query";
+
+ String responseContent = this.payService.post(url, request.toXML(), true);
+ RealNameResult result = BaseWxPayResult.fromXML(responseContent, RealNameResult.class);
+ result.checkResult(this.payService, request.getSignType(), true);
+ return result;
+ }
+
+ @Override
+ public RealNameResult queryRealName(String openid) throws WxPayException {
+ RealNameRequest request = RealNameRequest.newBuilder()
+ .openid(openid)
+ .build();
+ return this.queryRealName(request);
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImplTest.java
new file mode 100644
index 0000000000..dda2371948
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/RealNameServiceImplTest.java
@@ -0,0 +1,54 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.realname.RealNameRequest;
+import com.github.binarywang.wxpay.bean.realname.RealNameResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * 实名验证测试类.
+ *
+ *
+ * @author Binary Wang
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+@Slf4j
+public class RealNameServiceImplTest {
+
+ @Inject
+ private WxPayService payService;
+
+ /**
+ * 测试查询用户实名认证信息.
+ *
+ * @throws WxPayException the wx pay exception
+ */
+ @Test
+ public void testQueryRealName() throws WxPayException {
+ RealNameRequest request = RealNameRequest.newBuilder()
+ .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o")
+ .build();
+
+ RealNameResult result = this.payService.getRealNameService().queryRealName(request);
+ log.info("实名认证查询结果:{}", result);
+ }
+
+ /**
+ * 测试查询用户实名认证信息(简化方法).
+ *
+ * @throws WxPayException the wx pay exception
+ */
+ @Test
+ public void testQueryRealNameSimple() throws WxPayException {
+ RealNameResult result = this.payService.getRealNameService()
+ .queryRealName("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
+ log.info("实名认证查询结果:{}", result);
+ }
+}
From 14537479c75261ffe6618380eb764f527fc2b685 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 20:07:04 +0800
Subject: [PATCH 069/111] =?UTF-8?q?:art:=20#3813=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E5=A7=94?=
=?UTF-8?q?=E6=89=98=E4=BB=A3=E6=89=A3=E5=8D=8F=E8=AE=AE=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E5=90=8D=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../binarywang/wxpay/bean/result/WxSignQueryResult.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
index 808b9d7ddf..5241597194 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
@@ -86,7 +86,7 @@ public class WxSignQueryResult extends BaseWxPayResult implements Serializable {
* 协议解约方式
* 非必传
*/
- @XStreamAlias("contract_terminated_mode")
+ @XStreamAlias("contract_termination_mode")
private Integer contractTerminatedMode;
/**
@@ -114,7 +114,7 @@ protected void loadXml(Document d) {
contractSignedTime = readXmlString(d, "contract_signed_time");
contractExpiredTime = readXmlString(d, "contrace_Expired_time");
contractTerminatedTime = readXmlString(d, "contract_terminated_time");
- contractTerminatedMode = readXmlInteger(d, "contract_terminate_mode");
+ contractTerminatedMode = readXmlInteger(d, "contract_termination_mode");
contractTerminationRemark = readXmlString(d, "contract_termination_remark");
openId = readXmlString(d, "openid");
}
From 58cdef951fff41a02ee4e72f41b5c2ce1dbda893 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 20:08:30 +0800
Subject: [PATCH 070/111] =?UTF-8?q?:new:=20#3811=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E6=B7=BB=E5=8A=A0=E5=B0=8F=E6=B8=B8?=
=?UTF-8?q?=E6=88=8F=E9=81=93=E5=85=B7=E7=9B=B4=E8=B4=AD=EF=BC=88present?=
=?UTF-8?q?=5Fgoods=EF=BC=89API=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/api/WxMaXPayService.java | 10 +++
.../miniapp/api/impl/WxMaXPayServiceImpl.java | 16 +++++
.../xpay/WxMaXPayPresentGoodsRequest.java | 63 +++++++++++++++++++
.../xpay/WxMaXPayPresentGoodsResponse.java | 34 ++++++++++
.../miniapp/constant/WxMaApiUrlConstants.java | 1 +
5 files changed, 124 insertions(+)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsResponse.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java
index a633c93de6..68d4dc0c97 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java
@@ -71,6 +71,16 @@ public interface WxMaXPayService {
*/
WxMaXPayPresentCurrencyResponse presentCurrency(WxMaXPayPresentCurrencyRequest request, WxMaXPaySigParams sigParams) throws WxErrorException;
+ /**
+ * 道具直购。
+ *
+ * @param request 道具直购请求对象
+ * @param sigParams 签名参数对象
+ * @return 道具直购结果
+ * @throws WxErrorException 直购失败时抛出
+ */
+ WxMaXPayPresentGoodsResponse presentGoods(WxMaXPayPresentGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException;
+
/**
* 下载对账单。
*
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java
index 29a7c51a2c..37cf5e0d4e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java
@@ -108,6 +108,22 @@ public WxMaXPayPresentCurrencyResponse presentCurrency(WxMaXPayPresentCurrencyRe
return getDetailResponse;
}
+ @Override
+ public WxMaXPayPresentGoodsResponse presentGoods(WxMaXPayPresentGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+ final String postBody = request.toJson();
+ final String uri = sigParams.signUriWithPay(PRESENT_GOODS_URL, postBody);
+ String responseContent = this.service.post(uri, postBody);
+ WxMaXPayPresentGoodsResponse getDetailResponse = WxMaGsonBuilder.create()
+ .fromJson(responseContent, WxMaXPayPresentGoodsResponse.class);
+
+ if (getDetailResponse.getErrcode() != 0) {
+ throw new WxErrorException(
+ new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+ }
+
+ return getDetailResponse;
+ }
+
@Override
public WxMaXPayDownloadBillResponse downloadBill(WxMaXPayDownloadBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
final String postBody = request.toJson();
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsRequest.java
new file mode 100644
index 0000000000..ba9d7100da
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsRequest.java
@@ -0,0 +1,63 @@
+package cn.binarywang.wx.miniapp.bean.xpay;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小游戏道具直购API请求.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaXPayPresentGoodsRequest implements Serializable {
+ private static final long serialVersionUID = 7495157056049312109L;
+
+ /**
+ * 用户的openid.
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 环境。0-正式环境;1-沙箱环境.
+ */
+ @SerializedName("env")
+ private Integer env;
+
+ /**
+ * 商户订单号.
+ */
+ @SerializedName("order_id")
+ private String orderId;
+
+ /**
+ * 设备类型。0-安卓;1-iOS.
+ */
+ @SerializedName("device_type")
+ private Integer deviceType;
+
+ /**
+ * 道具id.
+ */
+ @SerializedName("goods_id")
+ private String goodsId;
+
+ /**
+ * 道具数量.
+ */
+ @SerializedName("goods_number")
+ private Integer goodsNumber;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsResponse.java
new file mode 100644
index 0000000000..ed3ea8d7f0
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayPresentGoodsResponse.java
@@ -0,0 +1,34 @@
+package cn.binarywang.wx.miniapp.bean.xpay;
+
+import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小游戏道具直购API响应.
+ *
+ * @author Binary Wang
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaXPayPresentGoodsResponse extends WxMaBaseResponse implements Serializable {
+ private static final long serialVersionUID = 7495157056049312110L;
+
+ /**
+ * 商户订单号.
+ */
+ @SerializedName("order_id")
+ private String orderId;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
index 45e1219659..3963286394 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
@@ -875,6 +875,7 @@ public interface XPay {
String NOTIFY_PROVIDE_GOODS_URL =
"https://api.weixin.qq.com/xpay/notify_provide_goods?pay_sig=%s";
String PRESENT_CURRENCY_URL = "https://api.weixin.qq.com/xpay/present_currency?pay_sig=%s";
+ String PRESENT_GOODS_URL = "https://api.weixin.qq.com/xpay/present_goods?pay_sig=%s";
String DOWNLOAD_BILL_URL = "https://api.weixin.qq.com/xpay/download_bill?pay_sig=%s";
String REFUND_ORDER_URL = "https://api.weixin.qq.com/xpay/refund_order?pay_sig=%s";
String CREATE_WITHDRAW_ORDER_URL =
From 621b8dc0e457302484054d3b1a997bc7da75b220 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 17:31:53 +0800
Subject: [PATCH 071/111] =?UTF-8?q?:new:=20#3812=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E5=AE=9E=E7=8E=B0=E7=94=A8=E5=B7=A5?=
=?UTF-8?q?=E5=85=B3=E7=B3=BBAPI=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/WxMaEmployeeRelationService.java | 41 +++++++++++++
.../wx/miniapp/api/WxMaService.java | 9 +++
.../miniapp/api/impl/BaseWxMaServiceImpl.java | 7 +++
.../impl/WxMaEmployeeRelationServiceImpl.java | 32 ++++++++++
.../employee/WxMaSendEmployeeMsgRequest.java | 61 +++++++++++++++++++
.../employee/WxMaUnbindEmployeeRequest.java | 51 ++++++++++++++++
.../miniapp/constant/WxMaApiUrlConstants.java | 8 +++
7 files changed, 209 insertions(+)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaEmployeeRelationService.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImpl.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaSendEmployeeMsgRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaEmployeeRelationService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaEmployeeRelationService.java
new file mode 100644
index 0000000000..4c0a74b2ce
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaEmployeeRelationService.java
@@ -0,0 +1,41 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.employee.WxMaSendEmployeeMsgRequest;
+import cn.binarywang.wx.miniapp.bean.employee.WxMaUnbindEmployeeRequest;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 小程序用工关系相关操作接口
+ *
+ * 文档地址:用工关系简介
+ *
+ *
+ * @author Binary Wang
+ * created on 2025-12-19
+ */
+public interface WxMaEmployeeRelationService {
+
+ /**
+ * 解绑用工关系
+ *
+ * 企业可以调用该接口解除和用户的B2C用工关系
+ *
+ * 文档地址:解绑用工关系
+ *
+ * @param request 解绑请求参数
+ * @throws WxErrorException 调用微信接口失败时抛出
+ */
+ void unbindEmployee(WxMaUnbindEmployeeRequest request) throws WxErrorException;
+
+ /**
+ * 推送用工消息
+ *
+ * 企业可以调用该接口向用户推送用工相关消息
+ *
+ * 文档地址:推送用工消息
+ *
+ * @param request 推送消息请求参数
+ * @throws WxErrorException 调用微信接口失败时抛出
+ */
+ void sendEmployeeMsg(WxMaSendEmployeeMsgRequest request) throws WxErrorException;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index 26ced8bedd..dc7425fa6e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -621,4 +621,13 @@ WxMaApiResponse execute(
* @return 交易投诉服务对象WxMaComplaintService
*/
WxMaComplaintService getComplaintService();
+
+ /**
+ * 获取用工关系服务对象。
+ *
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/laboruse/intro.html
+ *
+ * @return 用工关系服务对象WxMaEmployeeRelationService
+ */
+ WxMaEmployeeRelationService getEmployeeRelationService();
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
index 93bb2656e6..c0e1ff4a4e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -167,6 +167,8 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH
private final WxMaPromotionService wxMaPromotionService = new WxMaPromotionServiceImpl(this);
private final WxMaIntracityService intracityService = new WxMaIntracityServiceImpl(this);
private final WxMaComplaintService complaintService = new WxMaComplaintServiceImpl(this);
+ private final WxMaEmployeeRelationService employeeRelationService =
+ new WxMaEmployeeRelationServiceImpl(this);
private Map configMap = new HashMap<>();
private int retrySleepMillis = 1000;
@@ -1048,4 +1050,9 @@ public WxMaIntracityService getIntracityService() {
public WxMaComplaintService getComplaintService() {
return this.complaintService;
}
+
+ @Override
+ public WxMaEmployeeRelationService getEmployeeRelationService() {
+ return this.employeeRelationService;
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImpl.java
new file mode 100644
index 0000000000..8f240e9151
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImpl.java
@@ -0,0 +1,32 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaEmployeeRelationService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.employee.WxMaSendEmployeeMsgRequest;
+import cn.binarywang.wx.miniapp.bean.employee.WxMaUnbindEmployeeRequest;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Employee.SEND_EMPLOYEE_MSG_URL;
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Employee.UNBIND_EMPLOYEE_URL;
+
+/**
+ * 小程序用工关系相关操作接口实现
+ *
+ * @author Binary Wang
+ * created on 2025-12-19
+ */
+@RequiredArgsConstructor
+public class WxMaEmployeeRelationServiceImpl implements WxMaEmployeeRelationService {
+ private final WxMaService service;
+
+ @Override
+ public void unbindEmployee(WxMaUnbindEmployeeRequest request) throws WxErrorException {
+ this.service.post(UNBIND_EMPLOYEE_URL, request.toJson());
+ }
+
+ @Override
+ public void sendEmployeeMsg(WxMaSendEmployeeMsgRequest request) throws WxErrorException {
+ this.service.post(SEND_EMPLOYEE_MSG_URL, request.toJson());
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaSendEmployeeMsgRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaSendEmployeeMsgRequest.java
new file mode 100644
index 0000000000..2d50479817
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaSendEmployeeMsgRequest.java
@@ -0,0 +1,61 @@
+package cn.binarywang.wx.miniapp.bean.employee;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序推送用工消息请求实体
+ *
+ * 文档地址:推送用工消息
+ *
+ *
+ * @author Binary Wang
+ * created on 2025-12-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaSendEmployeeMsgRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:用户openid
+ * 是否必填:是
+ * 描述:需要接收消息的用户openid
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:企业id
+ * 是否必填:是
+ * 描述:企业id,小程序管理员在微信开放平台配置
+ *
+ */
+ @SerializedName("corp_id")
+ private String corpId;
+
+ /**
+ *
+ * 字段名:消息内容
+ * 是否必填:是
+ * 描述:推送的消息内容,文本格式,最长不超过200个字符
+ *
+ */
+ @SerializedName("msg")
+ private String msg;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java
new file mode 100644
index 0000000000..e56d84670c
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java
@@ -0,0 +1,51 @@
+package cn.binarywang.wx.miniapp.bean.employee;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序解绑用工关系请求实体
+ *
+ * 文档地址:解绑用工关系
+ *
+ *
+ * @author Binary Wang
+ * created on 2025-12-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaUnbindEmployeeRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:用户openid
+ * 是否必填:是
+ * 描述:需要解绑的用户openid
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:企业id
+ * 是否必填:是
+ * 描述:企业id,小程序管理员在微信开放平台配置
+ *
+ */
+ @SerializedName("corp_id")
+ private String corpId;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
index 3963286394..76625334f4 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
@@ -997,4 +997,12 @@ public interface Complaint {
/** 上传反馈图片 */
String UPLOAD_RESPONSE_IMAGE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/upload";
}
+
+ /** 用工关系 */
+ public interface Employee {
+ /** 解绑用工关系 */
+ String UNBIND_EMPLOYEE_URL = "https://api.weixin.qq.com/wxa/unbinduserb2cauthinfo";
+ /** 推送用工消息 */
+ String SEND_EMPLOYEE_MSG_URL = "https://api.weixin.qq.com/wxa/sendemployeerelationmsg";
+ }
}
From b0d9c6cc7679688fbdb3f29559d16755a13671f9 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 17:49:11 +0800
Subject: [PATCH 072/111] =?UTF-8?q?:new:=20#3717=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=95=86=E6=88=B7=E8=BD=AC?=
=?UTF-8?q?=E8=B4=A6=E6=96=B0=E5=A2=9E=E5=85=8D=E7=A1=AE=E8=AE=A4=E6=94=B6?=
=?UTF-8?q?=E6=AC=BE=E6=8E=88=E6=9D=83=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/NEW_TRANSFER_API_SUPPORT.md | 25 ++++
docs/NEW_TRANSFER_API_USAGE.md | 94 +++++++++++++++
.../bean/transfer/TransferBillsRequest.java | 20 ++++
.../wxpay/constant/WxPayConstants.java | 21 ++++
.../wxpay/example/NewTransferApiExample.java | 112 +++++++++++++++++-
5 files changed, 268 insertions(+), 4 deletions(-)
diff --git a/docs/NEW_TRANSFER_API_SUPPORT.md b/docs/NEW_TRANSFER_API_SUPPORT.md
index c7e9eaf490..835ff7d518 100644
--- a/docs/NEW_TRANSFER_API_SUPPORT.md
+++ b/docs/NEW_TRANSFER_API_SUPPORT.md
@@ -17,6 +17,7 @@
| **转账方式** | 批量转账 | 单笔转账 |
| **场景支持** | 基础场景 | 丰富场景(如佣金报酬等) |
| **撤销功能** | ❌ 不支持 | ✅ 支持 |
+| **授权模式** | 仅需确认模式 | ✅ 支持免确认授权模式 |
| **适用范围** | 所有商户 | **新开通商户必须使用** |
### 2. 新版API功能列表
@@ -27,6 +28,30 @@
✅ **回调通知** - `parseTransferBillsNotifyResult()`
✅ **RSA加密** - 自动处理用户姓名加密
✅ **场景支持** - 支持多种转账场景ID
+✅ **授权模式** - 支持免确认收款授权模式
+
+### 3. 收款授权模式支持
+
+**新增功能:免确认收款授权模式**
+
+- **需确认收款授权模式**(默认):用户需要手动确认才能收款
+- **免确认收款授权模式**:用户授权后,收款无需确认,转账直接到账
+
+#### 使用方法
+
+```java
+// 免确认授权模式 - 提升用户体验
+TransferBillsRequest request = TransferBillsRequest.newBuilder()
+ .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION)
+ // 其他参数...
+ .build();
+
+// 需确认授权模式(默认)
+TransferBillsRequest request2 = TransferBillsRequest.newBuilder()
+ .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION)
+ // 其他参数...
+ .build();
+```
## 快速开始
diff --git a/docs/NEW_TRANSFER_API_USAGE.md b/docs/NEW_TRANSFER_API_USAGE.md
index 9d1ac8254a..7b1a8da4ea 100644
--- a/docs/NEW_TRANSFER_API_USAGE.md
+++ b/docs/NEW_TRANSFER_API_USAGE.md
@@ -16,6 +16,100 @@
- **API前缀**: `/v3/fund-app/mch-transfer/transfer-bills`
- **特点**: 单笔转账,支持更丰富的转账场景
+## 收款授权模式功能
+
+### 授权模式说明
+
+微信支付转账支持两种收款授权模式:
+
+#### 1. 需确认收款授权模式(默认)
+- **常量**: `WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION`
+- **特点**: 用户收到转账后需要手动点击确认才能到账
+- **适用场景**: 一般的转账场景
+- **用户体验**: 安全性高,但需要额外操作
+
+#### 2. 免确认收款授权模式
+- **常量**: `WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION`
+- **特点**: 用户事先授权后,转账直接到账,无需确认
+- **适用场景**: 高频转账场景,如佣金发放、返现等
+- **用户体验**: 体验流畅,无需额外操作
+- **前提条件**: 需要用户事先进行授权
+
+### 使用示例
+
+#### 免确认授权模式转账
+
+```java
+TransferBillsRequest request = TransferBillsRequest.newBuilder()
+ .appid("your_appid")
+ .outBillNo("NO_CONFIRM_" + System.currentTimeMillis())
+ .transferSceneId("1005") // 佣金报酬场景
+ .openid("user_openid")
+ .transferAmount(200) // 2元
+ .transferRemark("免确认收款转账")
+ .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION)
+ .userRecvPerception("Y")
+ .build();
+
+try {
+ TransferBillsResult result = transferService.transferBills(request);
+ System.out.println("转账成功,直接到账:" + result.getTransferBillNo());
+} catch (WxPayException e) {
+ if ("USER_NOT_AUTHORIZED".equals(e.getErrCode())) {
+ System.err.println("用户未授权免确认收款,请先引导用户进行授权");
+ }
+}
+```
+
+#### 需确认授权模式转账(默认)
+
+```java
+TransferBillsRequest request = TransferBillsRequest.newBuilder()
+ .appid("your_appid")
+ .outBillNo("CONFIRM_" + System.currentTimeMillis())
+ .transferSceneId("1005")
+ .openid("user_openid")
+ .transferAmount(150) // 1.5元
+ .transferRemark("需确认收款转账")
+ // .receiptAuthorizationMode(...) // 不设置时使用默认的确认模式
+ .userRecvPerception("Y")
+ .build();
+
+TransferBillsResult result = transferService.transferBills(request);
+System.out.println("转账发起成功,等待用户确认:" + result.getPackageInfo());
+```
+
+### 错误处理
+
+使用免确认授权模式时,需要处理以下可能的错误:
+
+```java
+try {
+ TransferBillsResult result = transferService.transferBills(request);
+} catch (WxPayException e) {
+ switch (e.getErrCode()) {
+ case "USER_NOT_AUTHORIZED":
+ // 用户未授权免确认收款
+ System.err.println("请先引导用户进行免确认收款授权");
+ // 可以引导用户到授权页面
+ break;
+ case "AUTHORIZATION_EXPIRED":
+ // 授权已过期
+ System.err.println("用户授权已过期,请重新授权");
+ break;
+ default:
+ System.err.println("转账失败:" + e.getMessage());
+ }
+}
+```
+
+### 使用建议
+
+1. **高频转账场景**推荐使用免确认模式,提升用户体验
+2. **首次使用**需引导用户进行授权
+3. **处理异常**妥善处理授权相关异常,提供友好的错误提示
+4. **场景选择**根据业务场景选择合适的授权模式
+
## 使用新版转账API
### 1. 获取服务实例
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java
index 230e564e4b..2ac4b08c93 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java
@@ -87,6 +87,26 @@ public class TransferBillsRequest implements Serializable {
@SerializedName("transfer_scene_report_infos")
private List transferSceneReportInfos;
+ /**
+ * 收款授权模式
+ *
+ * 字段名:收款授权模式
+ * 变量名:receipt_authorization_mode
+ * 是否必填:否
+ * 类型:string
+ * 描述:
+ * 控制收款方式的授权模式,可选值:
+ * - CONFIRM_RECEIPT_AUTHORIZATION:需确认收款授权模式(默认值)
+ * - NO_CONFIRM_RECEIPT_AUTHORIZATION:免确认收款授权模式(需要用户事先授权)
+ * 为空时,默认为需确认收款授权模式
+ * 示例值:NO_CONFIRM_RECEIPT_AUTHORIZATION
+ *
+ *
+ * @see com.github.binarywang.wxpay.constant.WxPayConstants.ReceiptAuthorizationMode
+ */
+ @SerializedName("receipt_authorization_mode")
+ private String receiptAuthorizationMode;
+
@Data
@Builder(builderMethodName = "newBuilder")
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
index ec9e14ff2d..2b736691b7 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
@@ -524,4 +524,25 @@ public static class CASH_MARKETING {
}
}
+
+ /**
+ * 收款授权模式
+ *
+ * @see 官方文档
+ */
+ @UtilityClass
+ public static class ReceiptAuthorizationMode {
+ /**
+ * 需确认收款授权模式(默认值)
+ * 用户需要手动确认才能收款
+ */
+ public static final String CONFIRM_RECEIPT_AUTHORIZATION = "CONFIRM_RECEIPT_AUTHORIZATION";
+
+ /**
+ * 免确认收款授权模式
+ * 用户授权后,收款不需要确认,转账直接到账
+ */
+ public static final String NO_CONFIRM_RECEIPT_AUTHORIZATION = "NO_CONFIRM_RECEIPT_AUTHORIZATION";
+ }
+
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java
index 8d74e5a4ef..228234d589 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java
@@ -3,6 +3,7 @@
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.transfer.*;
import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.TransferService;
import com.github.binarywang.wxpay.service.WxPayService;
@@ -215,6 +216,100 @@ public void batchTransferExample() {
}
}
+ /**
+ * 使用免确认收款授权模式进行转账示例
+ * 注意:使用此模式前,用户需要先进行授权
+ */
+ public void transferWithNoConfirmAuthModeExample() {
+ try {
+ // 构建转账请求,使用免确认收款授权模式
+ TransferBillsRequest request = TransferBillsRequest.newBuilder()
+ .appid("wx1234567890123456")
+ .outBillNo("NO_CONFIRM_" + System.currentTimeMillis()) // 商户转账单号
+ .transferSceneId("1005") // 转账场景ID(佣金报酬)
+ .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") // 收款用户的openid
+ .transferAmount(200) // 转账金额,单位:分(此处为2元)
+ .transferRemark("免确认收款转账") // 转账备注
+ .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION)
+ .userRecvPerception("Y") // 用户收款感知
+ .build();
+
+ // 发起转账
+ TransferBillsResult result = transferService.transferBills(request);
+
+ System.out.println("=== 免确认授权模式转账成功 ===");
+ System.out.println("商户单号: " + result.getOutBillNo());
+ System.out.println("微信转账单号: " + result.getTransferBillNo());
+ System.out.println("状态: " + result.getState());
+ System.out.println("说明: 使用免确认授权模式,转账直接到账,无需用户确认");
+
+ } catch (WxPayException e) {
+ System.err.println("免确认授权转账失败: " + e.getMessage());
+ System.err.println("错误代码: " + e.getErrCode());
+
+ // 可能的错误原因
+ if ("USER_NOT_AUTHORIZED".equals(e.getErrCode())) {
+ System.err.println("用户未授权免确认收款,请先引导用户进行授权");
+ }
+ }
+ }
+
+ /**
+ * 使用需确认收款授权模式进行转账示例(默认模式)
+ */
+ public void transferWithConfirmAuthModeExample() {
+ try {
+ // 构建转账请求,显式设置为需确认收款授权模式
+ TransferBillsRequest request = TransferBillsRequest.newBuilder()
+ .appid("wx1234567890123456")
+ .outBillNo("CONFIRM_" + System.currentTimeMillis()) // 商户转账单号
+ .transferSceneId("1005") // 转账场景ID
+ .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") // 收款用户的openid
+ .transferAmount(150) // 转账金额,单位:分(此处为1.5元)
+ .transferRemark("需确认收款转账") // 转账备注
+ .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION)
+ .userRecvPerception("Y") // 用户收款感知
+ .build();
+
+ // 发起转账
+ TransferBillsResult result = transferService.transferBills(request);
+
+ System.out.println("=== 需确认授权模式转账成功 ===");
+ System.out.println("商户单号: " + result.getOutBillNo());
+ System.out.println("微信转账单号: " + result.getTransferBillNo());
+ System.out.println("状态: " + result.getState());
+ System.out.println("packageInfo: " + result.getPackageInfo());
+ System.out.println("说明: 使用需确认授权模式,用户需要手动确认才能收款");
+
+ } catch (WxPayException e) {
+ System.err.println("需确认授权转账失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 权限模式对比示例
+ * 展示两种权限模式的区别和使用场景
+ */
+ public void authModeComparisonExample() {
+ System.out.println("\n=== 收款授权模式对比 ===");
+ System.out.println("1. 需确认收款授权模式 (CONFIRM_RECEIPT_AUTHORIZATION):");
+ System.out.println(" - 这是默认模式");
+ System.out.println(" - 用户收到转账后需要手动点击确认才能到账");
+ System.out.println(" - 适用于一般的转账场景");
+ System.out.println(" - 转账状态可能包含 WAIT_USER_CONFIRM 等待确认状态");
+
+ System.out.println("\n2. 免确认收款授权模式 (NO_CONFIRM_RECEIPT_AUTHORIZATION):");
+ System.out.println(" - 用户事先授权后,转账直接到账,无需确认");
+ System.out.println(" - 提升用户体验,减少操作步骤");
+ System.out.println(" - 适用于高频转账场景,如佣金发放等");
+ System.out.println(" - 需要用户先进行授权,否则会返回授权错误");
+
+ System.out.println("\n使用建议:");
+ System.out.println("- 高频业务场景推荐使用免确认模式,提升用户体验");
+ System.out.println("- 首次使用需引导用户进行授权");
+ System.out.println("- 处理授权相关异常,提供友好的错误提示");
+ }
+
/**
* 使用配置示例
*/
@@ -230,20 +325,29 @@ public static void main(String[] args) {
// 创建示例实例
NewTransferApiExample example = new NewTransferApiExample(config);
+ // 权限模式对比说明
+ example.authModeComparisonExample();
+
// 运行示例
System.out.println("新版商户转账API使用示例");
System.out.println("===============================");
- // 1. 发起单笔转账
+ // 1. 发起转账(使用免确认授权模式)
+ // example.transferWithNoConfirmAuthModeExample();
+
+ // 2. 发起转账(使用需确认授权模式)
+ // example.transferWithConfirmAuthModeExample();
+
+ // 3. 发起单笔转账(默认模式)
example.transferExample();
- // 2. 查询转账结果
+ // 4. 查询转账结果
// example.queryByOutBillNoExample();
- // 3. 撤销转账
+ // 5. 撤销转账
// example.cancelTransferExample();
- // 4. 批量转账(传统API)
+ // 6. 批量转账(传统API)
// example.batchTransferExample();
}
}
\ No newline at end of file
From f249450dbe7fa079aa6d012ef5da9571be9fb074 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 18:03:36 +0800
Subject: [PATCH 073/111] =?UTF-8?q?:art:=20#3798=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=BE=AE=E4=BF=A1=E6=94=AF?=
=?UTF-8?q?=E4=BB=98=E4=B8=8B=E5=8D=95=E6=8E=A5=E5=8F=A3=E7=BB=93=E6=9E=9C?=
=?UTF-8?q?JsapiResult=E7=B1=BB=E4=B8=AD=E6=B7=BB=E5=8A=A0prepayId?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../result/WxPayUnifiedOrderV3Result.java | 138 +++++++--
.../result/WxPayUnifiedOrderV3ResultTest.java | 267 ++++++++++++++++++
2 files changed, 389 insertions(+), 16 deletions(-)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3ResultTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3Result.java
index 309fb8e752..5b60f3b520 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3Result.java
@@ -58,7 +58,7 @@ public class WxPayUnifiedOrderV3Result implements Serializable {
/**
*
* 字段名:二维码链接(NATIVE支付 会返回)
- * 变量名:h5_url
+ * 变量名:code_url
* 是否必填:是
* 类型:string[1,512]
* 描述:
@@ -81,6 +81,19 @@ public static class JsapiResult implements Serializable {
private String packageValue;
private String signType;
private String paySign;
+ /**
+ *
+ * 字段名:预支付交易会话标识
+ * 变量名:prepay_id
+ * 是否必填:否(用户可选存储)
+ * 类型:string[1,64]
+ * 描述:
+ * 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
+ * 此字段用于支持用户存储prepay_id,以便复用和重新生成支付签名
+ * 示例值:wx201410272009395522657a690389285100
+ *
+ */
+ private String prepayId;
private String getSignStr() {
return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue);
@@ -106,30 +119,123 @@ private String getSignStr() {
}
public T getPayInfo(TradeTypeEnum tradeType, String appId, String mchId, PrivateKey privateKey) {
- String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
- String nonceStr = SignUtils.genRandomStr();
switch (tradeType) {
case JSAPI:
- JsapiResult jsapiResult = new JsapiResult();
- jsapiResult.setAppId(appId).setTimeStamp(timestamp)
- .setPackageValue("prepay_id=" + this.prepayId).setNonceStr(nonceStr)
- //签名类型,默认为RSA,仅支持RSA。
- .setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey));
- return (T) jsapiResult;
+ return (T) buildJsapiResult(this.prepayId, appId, privateKey);
case H5:
return (T) this.h5Url;
case APP:
- AppResult appResult = new AppResult();
- appResult.setAppid(appId).setPrepayId(this.prepayId).setPartnerId(mchId)
- .setNoncestr(nonceStr).setTimestamp(timestamp)
- //暂填写固定值Sign=WXPay
- .setPackageValue("Sign=WXPay")
- .setSign(SignUtils.sign(appResult.getSignStr(), privateKey));
- return (T) appResult;
+ return (T) buildAppResult(this.prepayId, appId, mchId, privateKey);
case NATIVE:
return (T) this.codeUrl;
default:
throw new WxRuntimeException("不支持的支付类型");
}
}
+
+ /**
+ *
+ * 根据已有的prepay_id生成JSAPI支付所需的参数对象(解耦版本)
+ * 应用场景:
+ * 1. 用户已经通过createPartnerOrderV3或unifiedPartnerOrderV3获取了prepay_id
+ * 2. 用户希望存储prepay_id用于后续复用
+ * 3. 支付失败后,使用存储的prepay_id重新生成支付签名信息
+ *
+ * 使用示例:
+ * // 步骤1:创建订单并获取prepay_id
+ * WxPayUnifiedOrderV3Result result = wxPayService.unifiedPartnerOrderV3(TradeTypeEnum.JSAPI, request);
+ * String prepayId = result.getPrepayId();
+ * // 存储prepayId到数据库...
+ *
+ * // 步骤2:需要支付时,使用存储的prepay_id生成支付信息
+ * WxPayUnifiedOrderV3Result.JsapiResult payInfo = WxPayUnifiedOrderV3Result.getJsapiPayInfo(
+ * prepayId, appId, wxPayService.getConfig().getPrivateKey()
+ * );
+ *
+ *
+ * @param prepayId 预支付交易会话标识
+ * @param appId 应用ID
+ * @param privateKey 商户私钥,用于签名
+ * @return JSAPI支付所需的参数对象
+ */
+ public static JsapiResult getJsapiPayInfo(String prepayId, String appId, PrivateKey privateKey) {
+ if (prepayId == null || appId == null || privateKey == null) {
+ throw new IllegalArgumentException("prepayId, appId 和 privateKey 不能为空");
+ }
+ return buildJsapiResult(prepayId, appId, privateKey);
+ }
+
+ /**
+ *
+ * 根据已有的prepay_id生成APP支付所需的参数对象(解耦版本)
+ * 应用场景:
+ * 1. 用户已经通过createPartnerOrderV3或unifiedPartnerOrderV3获取了prepay_id
+ * 2. 用户希望存储prepay_id用于后续复用
+ * 3. 支付失败后,使用存储的prepay_id重新生成支付签名信息
+ *
+ * 使用示例:
+ * // 步骤1:创建订单并获取prepay_id
+ * WxPayUnifiedOrderV3Result result = wxPayService.unifiedPartnerOrderV3(TradeTypeEnum.APP, request);
+ * String prepayId = result.getPrepayId();
+ * // 存储prepayId到数据库...
+ *
+ * // 步骤2:需要支付时,使用存储的prepay_id生成支付信息
+ * WxPayUnifiedOrderV3Result.AppResult payInfo = WxPayUnifiedOrderV3Result.getAppPayInfo(
+ * prepayId, appId, mchId, wxPayService.getConfig().getPrivateKey()
+ * );
+ *
+ *
+ * @param prepayId 预支付交易会话标识
+ * @param appId 应用ID
+ * @param mchId 商户号
+ * @param privateKey 商户私钥,用于签名
+ * @return APP支付所需的参数对象
+ */
+ public static AppResult getAppPayInfo(String prepayId, String appId, String mchId, PrivateKey privateKey) {
+ if (prepayId == null || appId == null || mchId == null || privateKey == null) {
+ throw new IllegalArgumentException("prepayId, appId, mchId 和 privateKey 不能为空");
+ }
+ return buildAppResult(prepayId, appId, mchId, privateKey);
+ }
+
+ /**
+ * 构建JSAPI支付结果对象
+ *
+ * @param prepayId 预支付交易会话标识
+ * @param appId 应用ID
+ * @param privateKey 商户私钥,用于签名
+ * @return JSAPI支付所需的参数对象
+ */
+ private static JsapiResult buildJsapiResult(String prepayId, String appId, PrivateKey privateKey) {
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ String nonceStr = SignUtils.genRandomStr();
+ JsapiResult jsapiResult = new JsapiResult();
+ jsapiResult.setAppId(appId).setTimeStamp(timestamp)
+ .setPackageValue("prepay_id=" + prepayId).setNonceStr(nonceStr)
+ .setPrepayId(prepayId)
+ //签名类型,默认为RSA,仅支持RSA。
+ .setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey));
+ return jsapiResult;
+ }
+
+ /**
+ * 构建APP支付结果对象
+ *
+ * @param prepayId 预支付交易会话标识
+ * @param appId 应用ID
+ * @param mchId 商户号
+ * @param privateKey 商户私钥,用于签名
+ * @return APP支付所需的参数对象
+ */
+ private static AppResult buildAppResult(String prepayId, String appId, String mchId, PrivateKey privateKey) {
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ String nonceStr = SignUtils.genRandomStr();
+ AppResult appResult = new AppResult();
+ appResult.setAppid(appId).setPrepayId(prepayId).setPartnerId(mchId)
+ .setNoncestr(nonceStr).setTimestamp(timestamp)
+ //暂填写固定值Sign=WXPay
+ .setPackageValue("Sign=WXPay")
+ .setSign(SignUtils.sign(appResult.getSignStr(), privateKey));
+ return appResult;
+ }
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3ResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3ResultTest.java
new file mode 100644
index 0000000000..2e824b0e00
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3ResultTest.java
@@ -0,0 +1,267 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.v3.util.SignUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+
+/**
+ *
+ * WxPayUnifiedOrderV3Result 测试类
+ * 主要测试prepayId字段和静态工厂方法的解耦功能
+ *
+ *
+ * @author copilot
+ */
+public class WxPayUnifiedOrderV3ResultTest {
+
+ /**
+ * 生成测试用的RSA密钥对
+ */
+ private KeyPair generateKeyPair() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ return keyPairGenerator.generateKeyPair();
+ }
+
+ /**
+ * 测试JsapiResult中的prepayId字段是否正确设置
+ */
+ @Test
+ public void testJsapiResultWithPrepayId() throws Exception {
+ // 准备测试数据
+ String testPrepayId = "wx201410272009395522657a690389285100";
+ String testAppId = "wx8888888888888888";
+ KeyPair keyPair = generateKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ // 创建WxPayUnifiedOrderV3Result对象
+ WxPayUnifiedOrderV3Result result = new WxPayUnifiedOrderV3Result();
+ result.setPrepayId(testPrepayId);
+
+ // 调用getPayInfo生成JsapiResult
+ WxPayUnifiedOrderV3Result.JsapiResult jsapiResult =
+ result.getPayInfo(TradeTypeEnum.JSAPI, testAppId, null, privateKey);
+
+ // 验证prepayId字段是否正确设置
+ Assert.assertNotNull(jsapiResult.getPrepayId(), "prepayId不应为null");
+ Assert.assertEquals(jsapiResult.getPrepayId(), testPrepayId, "prepayId应该与设置的值相同");
+
+ // 验证其他字段
+ Assert.assertEquals(jsapiResult.getAppId(), testAppId);
+ Assert.assertNotNull(jsapiResult.getTimeStamp());
+ Assert.assertNotNull(jsapiResult.getNonceStr());
+ Assert.assertEquals(jsapiResult.getPackageValue(), "prepay_id=" + testPrepayId);
+ Assert.assertEquals(jsapiResult.getSignType(), "RSA");
+ Assert.assertNotNull(jsapiResult.getPaySign());
+ }
+
+ /**
+ * 测试使用静态工厂方法生成JsapiResult(解耦场景)
+ */
+ @Test
+ public void testGetJsapiPayInfoStaticMethod() throws Exception {
+ // 准备测试数据
+ String testPrepayId = "wx201410272009395522657a690389285100";
+ String testAppId = "wx8888888888888888";
+ KeyPair keyPair = generateKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ // 使用静态工厂方法生成JsapiResult
+ WxPayUnifiedOrderV3Result.JsapiResult jsapiResult =
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo(testPrepayId, testAppId, privateKey);
+
+ // 验证prepayId字段
+ Assert.assertNotNull(jsapiResult.getPrepayId(), "prepayId不应为null");
+ Assert.assertEquals(jsapiResult.getPrepayId(), testPrepayId, "prepayId应该与输入的值相同");
+
+ // 验证其他字段
+ Assert.assertEquals(jsapiResult.getAppId(), testAppId);
+ Assert.assertNotNull(jsapiResult.getTimeStamp());
+ Assert.assertNotNull(jsapiResult.getNonceStr());
+ Assert.assertEquals(jsapiResult.getPackageValue(), "prepay_id=" + testPrepayId);
+ Assert.assertEquals(jsapiResult.getSignType(), "RSA");
+ Assert.assertNotNull(jsapiResult.getPaySign());
+ }
+
+ /**
+ * 测试使用静态工厂方法生成AppResult(解耦场景)
+ */
+ @Test
+ public void testGetAppPayInfoStaticMethod() throws Exception {
+ // 准备测试数据
+ String testPrepayId = "wx201410272009395522657a690389285100";
+ String testAppId = "wx8888888888888888";
+ String testMchId = "1900000109";
+ KeyPair keyPair = generateKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ // 使用静态工厂方法生成AppResult
+ WxPayUnifiedOrderV3Result.AppResult appResult =
+ WxPayUnifiedOrderV3Result.getAppPayInfo(testPrepayId, testAppId, testMchId, privateKey);
+
+ // 验证prepayId字段
+ Assert.assertNotNull(appResult.getPrepayId(), "prepayId不应为null");
+ Assert.assertEquals(appResult.getPrepayId(), testPrepayId, "prepayId应该与输入的值相同");
+
+ // 验证其他字段
+ Assert.assertEquals(appResult.getAppid(), testAppId);
+ Assert.assertEquals(appResult.getPartnerId(), testMchId);
+ Assert.assertNotNull(appResult.getTimestamp());
+ Assert.assertNotNull(appResult.getNoncestr());
+ Assert.assertEquals(appResult.getPackageValue(), "Sign=WXPay");
+ Assert.assertNotNull(appResult.getSign());
+ }
+
+ /**
+ * 测试解耦场景:先获取prepayId,后续再生成支付信息
+ */
+ @Test
+ public void testDecoupledScenario() throws Exception {
+ // 模拟场景:先创建订单获取prepayId
+ String testPrepayId = "wx201410272009395522657a690389285100";
+ String testAppId = "wx8888888888888888";
+ KeyPair keyPair = generateKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ // 步骤1:模拟从创建订单接口获取prepayId
+ WxPayUnifiedOrderV3Result orderResult = new WxPayUnifiedOrderV3Result();
+ orderResult.setPrepayId(testPrepayId);
+
+ // 获取prepayId用于存储
+ String storedPrepayId = orderResult.getPrepayId();
+ Assert.assertEquals(storedPrepayId, testPrepayId);
+
+ // 步骤2:后续支付失败时,使用存储的prepayId重新生成支付信息
+ WxPayUnifiedOrderV3Result.JsapiResult newPayInfo =
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo(storedPrepayId, testAppId, privateKey);
+
+ // 验证重新生成的支付信息
+ Assert.assertEquals(newPayInfo.getPrepayId(), storedPrepayId);
+ Assert.assertEquals(newPayInfo.getPackageValue(), "prepay_id=" + storedPrepayId);
+ Assert.assertNotNull(newPayInfo.getPaySign());
+ }
+
+ /**
+ * 测试多次生成支付信息,签名应该不同(因为timestamp和nonceStr每次都不同)
+ */
+ @Test
+ public void testMultipleGenerationsHaveDifferentSignatures() throws Exception {
+ String testPrepayId = "wx201410272009395522657a690389285100";
+ String testAppId = "wx8888888888888888";
+ KeyPair keyPair = generateKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ // 生成第一次支付信息
+ WxPayUnifiedOrderV3Result.JsapiResult result1 =
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo(testPrepayId, testAppId, privateKey);
+
+ // 等待一秒确保timestamp不同
+ Thread.sleep(1000);
+
+ // 生成第二次支付信息
+ WxPayUnifiedOrderV3Result.JsapiResult result2 =
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo(testPrepayId, testAppId, privateKey);
+
+ // prepayId应该相同
+ Assert.assertEquals(result1.getPrepayId(), result2.getPrepayId());
+
+ // 但是timestamp、nonceStr和签名应该不同
+ Assert.assertNotEquals(result1.getTimeStamp(), result2.getTimeStamp(), "timestamp应该不同");
+ Assert.assertNotEquals(result1.getNonceStr(), result2.getNonceStr(), "nonceStr应该不同");
+ Assert.assertNotEquals(result1.getPaySign(), result2.getPaySign(), "签名应该不同");
+ }
+
+ /**
+ * 测试AppResult中的prepayId字段
+ */
+ @Test
+ public void testAppResultWithPrepayId() throws Exception {
+ String testPrepayId = "wx201410272009395522657a690389285100";
+ String testAppId = "wx8888888888888888";
+ String testMchId = "1900000109";
+ KeyPair keyPair = generateKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ WxPayUnifiedOrderV3Result result = new WxPayUnifiedOrderV3Result();
+ result.setPrepayId(testPrepayId);
+
+ // 调用getPayInfo生成AppResult
+ WxPayUnifiedOrderV3Result.AppResult appResult =
+ result.getPayInfo(TradeTypeEnum.APP, testAppId, testMchId, privateKey);
+
+ // 验证prepayId字段
+ Assert.assertNotNull(appResult.getPrepayId(), "prepayId不应为null");
+ Assert.assertEquals(appResult.getPrepayId(), testPrepayId, "prepayId应该与设置的值相同");
+ }
+
+ /**
+ * 测试getJsapiPayInfo方法的空值验证
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId 和 privateKey 不能为空")
+ public void testGetJsapiPayInfoWithNullPrepayId() {
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo(null, "appId", null);
+ }
+
+ /**
+ * 测试getJsapiPayInfo方法的空值验证 - appId为null
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId 和 privateKey 不能为空")
+ public void testGetJsapiPayInfoWithNullAppId() throws Exception {
+ KeyPair keyPair = generateKeyPair();
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo("prepayId", null, keyPair.getPrivate());
+ }
+
+ /**
+ * 测试getJsapiPayInfo方法的空值验证 - privateKey为null
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId 和 privateKey 不能为空")
+ public void testGetJsapiPayInfoWithNullPrivateKey() {
+ WxPayUnifiedOrderV3Result.getJsapiPayInfo("prepayId", "appId", null);
+ }
+
+ /**
+ * 测试getAppPayInfo方法的空值验证 - prepayId为null
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId, mchId 和 privateKey 不能为空")
+ public void testGetAppPayInfoWithNullPrepayId() {
+ WxPayUnifiedOrderV3Result.getAppPayInfo(null, "appId", "mchId", null);
+ }
+
+ /**
+ * 测试getAppPayInfo方法的空值验证 - appId为null
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId, mchId 和 privateKey 不能为空")
+ public void testGetAppPayInfoWithNullAppId() throws Exception {
+ KeyPair keyPair = generateKeyPair();
+ WxPayUnifiedOrderV3Result.getAppPayInfo("prepayId", null, "mchId", keyPair.getPrivate());
+ }
+
+ /**
+ * 测试getAppPayInfo方法的空值验证 - mchId为null
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId, mchId 和 privateKey 不能为空")
+ public void testGetAppPayInfoWithNullMchId() throws Exception {
+ KeyPair keyPair = generateKeyPair();
+ WxPayUnifiedOrderV3Result.getAppPayInfo("prepayId", "appId", null, keyPair.getPrivate());
+ }
+
+ /**
+ * 测试getAppPayInfo方法的空值验证 - privateKey为null
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "prepayId, appId, mchId 和 privateKey 不能为空")
+ public void testGetAppPayInfoWithNullPrivateKey() {
+ WxPayUnifiedOrderV3Result.getAppPayInfo("prepayId", "appId", "mchId", null);
+ }
+}
From e3463a0adfe0cb8d4111f751c5ff68b18f68fca8 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 20:27:44 +0800
Subject: [PATCH 074/111] =?UTF-8?q?:art:=20#3686=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BB=98=E6=AC=BE=E7=A0=81?=
=?UTF-8?q?=E6=94=AF=E4=BB=98=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E5=95=86=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../bean/request/WxPayCodepayRequest.java | 52 +++++++++++++++++++
.../service/impl/BaseWxPayServiceImpl.java | 41 ++++++++++++---
2 files changed, 85 insertions(+), 8 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayCodepayRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayCodepayRequest.java
index ecfa614a16..632561075a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayCodepayRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayCodepayRequest.java
@@ -45,6 +45,58 @@ public class WxPayCodepayRequest implements Serializable {
*/
@SerializedName(value = "mchid")
protected String mchid;
+ /**
+ *
+ * 字段名:服务商应用ID
+ * 变量名:sp_appid
+ * 是否必填:否
+ * 类型:string[1,32]
+ * 描述:
+ * 服务商模式下使用,由微信生成的应用ID,全局唯一。
+ * 示例值:wxd678efh567hg6787
+ *
+ */
+ @SerializedName(value = "sp_appid")
+ protected String spAppid;
+ /**
+ *
+ * 字段名:服务商商户号
+ * 变量名:sp_mchid
+ * 是否必填:否
+ * 类型:string[1,32]
+ * 描述:
+ * 服务商模式下使用,服务商商户号,由微信支付生成并下发。
+ * 示例值:1230000109
+ *
+ */
+ @SerializedName(value = "sp_mchid")
+ protected String spMchid;
+ /**
+ *
+ * 字段名:子商户应用ID
+ * 变量名:sub_appid
+ * 是否必填:否
+ * 类型:string[1,32]
+ * 描述:
+ * 服务商模式下使用,由微信生成的应用ID,全局唯一。
+ * 示例值:wxd678efh567hg6787
+ *
+ */
+ @SerializedName(value = "sub_appid")
+ protected String subAppid;
+ /**
+ *
+ * 字段名:子商户商户号
+ * 变量名:sub_mchid
+ * 是否必填:否
+ * 类型:string[1,32]
+ * 描述:
+ * 服务商模式下使用,子商户商户号,由微信支付生成并下发。
+ * 示例值:1230000109
+ *
+ */
+ @SerializedName(value = "sub_mchid")
+ protected String subMchid;
/**
*
* 字段名:商品描述
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index b4c2b919a4..484e191544 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -1201,15 +1201,40 @@ public WxPayMicropayResult micropay(WxPayMicropayRequest request) throws WxPayEx
@Override
public WxPayCodepayResult codepay(WxPayCodepayRequest request) throws WxPayException {
- if (StringUtils.isBlank(request.getAppid())) {
- request.setAppid(this.getConfig().getAppId());
- }
- if (StringUtils.isBlank(request.getMchid())) {
- request.setMchid(this.getConfig().getMchId());
+ // 判断是否为服务商模式:如果设置了sp_appid或sp_mchid或sub_mchid中的任何一个,则认为是服务商模式
+ boolean isPartnerMode = StringUtils.isNotBlank(request.getSpAppid())
+ || StringUtils.isNotBlank(request.getSpMchid())
+ || StringUtils.isNotBlank(request.getSubMchid());
+
+ if (isPartnerMode) {
+ // 服务商模式
+ if (StringUtils.isBlank(request.getSpAppid())) {
+ request.setSpAppid(this.getConfig().getAppId());
+ }
+ if (StringUtils.isBlank(request.getSpMchid())) {
+ request.setSpMchid(this.getConfig().getMchId());
+ }
+ if (StringUtils.isBlank(request.getSubAppid())) {
+ request.setSubAppid(this.getConfig().getSubAppId());
+ }
+ if (StringUtils.isBlank(request.getSubMchid())) {
+ request.setSubMchid(this.getConfig().getSubMchId());
+ }
+ String url = String.format("%s/v3/pay/partner/transactions/codepay", this.getPayBaseUrl());
+ String body = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(body, WxPayCodepayResult.class);
+ } else {
+ // 直连商户模式
+ if (StringUtils.isBlank(request.getAppid())) {
+ request.setAppid(this.getConfig().getAppId());
+ }
+ if (StringUtils.isBlank(request.getMchid())) {
+ request.setMchid(this.getConfig().getMchId());
+ }
+ String url = String.format("%s/v3/pay/transactions/codepay", this.getPayBaseUrl());
+ String body = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(body, WxPayCodepayResult.class);
}
- String url = String.format("%s/v3/pay/transactions/codepay", this.getPayBaseUrl());
- String body = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
- return GSON.fromJson(body, WxPayCodepayResult.class);
}
@Override
From 40428bc7cf2127acbae91bb4d2685d249e6cde83 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 21:54:43 +0800
Subject: [PATCH 075/111] =?UTF-8?q?:memo:=20=E6=B7=BB=E5=8A=A0=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0=E5=B0=8F=E7=A8=8B?=
=?UTF-8?q?=E5=BA=8F=E5=AE=A1=E6=A0=B8=E9=A2=9D=E5=BA=A6=E7=AE=A1=E7=90=86?=
=?UTF-8?q?=E6=8C=87=E5=8D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../AUDIT_QUOTA_MANAGEMENT_GUIDE.md | 321 ++++++++++++++++++
weixin-java-open/README.md | 30 ++
.../weixin/open/api/WxOpenMaService.java | 68 +++-
.../message/WxOpenMaSubmitAuditMessage.java | 31 ++
.../bean/result/WxOpenMaQueryQuotaResult.java | 38 ++-
.../api/impl/WxOpenMaServiceImplTest.java | 135 ++++++++
6 files changed, 617 insertions(+), 6 deletions(-)
create mode 100644 weixin-java-open/AUDIT_QUOTA_MANAGEMENT_GUIDE.md
diff --git a/weixin-java-open/AUDIT_QUOTA_MANAGEMENT_GUIDE.md b/weixin-java-open/AUDIT_QUOTA_MANAGEMENT_GUIDE.md
new file mode 100644
index 0000000000..d84932034a
--- /dev/null
+++ b/weixin-java-open/AUDIT_QUOTA_MANAGEMENT_GUIDE.md
@@ -0,0 +1,321 @@
+# 微信开放平台小程序审核额度管理最佳实践
+
+## 问题背景
+
+在使用微信开放平台第三方服务为多个授权小程序提交审核时,需要注意以下重要限制:
+
+### 审核额度限制
+
+- **默认额度**: 每个第三方平台账号每月默认有 **20 个** 审核额度
+- **消耗规则**: 每次调用 `submitAudit()` 提交一个小程序审核,会消耗 **1 个** 审核额度
+- **重置周期**: 额度每月初自动重置
+- **不可返还**: 审核撤回(undoCodeAudit)不会返还已消耗的额度
+- **增加额度**: 如需更多额度,需要联系微信开放平台客服申请
+
+### 常见问题
+
+**问题**: 开放平台是每个 appId 都要这样提交审核吗?额度不够吧?
+
+**回答**: 是的,每个授权的小程序(appId)都需要单独调用 `submitAudit()` 提交审核,每次提交会消耗 1 个审核额度。默认的 20 个额度对于管理大量小程序的第三方平台来说可能不够用,建议:
+1. 提交审核前先查询剩余额度
+2. 合理规划审核计划,避免重复提交审核
+3. 联系微信开放平台申请增加额度
+
+## API 使用说明
+
+### 1. 查询审核额度
+
+```java
+// 查询当前审核额度
+WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+
+System.out.println("当月剩余提交审核次数: " + quota.getRest()); // 剩余额度
+System.out.println("当月提交审核额度上限: " + quota.getLimit()); // 总额度
+System.out.println("剩余加急次数: " + quota.getSpeedupRest()); // 剩余加急次数
+System.out.println("加急额度上限: " + quota.getSpeedupLimit()); // 加急额度上限
+```
+
+**返回字段说明**:
+- `rest`: 当月剩余提交审核次数
+- `limit`: 当月提交审核额度上限(默认 20)
+- `speedupRest`: 剩余加急次数
+- `speedupLimit`: 加急额度上限
+
+### 2. 提交审核
+
+```java
+// 构建审核项
+WxMaCodeSubmitAuditItem item = new WxMaCodeSubmitAuditItem();
+item.setAddress("index"); // 页面路径
+item.setTag("工具"); // 标签
+item.setFirstClass("工具"); // 一级类目
+item.setSecondClass("效率"); // 二级类目
+item.setTitle("首页"); // 页面标题
+
+// 构建提交审核消息
+WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage();
+message.setItemList(Collections.singletonList(item));
+message.setVersionDesc("版本描述");
+
+// 提交审核
+WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message);
+System.out.println("审核ID: " + result.getAuditId());
+```
+
+## 最佳实践
+
+### 方案一:单个小程序提交前检查额度
+
+这是最基本的做法,适用于偶尔提交审核的场景。
+
+```java
+import me.chanjar.weixin.open.api.WxOpenMaService;
+import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage;
+import me.chanjar.weixin.open.bean.result.WxOpenMaQueryQuotaResult;
+import me.chanjar.weixin.open.bean.result.WxOpenMaSubmitAuditResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+public class AuditSubmitter {
+
+ /**
+ * 提交审核前检查额度
+ */
+ public WxOpenMaSubmitAuditResult submitWithQuotaCheck(
+ WxOpenMaService wxOpenMaService,
+ WxOpenMaSubmitAuditMessage message) throws WxErrorException {
+
+ // 1. 检查审核额度
+ WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+ System.out.println("当前剩余审核额度: " + quota.getRest());
+
+ if (quota.getRest() <= 0) {
+ throw new RuntimeException("审核额度不足,无法提交审核。剩余额度: " + quota.getRest());
+ }
+
+ // 2. 提交审核
+ WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message);
+ System.out.println("提交审核成功,审核ID: " + result.getAuditId());
+
+ // 3. 再次查询额度(可选)
+ quota = wxOpenMaService.queryQuota();
+ System.out.println("提交后剩余审核额度: " + quota.getRest());
+
+ return result;
+ }
+}
+```
+
+### 方案二:批量提交审核的额度管理
+
+适用于需要同时为多个小程序提交审核的场景。
+
+```java
+import me.chanjar.weixin.open.api.WxOpenComponentService;
+import me.chanjar.weixin.open.api.WxOpenMaService;
+import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage;
+import me.chanjar.weixin.open.bean.result.WxOpenMaQueryQuotaResult;
+import me.chanjar.weixin.open.bean.result.WxOpenMaSubmitAuditResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class BatchAuditSubmitter {
+
+ /**
+ * 批量提交审核结果
+ */
+ public static class BatchSubmitResult {
+ private int successCount; // 成功数量
+ private int failCount; // 失败数量
+ private int skipCount; // 跳过数量(额度不足)
+ private List failedAppIds; // 失败的 appId
+
+ // getters and setters...
+ }
+
+ /**
+ * 批量提交审核,带额度检查
+ */
+ public BatchSubmitResult batchSubmitWithQuotaCheck(
+ WxOpenComponentService wxOpenComponentService,
+ Map appIdToMessageMap) {
+
+ BatchSubmitResult result = new BatchSubmitResult();
+ result.setFailedAppIds(new ArrayList<>());
+
+ // 基本参数校验:避免空指针和空集合导致的 NoSuchElementException
+ if (appIdToMessageMap == null || appIdToMessageMap.isEmpty()) {
+ System.err.println("错误:待提交的小程序列表为空,未执行任何审核提交");
+ return result;
+ }
+
+ try {
+ // 1. 检查总体额度是否充足
+ // 使用任意一个已授权的小程序 appId 获取 WxMaService 来查询审核额度。
+ // 注意:审核额度是以第三方平台维度统计的,因此这里选择任意一个 appId 即可。
+ WxOpenMaQueryQuotaResult quota = wxOpenComponentService
+ .getWxMaServiceByAppid(appIdToMessageMap.keySet().iterator().next())
+ .queryQuota();
+
+ System.out.println("=== 批量提交审核开始 ===");
+ System.out.println("待提交数量: " + appIdToMessageMap.size());
+ System.out.println("当前剩余审核额度: " + quota.getRest());
+
+ if (quota.getRest() < appIdToMessageMap.size()) {
+ System.err.println("警告:审核额度不足!");
+ System.err.println(" 需要提交: " + appIdToMessageMap.size() + " 个");
+ System.err.println(" 剩余额度: " + quota.getRest());
+ System.err.println(" 缺少额度: " + (appIdToMessageMap.size() - quota.getRest()));
+ System.err.println("将仅提交前 " + quota.getRest() + " 个小程序");
+ }
+
+ // 2. 依次提交审核
+ int count = 0;
+ for (Map.Entry entry : appIdToMessageMap.entrySet()) {
+ String appId = entry.getKey();
+ WxOpenMaSubmitAuditMessage message = entry.getValue();
+
+ // 检查是否还有额度
+ if (count >= quota.getRest()) {
+ System.out.println("AppId: " + appId + " 跳过(额度不足)");
+ result.setSkipCount(result.getSkipCount() + 1);
+ continue;
+ }
+
+ try {
+ WxOpenMaService maService = wxOpenComponentService.getWxMaServiceByAppid(appId);
+ WxOpenMaSubmitAuditResult submitResult = maService.submitAudit(message);
+
+ System.out.println("AppId: " + appId + " 提交成功,审核ID: " + submitResult.getAuditId());
+ result.setSuccessCount(result.getSuccessCount() + 1);
+ count++;
+
+ } catch (WxErrorException e) {
+ System.err.println("AppId: " + appId + " 提交失败: " + e.getMessage());
+ result.setFailCount(result.getFailCount() + 1);
+ result.getFailedAppIds().add(appId);
+ count++;
+ }
+ }
+
+ // 3. 输出统计信息
+ System.out.println("=== 批量提交审核完成 ===");
+ System.out.println("成功: " + result.getSuccessCount());
+ System.out.println("失败: " + result.getFailCount());
+ System.out.println("跳过: " + result.getSkipCount());
+
+ // 4. 查询剩余额度
+ quota = wxOpenComponentService
+ .getWxMaServiceByAppid(appIdToMessageMap.keySet().iterator().next())
+ .queryQuota();
+ System.out.println("剩余额度: " + quota.getRest());
+
+ } catch (WxErrorException e) {
+ System.err.println("批量提交审核失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ return result;
+ }
+}
+```
+
+### 方案三:审核额度监控和告警
+
+建议实现一个审核额度监控机制,及时发现额度不足的情况。
+
+```java
+import me.chanjar.weixin.open.api.WxOpenMaService;
+import me.chanjar.weixin.open.bean.result.WxOpenMaQueryQuotaResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+public class QuotaMonitor {
+
+ /**
+ * 检查审核额度并发出告警
+ */
+ public void checkAndAlert(WxOpenMaService wxOpenMaService) {
+ try {
+ WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+
+ int rest = quota.getRest();
+ int limit = quota.getLimit();
+ double percentage = (double) rest / limit * 100;
+
+ System.out.println("审核额度状态:");
+ System.out.println(" 剩余: " + rest + " / " + limit);
+ System.out.println(" 使用率: " + String.format("%.1f", 100 - percentage) + "%");
+
+ // 根据剩余额度发出不同级别的告警
+ if (rest <= 0) {
+ sendCriticalAlert("审核额度已用尽!无法提交新的审核。");
+ } else if (rest <= 3) {
+ sendWarningAlert("审核额度严重不足!剩余额度: " + rest);
+ } else if (percentage < 30) {
+ sendInfoAlert("审核额度偏低,剩余: " + rest + " (" + String.format("%.1f", percentage) + "%)");
+ }
+
+ } catch (WxErrorException e) {
+ System.err.println("查询审核额度失败: " + e.getMessage());
+ }
+ }
+
+ private void sendCriticalAlert(String message) {
+ // 发送紧急告警(如:发送邮件、短信、钉钉消息等)
+ System.err.println("[严重] " + message);
+ }
+
+ private void sendWarningAlert(String message) {
+ // 发送警告(如:发送邮件、企业微信消息等)
+ System.out.println("[警告] " + message);
+ }
+
+ private void sendInfoAlert(String message) {
+ // 发送普通提示
+ System.out.println("[提示] " + message);
+ }
+}
+```
+
+## 常见问题 FAQ
+
+### Q1: 审核额度什么时候重置?
+A: 审核额度在每月初自动重置为默认值(通常是 20 个)。
+
+### Q2: 审核撤回会返还额度吗?
+A: 不会。调用 `undoCodeAudit()` 撤回审核不会返还已消耗的额度。
+
+### Q3: 如何增加审核额度?
+A: 需要联系微信开放平台客服申请增加额度。具体联系方式请参考微信开放平台官方文档。
+
+### Q4: 审核失败会消耗额度吗?
+A: 会。只要调用了 `submitAudit()` 接口提交审核,无论审核是否通过,都会消耗 1 个额度。
+
+### Q5: 加急审核会额外消耗额度吗?
+A: 加急审核(`speedAudit()`)使用的是单独的加急额度(`speedupRest`),不会消耗普通审核额度。但加急审核的前提是已经提交了审核,所以提交审核时仍会消耗 1 个普通审核额度。
+
+### Q6: 多个小程序共享审核额度吗?
+A: 是的。同一个第三方平台账号下,所有授权的小程序共享审核额度。每提交一个小程序审核,都会消耗该第三方平台的 1 个审核额度。
+
+### Q7: 如何避免审核额度不足?
+A: 建议采取以下措施:
+- 在批量提交审核前,先调用 `queryQuota()` 检查剩余额度
+- 实现审核额度监控和告警机制
+- 合理规划审核计划,避免不必要的重复提交审核
+- 提高代码质量,减少审核不通过的情况
+- 联系微信开放平台申请增加额度
+
+### Q8: 能否查询历史审核额度使用情况?
+A: 微信开放平台 API 目前只提供当前剩余额度查询,不提供历史使用记录。如需统计历史使用情况,需要自行记录每次调用 `submitAudit()` 的时间和次数。
+
+## 相关文档
+
+- [微信开放平台官方文档 - 查询额度](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/query_quota.html)
+- [微信开放平台官方文档 - 提交审核](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/submit_audit.html)
+- [微信开放平台官方文档 - 加急审核](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/speedup_audit.html)
+
+## 技术支持
+
+如有问题,请提交 Issue 到 [WxJava GitHub 仓库](https://github.com/binarywang/WxJava/issues)。
diff --git a/weixin-java-open/README.md b/weixin-java-open/README.md
index 6ca65dfef3..ad7f3d0109 100644
--- a/weixin-java-open/README.md
+++ b/weixin-java-open/README.md
@@ -31,6 +31,36 @@
---
+## 重要提示:小程序审核额度限制
+
+**在使用第三方平台代小程序提交审核时,请注意以下限制:**
+
+### 审核额度说明
+
+- **默认额度**: 每个第三方平台账号每月默认有 **20 个** 审核额度
+- **消耗规则**: 每次调用 `submitAudit()` 提交一个小程序审核,会消耗 **1 个** 审核额度
+- **重置周期**: 额度每月初自动重置
+- **额度查询**: 使用 `queryQuota()` 方法查询剩余额度
+
+### 最佳实践
+
+```java
+// 1. 先查询剩余额度
+WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+if (quota.getRest() <= 0) {
+ throw new RuntimeException("审核额度不足,剩余:" + quota.getRest());
+}
+
+// 2. 提交审核
+WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage();
+message.setItemList(itemList);
+WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message);
+```
+
+**详细说明**: 请参考 [AUDIT_QUOTA_MANAGEMENT_GUIDE.md](AUDIT_QUOTA_MANAGEMENT_GUIDE.md)
+
+---
+
## 代码示例
消息机制未实现,下面为通知回调中设置的代码部分
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
index ab229ba537..1033b57a16 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
@@ -539,10 +539,36 @@ WxOpenMaDomainResult modifyDomainDirectly(String action, List requestDom
/**
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用)
+ *
+ * 重要提示:审核额度限制
+ *
+ *
+ * - 每个第三方平台账号每月有审核额度限制(默认20次,可通过 {@link #queryQuota()} 查询)
+ * - 每次调用 submitAudit 提交一个小程序审核时,会消耗1个审核额度
+ * - 建议在提交审核前,先调用 {@link #queryQuota()} 检查剩余额度
+ * - 如需增加额度,请联系微信开放平台客服
+ *
+ *
+ * 最佳实践:
+ *
+ * {@code
+ * // 1. 先查询剩余额度
+ * WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+ * if (quota.getRest() <= 0) {
+ * throw new RuntimeException("审核额度不足,剩余:" + quota.getRest());
+ * }
+ *
+ * // 2. 提交审核
+ * WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage();
+ * message.setItemList(itemList);
+ * WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message);
+ * }
*
* @param submitAuditMessage the submit audit message
* @return the wx open ma submit audit result
* @throws WxErrorException the wx error exception
+ * @see #queryQuota() 查询审核额度
+ * @see #speedAudit(Long) 加急审核
*/
WxOpenMaSubmitAuditResult submitAudit(WxOpenMaSubmitAuditMessage submitAuditMessage) throws WxErrorException;
@@ -690,11 +716,43 @@ WxOpenMaDomainResult modifyDomainDirectly(String action, List requestDom
WxOpenMaGetCodePrivacyInfoResult getCodePrivacyInfo() throws WxErrorException;
/**
- * 查询服务商的当月提审限额和加急次数(Quota)
- * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/query_quota.html
- *
- * @return the wx open ma query quota result
- * @throws WxErrorException the wx error exception
+ * 查询服务商的当月提交审核限额和加急次数(Quota)
+ *
+ * 文档地址:
+ * 查询额度
+ *
+ *
+ * 返回字段说明:
+ *
+ *
+ * - rest: 当月剩余提交审核次数
+ * - limit: 当月提交审核额度上限(默认20次)
+ * - speedup_rest: 剩余加急次数
+ * - speedup_limit: 加急额度上限
+ *
+ *
+ * 重要说明:
+ *
+ *
+ * - 每个第三方平台账号每月初会重置审核额度
+ * - 每次调用 {@link #submitAudit} 提交审核会消耗1个额度
+ * - 审核撤回不会返还额度
+ * - 建议在批量提交审核前,先调用此接口检查额度是否充足
+ *
+ *
+ * 使用示例:
+ *
+ * {@code
+ * WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+ * System.out.println("剩余审核次数:" + quota.getRest());
+ * System.out.println("审核额度上限:" + quota.getLimit());
+ * System.out.println("剩余加急次数:" + quota.getSpeedupRest());
+ * }
+ *
+ * @return 审核额度信息
+ * @throws WxErrorException 调用微信接口失败时抛出
+ * @see #submitAudit(WxOpenMaSubmitAuditMessage) 提交审核
+ * @see #speedAudit(Long) 加急审核
*/
WxOpenMaQueryQuotaResult queryQuota() throws WxErrorException;
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenMaSubmitAuditMessage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenMaSubmitAuditMessage.java
index d8c1461d67..79400aa968 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenMaSubmitAuditMessage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenMaSubmitAuditMessage.java
@@ -10,8 +10,39 @@
/**
* 微信小程序代码包提交审核(仅供第三方开发者代小程序调用)
+ *
+ * 重要提示:审核额度限制
+ *
+ *
+ * - 每个第三方平台账号每月有审核额度限制(默认20次)
+ * - 每次调用 submitAudit 提交审核会消耗1个额度,无论审核是否通过
+ * - 建议在提交前先调用 queryQuota 检查剩余额度
+ *
+ *
+ * 使用示例:
+ *
+ * {@code
+ * // 1. 构建审核项
+ * WxMaCodeSubmitAuditItem item = new WxMaCodeSubmitAuditItem();
+ * item.setAddress("index");
+ * item.setTag("游戏");
+ * item.setFirstClass("游戏");
+ * item.setSecondClass("休闲游戏");
+ * item.setTitle("首页");
+ *
+ * // 2. 构建提交审核消息
+ * WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage();
+ * message.setItemList(Collections.singletonList(item));
+ * message.setVersionDesc("版本描述");
+ *
+ * // 3. 提交审核
+ * WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message);
+ * System.out.println("审核ID: " + result.getAuditId());
+ * }
*
* @author yqx
+ * @see me.chanjar.weixin.open.api.WxOpenMaService#submitAudit(WxOpenMaSubmitAuditMessage)
+ * @see me.chanjar.weixin.open.api.WxOpenMaService#queryQuota()
* created on 2018/9/13
*/
@Data
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaQueryQuotaResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaQueryQuotaResult.java
index 3b02906242..0f964b5f44 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaQueryQuotaResult.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaQueryQuotaResult.java
@@ -6,7 +6,31 @@
import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
/**
- * 微信开放平台小程序当前分阶段发布详情
+ * 微信开放平台小程序提交审核额度查询结果
+ *
+ * 用于查询第三方平台服务商的当月提交审核限额和加急次数
+ *
+ *
+ * 字段说明:
+ *
+ *
+ * - rest: 当月剩余提交审核次数
+ * - limit: 当月提交审核额度上限(默认20次,可联系微信开放平台增加)
+ * - speedupRest: 剩余加急次数
+ * - speedupLimit: 加急额度上限
+ *
+ *
+ * 重要提示:
+ *
+ *
+ * - 每次调用 submitAudit 提交小程序审核,会消耗1个审核额度
+ * - 额度每月初自动重置
+ * - 审核撤回不会返还已消耗的额度
+ * - 建议在批量提交审核前,先检查剩余额度是否充足
+ *
+ *
+ * @see me.chanjar.weixin.open.api.WxOpenMaService#queryQuota()
+ * @see me.chanjar.weixin.open.api.WxOpenMaService#submitAudit(me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage)
*/
@Data
@EqualsAndHashCode(callSuper = true)
@@ -14,15 +38,27 @@ public class WxOpenMaQueryQuotaResult extends WxOpenResult {
private static final long serialVersionUID = 5915265985261653007L;
+ /**
+ * 当月剩余提交审核次数
+ */
@SerializedName("rest")
private Integer rest;
+ /**
+ * 当月提交审核额度上限
+ */
@SerializedName("limit")
private Integer limit;
+ /**
+ * 剩余加急次数
+ */
@SerializedName("speedup_rest")
private Integer speedupRest;
+ /**
+ * 加急额度上限
+ */
@SerializedName("speedup_limit")
private Integer speedupLimit;
diff --git a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImplTest.java b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImplTest.java
index 4d8e41b59e..1de6ffe2d6 100644
--- a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImplTest.java
+++ b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImplTest.java
@@ -306,6 +306,141 @@ public void testGetGrayReleasePlan() {
@Test
public void testQueryQuota() {
+ // 此测试方法演示如何使用审核额度查询功能
+ // 注意:实际运行需要真实的微信 API 凭据
+ /*
+ try {
+ // 查询当前审核额度
+ WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+
+ System.out.println("审核额度信息:");
+ System.out.println(" 当月剩余提交审核次数: " + quota.getRest());
+ System.out.println(" 当月提交审核额度上限: " + quota.getLimit());
+ System.out.println(" 剩余加急次数: " + quota.getSpeedupRest());
+ System.out.println(" 加急额度上限: " + quota.getSpeedupLimit());
+
+ // 检查额度是否充足
+ if (quota.getRest() <= 0) {
+ System.err.println("警告:审核额度已用尽!");
+ } else if (quota.getRest() <= 5) {
+ System.out.println("提示:审核额度即将用尽,请注意!");
+ }
+ } catch (WxErrorException e) {
+ e.printStackTrace();
+ }
+ */
+ }
+
+ /**
+ * 演示提交审核前检查额度的最佳实践
+ *
+ * 这是一个完整的示例,展示如何在提交审核前检查额度,避免额度不足导致的失败
+ *
+ */
+ @Test
+ public void testSubmitAuditWithQuotaCheck() {
+ // 此测试方法演示提交审核前的额度检查最佳实践
+ // 注意:实际运行需要真实的微信 API 凭据
+ /*
+ try {
+ // 步骤1:检查审核额度
+ WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota();
+ System.out.println("当前剩余审核额度: " + quota.getRest());
+
+ if (quota.getRest() <= 0) {
+ throw new RuntimeException("审核额度不足,无法提交审核。剩余额度: " + quota.getRest());
+ }
+
+ // 步骤2:准备审核数据
+ WxMaCodeSubmitAuditItem item = new WxMaCodeSubmitAuditItem();
+ item.setAddress("index");
+ item.setTag("工具");
+ item.setFirstClass("工具");
+ item.setSecondClass("效率");
+ item.setTitle("首页");
+
+ WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage();
+ message.setItemList(Collections.singletonList(item));
+ message.setVersionDesc("修复若干已知问题,优化用户体验");
+
+ // 步骤3:提交审核
+ WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message);
+ System.out.println("提交审核成功,审核ID: " + result.getAuditId());
+
+ // 步骤4:再次查询额度,确认已消耗
+ quota = wxOpenMaService.queryQuota();
+ System.out.println("提交后剩余审核额度: " + quota.getRest());
+
+ } catch (WxErrorException e) {
+ System.err.println("提交审核失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ */
+ }
+
+ /**
+ * 演示批量提交审核时的额度管理策略
+ *
+ * 当需要为多个小程序提交审核时,应该先统一检查额度是否充足
+ *
+ */
+ @Test
+ public void testBatchSubmitAuditWithQuotaManagement() {
+ // 此测试方法演示批量提交审核时的额度管理策略
+ // 注意:实际运行需要真实的微信 API 凭据,以及 WxOpenComponentService 实例
+ /*
+ // 假设已经初始化了 wxOpenComponentService
+ // WxOpenComponentService wxOpenComponentService = ...;
+
+ try {
+ // 假设需要为多个小程序提交审核
+ List appIds = Arrays.asList("appid1", "appid2", "appid3");
+
+ // 步骤1:通过任意一个小程序服务查询总体额度
+ // 注意:审核额度是第三方平台级别的,所有授权小程序共享
+ WxOpenMaService firstMaService = wxOpenComponentService.getWxMaServiceByAppid(appIds.get(0));
+ WxOpenMaQueryQuotaResult quota = firstMaService.queryQuota();
+ System.out.println("当前剩余审核额度: " + quota.getRest());
+
+ if (quota.getRest() < appIds.size()) {
+ System.err.println("警告:审核额度不足!");
+ System.err.println(" 需要提交: " + appIds.size() + " 个");
+ System.err.println(" 剩余额度: " + quota.getRest());
+ System.err.println(" 缺少额度: " + (appIds.size() - quota.getRest()));
+ return;
+ }
+
+ // 步骤2:依次提交审核
+ int successCount = 0;
+ for (String appId : appIds) {
+ try {
+ WxOpenMaService maService = wxOpenComponentService.getWxMaServiceByAppid(appId);
+
+ WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage();
+ // ... 设置审核信息
+
+ WxOpenMaSubmitAuditResult result = maService.submitAudit(message);
+ System.out.println("AppId: " + appId + " 提交成功,审核ID: " + result.getAuditId());
+ successCount++;
+
+ } catch (WxErrorException e) {
+ System.err.println("AppId: " + appId + " 提交失败: " + e.getMessage());
+ }
+ }
+
+ // 步骤3:输出统计信息
+ System.out.println("批量提交完成:");
+ System.out.println(" 成功: " + successCount);
+ System.out.println(" 失败: " + (appIds.size() - successCount));
+
+ // 步骤4:查询剩余额度
+ quota = firstMaService.queryQuota();
+ System.out.println(" 剩余额度: " + quota.getRest());
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ */
}
@Test
From b601b557f90053979412d152d8e3f836c7913513 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 21:57:27 +0800
Subject: [PATCH 076/111] =?UTF-8?q?:art:=20=E6=94=AF=E4=BB=98=E5=9B=9E?=
=?UTF-8?q?=E8=B0=83=E7=BB=93=E6=9E=9C=E8=A7=A3=E6=9E=90=E6=97=B6=E6=8F=90?=
=?UTF-8?q?=E4=BE=9B=E5=BF=85=E8=A6=81=E7=9A=84=E6=A3=80=E6=B5=8B=E6=8A=A5?=
=?UTF-8?q?=E9=94=99=E5=88=A4=E6=96=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../service/impl/BaseWxPayServiceImpl.java | 7 ++++
.../impl/BaseWxPayServiceImplTest.java | 36 +++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 484e191544..2e896cda7e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -344,6 +344,13 @@ public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData) throws WxPa
public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String signType) throws WxPayException {
try {
log.debug("微信支付异步通知请求参数:{}", xmlData);
+
+ // 检测数据格式并给出适当的处理建议
+ if (xmlData != null && xmlData.trim().startsWith("{")) {
+ throw new WxPayException("检测到V3版本的JSON格式通知数据,请使用parseOrderNotifyV3Result方法解析。" +
+ " V3 API需要传入SignatureHeader参数进行签名验证。");
+ }
+
WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlData);
if (signType == null) {
this.switchover(result.getMchId(), result.getAppid());
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
index bd24f188d0..7dff396de5 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
@@ -627,6 +627,42 @@ public void testParseOrderNotifyResult() throws Exception {
}
+ /**
+ * Test parse order notify result with JSON format should give helpful error.
+ * 测试当传入V3版本的JSON格式通知数据时,应该抛出清晰的错误提示
+ *
+ * @throws Exception the exception
+ */
+ @Test
+ public void testParseOrderNotifyResultWithJsonShouldGiveHelpfulError() throws Exception {
+ String jsonString = "{\n" +
+ " \"id\": \"EV-2018022511223320873\",\n" +
+ " \"create_time\": \"2015-05-20T13:29:35+08:00\",\n" +
+ " \"resource_type\": \"encrypt-resource\",\n" +
+ " \"event_type\": \"TRANSACTION.SUCCESS\",\n" +
+ " \"summary\": \"支付成功\",\n" +
+ " \"resource\": {\n" +
+ " \"algorithm\": \"AEAD_AES_256_GCM\",\n" +
+ " \"ciphertext\": \"test\",\n" +
+ " \"associated_data\": \"transaction\",\n" +
+ " \"nonce\": \"test\"\n" +
+ " }\n" +
+ "}";
+
+ try {
+ this.payService.parseOrderNotifyResult(jsonString);
+ fail("Expected WxPayException for JSON input");
+ } catch (WxPayException e) {
+ // 验证错误消息包含V3版本和parseOrderNotifyV3Result方法的指导信息
+ String message = e.getMessage();
+ assertTrue(message.contains("V3版本"), "错误消息应包含'V3版本'");
+ assertTrue(message.contains("JSON格式"), "错误消息应包含'JSON格式'");
+ assertTrue(message.contains("parseOrderNotifyV3Result"), "错误消息应包含'parseOrderNotifyV3Result'方法名");
+ assertTrue(message.contains("SignatureHeader"), "错误消息应包含'SignatureHeader'");
+ log.info("JSON格式检测正常,错误提示: {}", message);
+ }
+ }
+
/**
* Test get wx api data.
*
From 8e760a972ddbe9e79ca29d3b3d0499de36a293bf Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 22 Dec 2025 09:37:25 +0800
Subject: [PATCH 077/111] =?UTF-8?q?:art:=20#3654=20=E3=80=90=E5=BC=80?=
=?UTF-8?q?=E6=94=BE=E5=B9=B3=E5=8F=B0=E3=80=91=E6=B6=88=E6=81=AF=E8=B7=AF?=
=?UTF-8?q?=E7=94=B1=E5=A4=84=E7=90=86=E7=A8=8B=E5=BA=8F=E5=A2=9E=E5=8A=A0?=
=?UTF-8?q?=E5=AF=B9=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../open/api/impl/WxOpenMessageRouter.java | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMessageRouter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMessageRouter.java
index 7314bfd694..3c2c3058f4 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMessageRouter.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMessageRouter.java
@@ -23,4 +23,29 @@ public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage, String appId) {
public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage, final Map context, String appId) {
return route(wxMessage, context, wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId));
}
+
+ /**
+ * 路由微信消息到小程序服务 (Route WeChat message to Mini App service)
+ *
+ * @param wxMessage the wx message
+ * @param appId the app id
+ * @return the wx mp xml out message
+ */
+ public WxMpXmlOutMessage routeForMa(final WxMpXmlMessage wxMessage, String appId) {
+ return routeForMa(wxMessage, new HashMap<>(), appId);
+ }
+
+ /**
+ * 路由微信消息到小程序服务 (Route WeChat message to Mini App service)
+ *
+ * @param wxMessage the wx message
+ * @param context the context
+ * @param appId the app id
+ * @return the wx mp xml out message
+ */
+ public WxMpXmlOutMessage routeForMa(final WxMpXmlMessage wxMessage, final Map context, String appId) {
+ // 将小程序服务放入上下文中,以便处理器可以访问 (Put Mini App service in context so handlers can access it)
+ context.put("wxOpenMaService", wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appId));
+ return route(wxMessage, context, wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId));
+ }
}
From c2d0cf8d4e45969c167d8f34db24656409068e38 Mon Sep 17 00:00:00 2001
From: yanglegetuo <57779135+yanglegetuo@users.noreply.github.com>
Date: Wed, 24 Dec 2025 10:05:07 +0800
Subject: [PATCH 078/111] =?UTF-8?q?:new:=20#3818=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=20=E6=96=B0=E5=A2=9E=E8=AE=BE?=
=?UTF-8?q?=E5=A4=87=E7=BB=84=E7=9B=B8=E5=85=B3=E7=9A=84=20API=20=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/WxMaDeviceSubscribeService.java | 55 ++++++++++++++++++-
.../impl/WxMaDeviceSubscribeServiceImpl.java | 54 +++++++++++++++++-
.../device/WxMaCreateIotGroupIdRequest.java | 38 +++++++++++++
.../device/WxMaGetIotGroupInfoRequest.java | 33 +++++++++++
.../WxMaIotGroupDeviceInfoResponse.java | 51 +++++++++++++++++
.../device/WxMaIotGroupDeviceRequest.java | 44 +++++++++++++++
.../miniapp/constant/WxMaApiUrlConstants.java | 8 +++
.../WxMaDeviceSubscribeServiceImplTest.java | 55 +++++++++++++++++--
8 files changed, 329 insertions(+), 9 deletions(-)
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaCreateIotGroupIdRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaGetIotGroupInfoRequest.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceInfoResponse.java
create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceRequest.java
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java
index f44f64e48d..b45bc058a6 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java
@@ -1,9 +1,10 @@
package cn.binarywang.wx.miniapp.api;
-import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceSubscribeMessageRequest;
-import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceTicketRequest;
+import cn.binarywang.wx.miniapp.bean.device.*;
import me.chanjar.weixin.common.error.WxErrorException;
+import java.util.List;
+
/**
* 小程序设备订阅消息相关 API
* 文档:
@@ -21,6 +22,7 @@ public interface WxMaDeviceSubscribeService {
* 注意:
* 设备ticket有效时间为5分钟
*
+ *
* @param deviceTicketRequest
* @return
* @throws WxErrorException
@@ -37,4 +39,53 @@ public interface WxMaDeviceSubscribeService {
*/
void sendDeviceSubscribeMsg(WxMaDeviceSubscribeMessageRequest deviceSubscribeMessageRequest) throws WxErrorException;
+ /**
+ *
+ * 创建设备组
+ * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/createIotGroupId.html
+ *
+ *
+ * @param createIotGroupIdRequest 请求参数
+ * @return 设备组的唯一标识
+ * @throws WxErrorException
+ */
+ String createIotGroupId(WxMaCreateIotGroupIdRequest createIotGroupIdRequest) throws WxErrorException;
+
+ /**
+ *
+ * 查询设备组信息
+ * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/getIotGroupInfo.html
+ *
+ *
+ * @param getIotGroupInfoRequest 请求参数
+ * @return 设备组信息
+ * @throws WxErrorException
+ */
+ WxMaIotGroupDeviceInfoResponse getIotGroupInfo(WxMaGetIotGroupInfoRequest getIotGroupInfoRequest) throws WxErrorException;
+
+ /**
+ *
+ * 设备组添加设备
+ * 一个设备组最多添加 50 个设备。 一个设备同一时间只能被添加到一个设备组中。
+ * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/addIotGroupDevice.html
+ *
+ *
+ * @param request 请求参数
+ * @return 成功添加的设备信息
+ * @throws WxErrorException
+ */
+ List addIotGroupDevice(WxMaIotGroupDeviceRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 设备组删除设备
+ * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/removeIotGroupDevice.html
+ *
+ *
+ * @param request 请求参数
+ * @return 成功删除的设备信息
+ * @throws WxErrorException
+ */
+ List removeIotGroupDevice(WxMaIotGroupDeviceRequest request) throws WxErrorException;
+
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java
index 7f8dce1df8..632fe7bd94 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java
@@ -2,18 +2,26 @@
import cn.binarywang.wx.miniapp.api.WxMaDeviceSubscribeService;
import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceSubscribeMessageRequest;
-import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceTicketRequest;
+import cn.binarywang.wx.miniapp.bean.device.*;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.util.List;
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.GET_SN_TICKET_URL;
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.SEND_DEVICE_SUBSCRIBE_MSG_URL;
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.CREATE_IOT_GROUP_ID_URL;
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.GET_IOT_GROUP_INFO_URL;
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.ADD_IOT_GROUP_DEVICE_URL;
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.REMOVE_IOT_GROUP_DEVICE_URL;
/**
* 小程序设备订阅消息相关 API
@@ -47,4 +55,46 @@ public void sendDeviceSubscribeMsg(WxMaDeviceSubscribeMessageRequest deviceSubsc
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
}
+
+ @Override
+ public String createIotGroupId(WxMaCreateIotGroupIdRequest createIotGroupIdRequest) throws WxErrorException {
+ String responseContent = this.service.post(CREATE_IOT_GROUP_ID_URL, createIotGroupIdRequest.toJson());
+ JsonObject jsonObject = GsonParser.parse(responseContent);
+ if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+ }
+ return jsonObject.get("group_id").getAsString();
+ }
+
+ @Override
+ public WxMaIotGroupDeviceInfoResponse getIotGroupInfo(WxMaGetIotGroupInfoRequest getIotGroupInfoRequest) throws WxErrorException {
+ String responseContent = this.service.post(GET_IOT_GROUP_INFO_URL, getIotGroupInfoRequest.toJson());
+ JsonObject jsonObject = GsonParser.parse(responseContent);
+ if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+ }
+ return WxGsonBuilder.create().fromJson(responseContent, WxMaIotGroupDeviceInfoResponse.class);
+ }
+
+ @Override
+ public List addIotGroupDevice(WxMaIotGroupDeviceRequest request) throws WxErrorException {
+ String responseContent = this.service.post(ADD_IOT_GROUP_DEVICE_URL, request.toJson());
+ JsonObject jsonObject = GsonParser.parse(responseContent);
+ if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+ }
+ return WxMaGsonBuilder.create().fromJson(jsonObject.getAsJsonArray("device_list"), new TypeToken>() {
+ }.getType());
+ }
+
+ @Override
+ public List removeIotGroupDevice(WxMaIotGroupDeviceRequest request) throws WxErrorException {
+ String responseContent = this.service.post(REMOVE_IOT_GROUP_DEVICE_URL, request.toJson());
+ JsonObject jsonObject = GsonParser.parse(responseContent);
+ if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+ }
+ return WxMaGsonBuilder.create().fromJson(jsonObject.getAsJsonArray("device_list"), new TypeToken>() {
+ }.getType());
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaCreateIotGroupIdRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaCreateIotGroupIdRequest.java
new file mode 100644
index 0000000000..20a0a146ae
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaCreateIotGroupIdRequest.java
@@ -0,0 +1,38 @@
+package cn.binarywang.wx.miniapp.bean.device;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @Author: yanglegetuo
+ * @Date: 2025/12/22 8:51
+ * @Description: 创建设备组 请求参数
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaCreateIotGroupIdRequest implements Serializable {
+ private static final long serialVersionUID = 1827809470414413390L;
+ /**
+ * 设备型号id。通过注册设备获得(必填)
+ */
+ @SerializedName("model_id")
+ private String modelId;
+ /**
+ * 设备组的名称(创建时决定,无法修改)
+ */
+ @SerializedName("group_name")
+ private String groupName;
+
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaGetIotGroupInfoRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaGetIotGroupInfoRequest.java
new file mode 100644
index 0000000000..47ebc2be44
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaGetIotGroupInfoRequest.java
@@ -0,0 +1,33 @@
+package cn.binarywang.wx.miniapp.bean.device;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @Author: yanglegetuo
+ * @Date: 2025/12/22 8:51
+ * @Description: 查询设备组信息 请求参数
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaGetIotGroupInfoRequest implements Serializable {
+
+ private static final long serialVersionUID = 4913375114243384968L;
+ /**
+ * 设备组的唯一标识(必填)
+ */
+ @SerializedName("group_id")
+ private String groupId;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceInfoResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceInfoResponse.java
new file mode 100644
index 0000000000..e7bd92edf8
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceInfoResponse.java
@@ -0,0 +1,51 @@
+package cn.binarywang.wx.miniapp.bean.device;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: yanglegetuo
+ * @Date: 2025/12/22 8:51
+ * @Description: 设备组信息 响应参数
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaIotGroupDeviceInfoResponse implements Serializable {
+
+ private static final long serialVersionUID = 6615660801230308048L;
+ /**
+ * 设备组名称
+ */
+ @SerializedName("group_name")
+ private String groupName;
+ /**
+ * 设备列表
+ */
+ @SerializedName("device_list")
+ private List deviceList;
+
+ /**
+ * 设备型号id。通过注册设备获得(必填)
+ */
+ @SerializedName("model_id")
+ private String modelId;
+ /**
+ * 设备类型
+ */
+ @SerializedName("model_type")
+ private String modelType;
+
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceRequest.java
new file mode 100644
index 0000000000..02b87d6f02
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/device/WxMaIotGroupDeviceRequest.java
@@ -0,0 +1,44 @@
+package cn.binarywang.wx.miniapp.bean.device;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: yanglegetuo
+ * @Date: 2025/12/22 8:51
+ * @Description: 设备组 添加/移除 设备 请求参数
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaIotGroupDeviceRequest implements Serializable {
+ private static final long serialVersionUID = -5648997758678588138L;
+
+ /**
+ * 设备组的唯一标识(必填)
+ */
+ @SerializedName("group_id")
+ private String groupId;
+ /**
+ * 设备列表
+ */
+ @SerializedName("device_list")
+ private List deviceList;
+ /**
+ * 是否强制更新设备列表,等于 true 时将已存在其它设备组中的设备移除并添加到当前设备组,慎用。
+ */
+ @SerializedName("force_add")
+ private Boolean forceAdd;
+
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
index 76625334f4..2a7496e06e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
@@ -569,6 +569,14 @@ public interface DeviceSubscribe {
/** 发送设备订阅消息 */
String SEND_DEVICE_SUBSCRIBE_MSG_URL =
"https://api.weixin.qq.com/cgi-bin/message/device/subscribe/send";
+ /** 创建设备组 */
+ String CREATE_IOT_GROUP_ID_URL = "https://api.weixin.qq.com/wxa/business/group/createid";
+ /** 设备组添加设备 */
+ String ADD_IOT_GROUP_DEVICE_URL = "https://api.weixin.qq.com/wxa/business/group/adddevice";
+ /** 设备组删除设备 */
+ String REMOVE_IOT_GROUP_DEVICE_URL = "https://api.weixin.qq.com/wxa/business/group/removedevice";
+ /** 查询设备组信息 */
+ String GET_IOT_GROUP_INFO_URL = "https://api.weixin.qq.com/wxa/business/group/getinfo";
}
/**
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImplTest.java
index e1c4390549..838713868a 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImplTest.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImplTest.java
@@ -1,24 +1,27 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceSubscribeMessageRequest;
-import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceTicketRequest;
+import cn.binarywang.wx.miniapp.bean.device.*;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.common.collect.Lists;
-import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.GsonParser;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
+import java.util.Collections;
+import java.util.List;
+
/**
* 小程序设备订阅消息相关 测试类
*
* @author JCLee
* @since 2021-12-16 17:13:35
*/
+@Slf4j
@Test
@Guice(modules = ApiTestModule.class)
public class WxMaDeviceSubscribeServiceImplTest {
@@ -27,7 +30,7 @@ public class WxMaDeviceSubscribeServiceImplTest {
protected WxMaService wxService;
@Test
- public void testGetSnTicket() throws WxErrorException{
+ public void testGetSnTicket() throws WxErrorException {
WxMaDeviceTicketRequest wxMaDeviceTicketRequest = new WxMaDeviceTicketRequest();
wxMaDeviceTicketRequest.setModelId("11111");
wxMaDeviceTicketRequest.setSn("11111");
@@ -36,7 +39,7 @@ public void testGetSnTicket() throws WxErrorException{
}
@Test
- public void sendDeviceSubscribeMsg() throws WxErrorException{
+ public void sendDeviceSubscribeMsg() throws WxErrorException {
WxMaDeviceSubscribeMessageRequest wxMaDeviceSubscribeMessageRequest = new WxMaDeviceSubscribeMessageRequest();
wxMaDeviceSubscribeMessageRequest.setToOpenidList(Lists.newArrayList("1", "2"));
wxMaDeviceSubscribeMessageRequest.setPage("pages/index/index");
@@ -56,4 +59,46 @@ public void sendDeviceSubscribeMsg() throws WxErrorException{
wxMaDeviceSubscribeMessageRequest.setData(data);
this.wxService.getDeviceSubscribeService().sendDeviceSubscribeMsg(wxMaDeviceSubscribeMessageRequest);
}
+
+ @Test
+ public void testCreateIotGroupId() throws WxErrorException {
+ WxMaCreateIotGroupIdRequest request = new WxMaCreateIotGroupIdRequest();
+ request.setModelId("11111");
+ request.setGroupName("测试设备组");
+ String groupId = this.wxService.getDeviceSubscribeService().createIotGroupId(request);
+ System.out.println(groupId);
+ }
+
+ @Test
+ public void testGetIotGroupInfo() throws WxErrorException {
+ WxMaGetIotGroupInfoRequest request = new WxMaGetIotGroupInfoRequest();
+ request.setGroupId("12313123");
+ WxMaIotGroupDeviceInfoResponse response = this.wxService.getDeviceSubscribeService().getIotGroupInfo(request);
+ log.info("testGetIotGroupInfo = {}", response);
+ }
+
+ @Test
+ public void testAddIotGroupDevice() throws WxErrorException {
+ WxMaDeviceTicketRequest deviceTicketRequest = new WxMaDeviceTicketRequest();
+ deviceTicketRequest.setSn("2222222");
+ deviceTicketRequest.setModelId("sdfeweee");
+ WxMaIotGroupDeviceRequest request = new WxMaIotGroupDeviceRequest();
+ request.setGroupId("12313123");
+ request.setDeviceList(Collections.singletonList(deviceTicketRequest));
+ request.setForceAdd(true);
+ List response = this.wxService.getDeviceSubscribeService().addIotGroupDevice(request);
+ log.info("testAddIotGroupDevice = {}", response);
+ }
+
+ @Test
+ public void testRemoveIotGroupDevice() throws WxErrorException {
+ WxMaDeviceTicketRequest deviceTicketRequest = new WxMaDeviceTicketRequest();
+ deviceTicketRequest.setSn("2222222");
+ deviceTicketRequest.setModelId("sdfeweee");
+ WxMaIotGroupDeviceRequest request = new WxMaIotGroupDeviceRequest();
+ request.setGroupId("12313123");
+ request.setDeviceList(Collections.singletonList(deviceTicketRequest));
+ List response = this.wxService.getDeviceSubscribeService().removeIotGroupDevice(request);
+ log.info("testRemoveIotGroupDevice = {}", response);
+ }
}
From f00a71965a435e5de2f9e5ee6e1a989f46a7033e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Charon-=E5=BC=A0=E5=90=8C=E5=AD=A6?=
<38038850+xgl6@users.noreply.github.com>
Date: Wed, 24 Dec 2025 15:47:54 +0800
Subject: [PATCH 079/111] =?UTF-8?q?:art:=20=E4=BF=AE=E6=94=B9=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E5=8C=BB=E4=BF=9D=E6=94=AF=E4=BB=98=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=E7=9A=84=E8=A7=84=E8=8C=83=E6=80=A7=E9=97=AE=E9=A2=98=EF=BC=8C?=
=?UTF-8?q?=E4=B8=BB=E8=A6=81=E6=B6=89=E5=8F=8A=E5=AD=97=E6=AE=B5=E5=91=BD?=
=?UTF-8?q?=E5=90=8D=E6=A0=87=E5=87=86=E5=8C=96=E3=80=81=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=B0=83=E6=95=B4=E5=92=8C=20API=20=E6=96=B9?=
=?UTF-8?q?=E6=B3=95=E7=AD=BE=E5=90=8D=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/bean/mipay/MedInsOrdersRequest.java | 49 +++++++++++++------
.../wxpay/bean/mipay/MedInsOrdersResult.java | 26 +++++-----
.../bean/mipay/MedInsRefundNotifyRequest.java | 10 ----
.../wxpay/service/MiPayService.java | 3 +-
.../wxpay/service/impl/MiPayServiceImpl.java | 4 +-
.../service/impl/MiPayServiceImplTest.java | 3 +-
6 files changed, 51 insertions(+), 44 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersRequest.java
index 1819b328c8..b651100a59 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersRequest.java
@@ -12,6 +12,7 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
/**
* 医保自费混合收款下单请求
@@ -25,6 +26,7 @@
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
+@Accessors(chain = true)
public class MedInsOrdersRequest {
/**
@@ -282,72 +284,72 @@ public class MedInsOrdersRequest {
* 字段名:总金额
* 变量名:total_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:总金额
*
*/
@SerializedName("total_fee")
- public Long totalFee;
+ public Integer totalFee;
/**
*
* 字段名:医保统筹基金支付金额
* 变量名:med_ins_gov_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保统筹基金支付金额
*
*/
@SerializedName("med_ins_gov_fee")
- public Long medInsGovFee;
+ public Integer medInsGovFee;
/**
*
* 字段名:医保个人账户支付金额
* 变量名:med_ins_self_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保个人账户支付金额
*
*/
@SerializedName("med_ins_self_fee")
- public Long medInsSelfFee;
+ public Integer medInsSelfFee;
/**
*
* 字段名:医保其他基金支付金额
* 变量名:med_ins_other_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保其他基金支付金额
*
*/
@SerializedName("med_ins_other_fee")
- public Long medInsOtherFee;
+ public Integer medInsOtherFee;
/**
*
* 字段名:医保现金支付金额
* 变量名:med_ins_cash_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保现金支付金额
*
*/
@SerializedName("med_ins_cash_fee")
- public Long medInsCashFee;
+ public Integer medInsCashFee;
/**
*
* 字段名:微信支付现金支付金额
* 变量名:wechat_pay_cash_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:微信支付现金支付金额
*
*/
@SerializedName("wechat_pay_cash_fee")
- public Long wechatPayCashFee;
+ public Integer wechatPayCashFee;
/**
*
@@ -462,6 +464,11 @@ public class MedInsOrdersRequest {
* 支付人身份信息
*
*/
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Accessors(chain = true)
public static class PersonIdentification {
/**
*
@@ -507,18 +514,23 @@ public static class PersonIdentification {
* 现金增加明细实体
*
*/
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Accessors(chain = true)
public static class CashAddEntity {
/**
*
* 字段名:现金增加金额
* 变量名:cash_add_fee
* 必填:是
- * 类型:long
+ * 类型:Integer
* 描述:现金增加金额
*
*/
@SerializedName("cash_add_fee")
- public Long cashAddFee;
+ public Integer cashAddFee;
/**
*
@@ -538,18 +550,23 @@ public static class CashAddEntity {
* 现金减少明细实体
*
*/
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Accessors(chain = true)
public static class CashReduceEntity {
/**
*
* 字段名:现金减少金额
* 变量名:cash_reduce_fee
* 必填:是
- * 类型:long
+ * 类型:Integer
* 描述:现金减少金额
*
*/
@SerializedName("cash_reduce_fee")
- public Long cashReduceFee;
+ public Integer cashReduceFee;
/**
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java
index 4fc68e279f..9a119d8723 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsOrdersResult.java
@@ -65,7 +65,7 @@ public class MedInsOrdersResult {
*
*/
@SerializedName("med_ins_pay_status")
- public MedInsPayStatusEnum medInsPayStatusEnum;
+ public MedInsPayStatusEnum medInsPayStatus;
/**
*
@@ -320,72 +320,72 @@ public class MedInsOrdersResult {
* 字段名:总金额
* 变量名:total_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:总金额
*
*/
@SerializedName("total_fee")
- public Long totalFee;
+ public Integer totalFee;
/**
*
* 字段名:医保统筹基金支付金额
* 变量名:med_ins_gov_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保统筹基金支付金额
*
*/
@SerializedName("med_ins_gov_fee")
- public Long medInsGovFee;
+ public Integer medInsGovFee;
/**
*
* 字段名:医保个人账户支付金额
* 变量名:med_ins_self_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保个人账户支付金额
*
*/
@SerializedName("med_ins_self_fee")
- public Long medInsSelfFee;
+ public Integer medInsSelfFee;
/**
*
* 字段名:医保其他基金支付金额
* 变量名:med_ins_other_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保其他基金支付金额
*
*/
@SerializedName("med_ins_other_fee")
- public Long medInsOtherFee;
+ public Integer medInsOtherFee;
/**
*
* 字段名:医保现金支付金额
* 变量名:med_ins_cash_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:医保现金支付金额
*
*/
@SerializedName("med_ins_cash_fee")
- public Long medInsCashFee;
+ public Integer medInsCashFee;
/**
*
* 字段名:微信支付现金支付金额
* 变量名:wechat_pay_cash_fee
* 必填:否
- * 类型:long
+ * 类型:Integer
* 描述:微信支付现金支付金额
*
*/
@SerializedName("wechat_pay_cash_fee")
- public Long wechatPayCashFee;
+ public Integer wechatPayCashFee;
/**
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java
index b6e15a3644..cb935b52cd 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/mipay/MedInsRefundNotifyRequest.java
@@ -20,16 +20,6 @@
@AllArgsConstructor
public class MedInsRefundNotifyRequest {
- /**
- *
- * 字段名:医保自费混合订单号
- * 必填:是
- * 类型:string(32)
- * 描述:医保自费混合订单号
- *
- */
- private String mixTradeNo;
-
/**
*
* 字段名:医疗机构的商户号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java
index 83b75ad40c..5e2f678c16 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MiPayService.java
@@ -87,8 +87,9 @@ public interface MiPayService {
*
*
* @param request 医保退款通知请求参数
+ * @param mixTradeNo 【医保自费混合订单号】 医保自费混合订单号
* @throws WxPayException the wx pay exception
*/
- void medInsRefundNotify(MedInsRefundNotifyRequest request) throws WxPayException;
+ void medInsRefundNotify(MedInsRefundNotifyRequest request, String mixTradeNo) throws WxPayException;
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java
index 3063d7731e..769b789fa3 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImpl.java
@@ -59,8 +59,8 @@ public MiPayNotifyV3Result parseMiPayNotifyV3Result(String notifyData, Signature
}
@Override
- public void medInsRefundNotify(MedInsRefundNotifyRequest request) throws WxPayException {
- String url = String.format("%s/v3/med-ins/refunds/notify?mix_trade_no=%s", this.payService.getPayBaseUrl(), request.getMixTradeNo());
+ public void medInsRefundNotify(MedInsRefundNotifyRequest request, String mixTradeNo) throws WxPayException {
+ String url = String.format("%s/v3/med-ins/refunds/notify?mix_trade_no=%s", this.payService.getPayBaseUrl(), mixTradeNo);
this.payService.postV3(url, GSON.toJson(request));
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java
index 23c3c56816..095d355bd4 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MiPayServiceImplTest.java
@@ -131,13 +131,12 @@ public void medInsRefundNotify() throws WxPayException {
// 解析请求参数
MedInsRefundNotifyRequest request = GSON.fromJson(requestParamStr, MedInsRefundNotifyRequest.class);
- request.setMixTradeNo(mixTradeNo);
MiPayService miPayService = wxPayService.getMiPayService();
try {
// 调用医保退款通知方法,预期会失败,因为是模拟数据
- miPayService.medInsRefundNotify(request);
+ miPayService.medInsRefundNotify(request,mixTradeNo);
log.info("医保退款通知调用成功");
} catch (WxPayException e) {
// 预期会抛出异常,因为是模拟数据
From 912ba354e992afc162b124e97e1ef9fcaf1fb4cd Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Dec 2025 16:12:56 +0800
Subject: [PATCH 080/111] =?UTF-8?q?:art:=20#3821=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E4=BF=AE=E5=A4=8D=20WxCpRe?=
=?UTF-8?q?disConfigImpl.getWebhookKey()=20=E6=96=B9=E6=B3=95=E6=97=A0?=
=?UTF-8?q?=E9=99=90=E9=80=92=E5=BD=92=E8=B0=83=E7=94=A8=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/config/impl/WxCpRedisConfigImpl.java | 2 +-
.../config/impl/WxCpRedisConfigImplTest.java | 48 +++++++++++++++++++
2 files changed, 49 insertions(+), 1 deletion(-)
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImplTest.java
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
index 15c0ff6727..49cd7c4559 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
@@ -468,7 +468,7 @@ public boolean autoRefreshToken() {
@Override
public String getWebhookKey() {
- return this.getWebhookKey();
+ return this.webhookKey;
}
@Override
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImplTest.java
new file mode 100644
index 0000000000..1a7fdccbd1
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImplTest.java
@@ -0,0 +1,48 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import redis.clients.jedis.JedisPool;
+
+/**
+ * WxCpRedisConfigImpl 测试类
+ */
+public class WxCpRedisConfigImplTest {
+
+ /**
+ * 测试 getWebhookKey 方法不会导致无限递归
+ * 这个测试验证了 #issue 中提到的无限递归问题已被修复
+ */
+ @Test
+ public void testGetWebhookKeyNoInfiniteRecursion() {
+ // 使用 Mockito 创建 mock JedisPool,避免真实连接
+ JedisPool jedisPool = Mockito.mock(JedisPool.class);
+
+ WxCpRedisConfigImpl config = new WxCpRedisConfigImpl(jedisPool);
+
+ // 测试1: webhookKey 为 null 时应该返回 null,而不是抛出 StackOverflowError
+ String webhookKey = config.getWebhookKey();
+ Assert.assertNull(webhookKey, "未设置 webhookKey 时应返回 null");
+
+ // 测试2: 通过反射设置 webhookKey,然后验证能够正确获取
+ // 注意:由于 WxCpRedisConfigImpl 没有提供 setWebhookKey 方法,
+ // 我们通过反射来设置这个字段以测试 getter 的正确性
+ try {
+ java.lang.reflect.Field field = WxCpRedisConfigImpl.class.getDeclaredField("webhookKey");
+ boolean originalAccessible = field.isAccessible();
+ field.setAccessible(true);
+ try {
+ String testWebhookKey = "test-webhook-key-123";
+ field.set(config, testWebhookKey);
+
+ String retrievedKey = config.getWebhookKey();
+ Assert.assertEquals(retrievedKey, testWebhookKey, "应该返回设置的 webhookKey 值");
+ } finally {
+ field.setAccessible(originalAccessible);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ Assert.fail("反射设置 webhookKey 失败: " + e.getMessage());
+ }
+ }
+}
From 6504f5d82cf6ee87f6839b42b697201eda7f443b Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Dec 2025 16:14:20 +0800
Subject: [PATCH 081/111] =?UTF-8?q?:new:=20#3823=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E6=B7=BB=E5=8A=A0=E8=8E=B7?=
=?UTF-8?q?=E5=8F=96=E5=BA=94=E7=94=A8=E7=AE=A1=E7=90=86=E5=91=98=E5=88=97?=
=?UTF-8?q?=E8=A1=A8=E7=9A=84=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/cp/api/WxCpAgentService.java | 15 +++++
.../cp/api/impl/WxCpAgentServiceImpl.java | 18 ++++++
.../weixin/cp/constant/WxCpApiPathConsts.java | 4 ++
.../cp/api/impl/WxCpAgentServiceImplTest.java | 62 +++++++++++++++++++
4 files changed, 99 insertions(+)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java
index 9eddc0f507..05f06f1da9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java
@@ -2,6 +2,7 @@
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpAgent;
+import me.chanjar.weixin.cp.bean.WxCpTpAdmin;
import java.util.List;
@@ -52,4 +53,18 @@ public interface WxCpAgentService {
*/
List list() throws WxErrorException;
+ /**
+ *
+ * 获取应用管理员列表
+ * 第三方服务商可以用此接口获取授权企业中某个第三方应用或者代开发应用的管理员列表(不包括外部管理员),
+ * 以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
+ * 详情请见: 文档
+ *
+ *
+ * @param agentId 应用id
+ * @return admin list
+ * @throws WxErrorException the wx error exception
+ */
+ WxCpTpAdmin getAdminList(Integer agentId) throws WxErrorException;
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java
index 81628fed82..cc08d33bb1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java
@@ -11,6 +11,7 @@
import me.chanjar.weixin.cp.api.WxCpAgentService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAgent;
+import me.chanjar.weixin.cp.bean.WxCpTpAdmin;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.util.List;
@@ -65,4 +66,21 @@ public List list() throws WxErrorException {
}.getType());
}
+ @Override
+ public WxCpTpAdmin getAdminList(Integer agentId) throws WxErrorException {
+ if (agentId == null) {
+ throw new IllegalArgumentException("缺少agentid参数");
+ }
+
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("agentid", agentId);
+ String url = this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_GET_ADMIN_LIST);
+ String responseContent = this.mainService.post(url, jsonObject.toString());
+ JsonObject respObj = GsonParser.parse(responseContent);
+ if (respObj.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.CP));
+ }
+ return WxCpTpAdmin.fromJson(responseContent);
+ }
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index ad4d4f33f2..ade813ef5c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -112,6 +112,10 @@ interface Agent {
* The constant AGENT_LIST.
*/
String AGENT_LIST = "/cgi-bin/agent/list";
+ /**
+ * The constant AGENT_GET_ADMIN_LIST.
+ */
+ String AGENT_GET_ADMIN_LIST = "/cgi-bin/agent/get_admin_list";
}
/**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImplTest.java
index 07438056c3..cbd947b925 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImplTest.java
@@ -1,11 +1,14 @@
package me.chanjar.weixin.cp.api.impl;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.WxCpAgentService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAgent;
+import me.chanjar.weixin.cp.bean.WxCpTpAdmin;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
@@ -82,6 +85,20 @@ public void testList() throws WxErrorException {
assertThat(list.get(0).getSquareLogoUrl()).isNotEmpty();
}
+ /**
+ * Test get admin list.
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetAdminList() throws WxErrorException {
+ final Integer agentId = this.wxCpService.getWxCpConfigStorage().getAgentId();
+ WxCpTpAdmin adminList = this.wxCpService.getAgentService().getAdminList(agentId);
+
+ assertThat(adminList).isNotNull();
+ assertThat(adminList.getErrcode()).isEqualTo(0L);
+ }
+
/**
* The type Mock test.
*/
@@ -118,6 +135,51 @@ public void testGet() throws Exception {
}
+ /**
+ * Test get admin list.
+ *
+ * @throws Exception the exception
+ */
+ @Test
+ public void testGetAdminList() throws Exception {
+ // 构建响应JSON
+ JsonObject admin1 = new JsonObject();
+ admin1.addProperty("userid", "zhangsan");
+ admin1.addProperty("open_userid", "woAJ2GCAAAXtWyujaWJHDDGi0mACH71w");
+ admin1.addProperty("auth_type", 1);
+
+ JsonObject admin2 = new JsonObject();
+ admin2.addProperty("userid", "lisi");
+ admin2.addProperty("open_userid", "woAJ2GCAAAXtWyujaWJHDDGi0mACH72w");
+ admin2.addProperty("auth_type", 2);
+
+ JsonArray adminArray = new JsonArray();
+ adminArray.add(admin1);
+ adminArray.add(admin2);
+
+ JsonObject returnJsonObj = new JsonObject();
+ returnJsonObj.addProperty("errcode", 0);
+ returnJsonObj.addProperty("errmsg", "ok");
+ returnJsonObj.add("admin", adminArray);
+ String returnJson = returnJsonObj.toString();
+
+ JsonObject requestJson = new JsonObject();
+ requestJson.addProperty("agentid", 9);
+ final WxCpConfigStorage configStorage = new WxCpDefaultConfigImpl();
+ when(wxService.getWxCpConfigStorage()).thenReturn(configStorage);
+ when(wxService.post(configStorage.getApiUrl(WxCpApiPathConsts.Agent.AGENT_GET_ADMIN_LIST), requestJson.toString())).thenReturn(returnJson);
+ when(wxService.getAgentService()).thenReturn(new WxCpAgentServiceImpl(wxService));
+
+ WxCpAgentService wxAgentService = this.wxService.getAgentService();
+ WxCpTpAdmin adminList = wxAgentService.getAdminList(9);
+
+ assertEquals(0, adminList.getErrcode().intValue());
+ assertEquals(2, adminList.getAdmin().size());
+ assertEquals("zhangsan", adminList.getAdmin().get(0).getUserId());
+ assertEquals("woAJ2GCAAAXtWyujaWJHDDGi0mACH71w", adminList.getAdmin().get(0).getOpenUserId());
+ assertEquals(1, adminList.getAdmin().get(0).getAuthType().intValue());
+ }
+
}
}
From db15aec8de330c457f33b25562021237e345fb40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A4=8F=E6=B2=AB=E6=B5=85=E5=BE=AE=E5=87=89?=
<71374855+limingAlex1314@users.noreply.github.com>
Date: Sat, 27 Dec 2025 21:10:39 +0800
Subject: [PATCH 082/111] =?UTF-8?q?:bug:=20#3825=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=E5=88=9D?=
=?UTF-8?q?=E5=A7=8B=E5=8C=96api=20v3=E8=AF=B7=E6=B1=82=E6=97=B6=E8=AF=81?=
=?UTF-8?q?=E4=B9=A6=E5=BA=8F=E5=88=97=E5=8F=B7=E5=80=BC=E7=94=9F=E6=88=90?=
=?UTF-8?q?=E6=9D=A1=E4=BB=B6=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/github/binarywang/wxpay/config/WxPayConfig.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index f4a1c3d008..a3a9dc7a92 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -343,7 +343,7 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
certificate = (X509Certificate) objects[1];
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
- if (certificate == null && StringUtils.isBlank(this.getCertSerialNo()) && (StringUtils.isNotBlank(this.getPrivateCertPath()) || StringUtils.isNotBlank(this.getPrivateCertString())) || this.getPrivateCertContent() != null) {
+ if (certificate == null && StringUtils.isBlank(this.getCertSerialNo()) && (StringUtils.isNotBlank(this.getPrivateCertPath()) || StringUtils.isNotBlank(this.getPrivateCertString()) || this.getPrivateCertContent() != null)) {
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
this.privateCertContent, "privateCertPath")) {
certificate = PemUtils.loadCertificate(certInputStream);
From 7c6dbf15e44dcd521703f1a8c2f6b2d381b9759b Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 3 Jan 2026 16:45:03 +0800
Subject: [PATCH 083/111] =?UTF-8?q?:art:=20#3827=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E4=BF=AE=E5=A4=8D=E7=BE=A4?=
=?UTF-8?q?=E8=81=8A=E5=8F=98=E6=9B=B4=E4=BA=8B=E4=BB=B6MemChangeList?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=91=BD=E5=90=8D=E8=A7=84=E8=8C=83=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/bean/message/WxCpXmlMessage.java | 2 +-
.../cp/bean/message/WxCpXmlMessageTest.java | 74 +++++++++++++++++++
2 files changed, 75 insertions(+), 1 deletion(-)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
index d15eda8826..4001c7d0e4 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
@@ -157,7 +157,7 @@ public class WxCpXmlMessage implements Serializable {
@XStreamAlias("MemChangeList")
@XStreamConverter(value = XStreamCDataConverter.class)
- private String MemChangeList;
+ private String memChangeList;
@XStreamAlias("LastMemVer")
@XStreamConverter(value = XStreamCDataConverter.class)
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
index 94874519c0..0b2324a5f5 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
@@ -497,4 +497,78 @@ public void testIntelligentRobotMessage() {
assertEquals(wxMessage.getRobotId(), "robot_id_123");
assertEquals(wxMessage.getSessionId(), "session_id_456");
}
+
+ /**
+ * Test external chat change event
+ * 测试企业微信群聊变更事件解析 - 群成员变更场景
+ */
+ public void testExternalChatChangeEvent() {
+ // 测试群成员加入事件
+ String xmlAddMember = ""
+ + " "
+ + " "
+ + "1403610513 "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "1 "
+ + "2 "
+ + " "
+ + " ";
+ WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xmlAddMember);
+ assertEquals(wxMessage.getToUserName(), "toUser");
+ assertEquals(wxMessage.getFromUserName(), "sys");
+ assertEquals(wxMessage.getCreateTime(), Long.valueOf(1403610513L));
+ assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.EVENT);
+ assertEquals(wxMessage.getEvent(), WxCpConsts.EventType.CHANGE_EXTERNAL_CHAT);
+ assertEquals(wxMessage.getChangeType(), "update");
+ assertEquals(wxMessage.getChatId(), "wrOgQhDgAAMYQiS5ol9G7gK9JVAAAA");
+ assertEquals(wxMessage.getUpdateDetail(), "add_member");
+ assertEquals(wxMessage.getJoinScene(), "1");
+ assertEquals(wxMessage.getMemChangeCnt(), "2");
+ assertEquals(wxMessage.getMemChangeList(), "wmEJiCwAAA9KG2qlSq6rKwASSgAAAA,wmEJiCwAAA9KG2qlSq6rKwBBBBBBB");
+
+ // 测试群成员退出事件
+ String xmlDelMember = ""
+ + " "
+ + " "
+ + "1403610513 "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "1 "
+ + "1 "
+ + " "
+ + " ";
+ WxCpXmlMessage wxMessage2 = WxCpXmlMessage.fromXml(xmlDelMember);
+ assertEquals(wxMessage2.getEvent(), WxCpConsts.EventType.CHANGE_EXTERNAL_CHAT);
+ assertEquals(wxMessage2.getChangeType(), "update");
+ assertEquals(wxMessage2.getChatId(), "wrOgQhDgAAMYQiS5ol9G7gK9JVAAAA");
+ assertEquals(wxMessage2.getUpdateDetail(), "del_member");
+ assertEquals(wxMessage2.getQuitScene(), "1");
+ assertEquals(wxMessage2.getMemChangeCnt(), "1");
+ assertEquals(wxMessage2.getMemChangeList(), "wmEJiCwAAA9KG2qlSq6rKwASSgAAAA");
+
+ // 测试空MemChangeList场景(某些情况下可能没有成员变更列表)
+ String xmlNoMemChangeList = ""
+ + " "
+ + " "
+ + "1403610513 "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " ";
+ WxCpXmlMessage wxMessage3 = WxCpXmlMessage.fromXml(xmlNoMemChangeList);
+ assertEquals(wxMessage3.getEvent(), WxCpConsts.EventType.CHANGE_EXTERNAL_CHAT);
+ assertEquals(wxMessage3.getChangeType(), "update");
+ assertEquals(wxMessage3.getUpdateDetail(), "change_name");
+ // 当XML中没有MemChangeList元素时,字段应该为null而不是空字符串
+ assertThat(wxMessage3.getMemChangeList()).isNull();
+ }
}
From 42f727cd412be46a86015c34de49256d7377c87e Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Sat, 3 Jan 2026 23:30:31 +0800
Subject: [PATCH 084/111] =?UTF-8?q?:bookmark:=20=E5=8F=91=E5=B8=83=204.8.0?=
=?UTF-8?q?=20=E6=AD=A3=E5=BC=8F=E7=89=88=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/maven-publish.yml | 17 ++++++++++++++++-
pom.xml | 2 +-
solon-plugins/pom.xml | 2 +-
.../wx-java-channel-multi-solon-plugin/pom.xml | 2 +-
.../wx-java-channel-solon-plugin/pom.xml | 2 +-
.../wx-java-cp-multi-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-cp-solon-plugin/pom.xml | 2 +-
.../wx-java-miniapp-multi-solon-plugin/pom.xml | 2 +-
.../wx-java-miniapp-solon-plugin/pom.xml | 2 +-
.../wx-java-mp-multi-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-mp-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-open-solon-plugin/pom.xml | 2 +-
solon-plugins/wx-java-pay-solon-plugin/pom.xml | 2 +-
.../wx-java-qidian-solon-plugin/pom.xml | 2 +-
spring-boot-starters/pom.xml | 2 +-
.../pom.xml | 2 +-
.../wx-java-channel-spring-boot-starter/pom.xml | 2 +-
.../pom.xml | 2 +-
.../wx-java-cp-spring-boot-starter/pom.xml | 2 +-
.../pom.xml | 2 +-
.../pom.xml | 2 +-
.../wx-java-miniapp-spring-boot-starter/pom.xml | 2 +-
.../pom.xml | 2 +-
.../wx-java-mp-spring-boot-starter/pom.xml | 2 +-
.../wx-java-open-spring-boot-starter/pom.xml | 2 +-
.../wx-java-pay-spring-boot-starter/pom.xml | 2 +-
.../wx-java-qidian-spring-boot-starter/pom.xml | 2 +-
weixin-graal/pom.xml | 2 +-
weixin-java-channel/pom.xml | 2 +-
weixin-java-common/pom.xml | 2 +-
weixin-java-cp/pom.xml | 2 +-
weixin-java-miniapp/pom.xml | 2 +-
weixin-java-mp/pom.xml | 2 +-
weixin-java-open/pom.xml | 2 +-
weixin-java-pay/pom.xml | 2 +-
weixin-java-qidian/pom.xml | 2 +-
36 files changed, 51 insertions(+), 36 deletions(-)
diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml
index de68370ae1..a12c20b112 100644
--- a/.github/workflows/maven-publish.yml
+++ b/.github/workflows/maven-publish.yml
@@ -33,7 +33,22 @@ jobs:
VERSION="${BASE_VER}.B"
TAG="v${BASE_VER}"
IS_RELEASE="true"
- echo "Matched release commit: VERSION=$VERSION, TAG=$TAG"
+ echo "Matched test release commit: VERSION=$VERSION, TAG=$TAG"
+ # 检查并打tag
+ if git tag | grep -q "^$TAG$"; then
+ echo "Tag $TAG already exists."
+ else
+ git config user.name "Binary Wang"
+ git config user.email "a@binarywang.com"
+ git tag -a "$TAG" -m "Release $TAG"
+ git push origin "$TAG"
+ echo "Tag $TAG created and pushed."
+ fi
+ elif [[ "$COMMIT_MSG" =~ ^:bookmark:\ 发布\ ([0-9]+\.[0-9]+\.[0-9]+)\ 正式版本 ]]; then
+ VERSION="${BASH_REMATCH[1]}"
+ TAG="v${VERSION}"
+ IS_RELEASE="true"
+ echo "Matched formal release commit: VERSION=$VERSION, TAG=$TAG"
# 检查并打tag
if git tag | grep -q "^$TAG$"; then
echo "Tag $TAG already exists."
diff --git a/pom.xml b/pom.xml
index eba8e083a7..0a33fbdf16 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
pom
WxJava - Weixin/Wechat Java SDK
微信开发Java SDK
diff --git a/solon-plugins/pom.xml b/solon-plugins/pom.xml
index 971fc184ab..d0ca564c24 100644
--- a/solon-plugins/pom.xml
+++ b/solon-plugins/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
pom
wx-java-solon-plugins
diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml
index df721c03a3..995ecbd532 100644
--- a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-channel-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-solon-plugin/pom.xml
index 5e497a4c46..b2ca356692 100644
--- a/solon-plugins/wx-java-channel-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-channel-solon-plugin/pom.xml
@@ -3,7 +3,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml
index c39b84dc23..17e24bfe2d 100644
--- a/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml
@@ -4,7 +4,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-cp-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-solon-plugin/pom.xml
index 412db6ea98..7e6f2f8164 100644
--- a/solon-plugins/wx-java-cp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-cp-solon-plugin/pom.xml
@@ -4,7 +4,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml
index a1cab06d60..932f9244ce 100644
--- a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml
index 513de54cd7..5ad8da85e6 100644
--- a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml
@@ -4,7 +4,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml
index b65c3e4945..7c02acdfef 100644
--- a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-mp-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-solon-plugin/pom.xml
index b504caf7d8..d2507cc0db 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-mp-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-open-solon-plugin/pom.xml b/solon-plugins/wx-java-open-solon-plugin/pom.xml
index 368d4a6258..0f0527183f 100644
--- a/solon-plugins/wx-java-open-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-open-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-pay-solon-plugin/pom.xml b/solon-plugins/wx-java-pay-solon-plugin/pom.xml
index baace7b37b..7c1cb4e850 100644
--- a/solon-plugins/wx-java-pay-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-pay-solon-plugin/pom.xml
@@ -5,7 +5,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml
index f1c7c2f26b..724bdf4ac5 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml
@@ -3,7 +3,7 @@
wx-java-solon-plugins
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml
index fda5172752..1f4881e9dd 100644
--- a/spring-boot-starters/pom.xml
+++ b/spring-boot-starters/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
pom
wx-java-spring-boot-starters
diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml
index e0a53f8313..b44f597d22 100644
--- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml
index 63b74d4763..95021e2d22 100644
--- a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
index b1806a3476..550a14d2ad 100644
--- a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml
index 5b8419bf23..81f68274c5 100644
--- a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml
index 097fc7e07a..f1cc1fba13 100644
--- a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml
index e265218a37..8c8854067f 100644
--- a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
index a6f0fc2a38..bcc61b0309 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
@@ -4,7 +4,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
index 1ded1a4e16..6323ae4b6a 100644
--- a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
index 2b83d966ac..273364c9a7 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
index 8afd1b83a9..9a25cd89d7 100644
--- a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
index ff1d6b84b1..8b67ade1ea 100644
--- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml
index 2257f8253e..a0fc329434 100644
--- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml
index 8e91201f38..3a220b2888 100644
--- a/weixin-graal/pom.xml
+++ b/weixin-graal/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-graal
diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml
index b18759d728..13dd26fa98 100644
--- a/weixin-java-channel/pom.xml
+++ b/weixin-java-channel/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-channel
diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml
index d3496f923a..e5427942c4 100644
--- a/weixin-java-common/pom.xml
+++ b/weixin-java-common/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-common
diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml
index 00a6b2d06c..6c47176afc 100644
--- a/weixin-java-cp/pom.xml
+++ b/weixin-java-cp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-cp
diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml
index ac308a4164..340a15e58e 100644
--- a/weixin-java-miniapp/pom.xml
+++ b/weixin-java-miniapp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-miniapp
diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml
index 2b2e8e4210..c33aaeab86 100644
--- a/weixin-java-mp/pom.xml
+++ b/weixin-java-mp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-mp
diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml
index f354cb16f8..4c34786dad 100644
--- a/weixin-java-open/pom.xml
+++ b/weixin-java-open/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-open
diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml
index 1902f01892..9bf6d49a7a 100644
--- a/weixin-java-pay/pom.xml
+++ b/weixin-java-pay/pom.xml
@@ -5,7 +5,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
4.0.0
diff --git a/weixin-java-qidian/pom.xml b/weixin-java-qidian/pom.xml
index 5135dea3c8..293a4655cf 100644
--- a/weixin-java-qidian/pom.xml
+++ b/weixin-java-qidian/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.7.9.B
+ 4.8.0
weixin-java-qidian
From 84b5c4d2d0774f800237634e5d0336f53c004fe3 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Sun, 4 Jan 2026 09:44:40 +0800
Subject: [PATCH 085/111] =?UTF-8?q?:memo:=20=E6=9B=B4=E6=96=B0=E7=89=88?=
=?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index f1cccac4b3..79a19f10b1 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@
### 重要信息
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。
2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。
-3. **2024-12-30 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**!
+3. **2026-01-03 发布 [【4.8.0正式版】](https://mp.weixin.qq.com/s/mJoFtGc25pXCn3uZRh6Q-w)**!
5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
@@ -95,7 +95,7 @@
com.github.binarywang
(不同模块参考下文)
- 4.7.0
+ 4.8.0
```
From 521d46d95706960cf5a73f40f472001489b52edc Mon Sep 17 00:00:00 2001
From: HeCG95 <52144646+HeCG95@users.noreply.github.com>
Date: Mon, 5 Jan 2026 19:33:02 +0800
Subject: [PATCH 086/111] =?UTF-8?q?:new:=20#3828=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E6=9B=B4=E6=96=B0=E5=95=86?=
=?UTF-8?q?=E5=AE=B6=E8=BD=AC=E8=B4=A6=20API=20=E7=9A=84=E8=AF=B7=E6=B1=82?=
=?UTF-8?q?=E5=92=8C=E5=93=8D=E5=BA=94=E5=AD=97=E6=AE=B5=EF=BC=8C=E5=90=8C?=
=?UTF-8?q?=E6=AD=A5=E5=AE=98=E6=96=B9=E6=96=87=E6=A1=A3=E7=9A=84=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5=E8=B0=83=E6=95=B4=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=BD=AC?=
=?UTF-8?q?=E8=B4=A6=E5=9C=BA=E6=99=AF=E6=8A=A5=E5=A4=87=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../BusinessOperationTransferRequest.java | 44 ++++++++++++++++---
.../BusinessOperationTransferResult.java | 28 ++++++++----
.../BusinessOperationTransferExample.java | 12 ++++-
.../BusinessOperationTransferServiceTest.java | 19 ++++++--
4 files changed, 84 insertions(+), 19 deletions(-)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
index 91d9438833..60b8edaf40 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
@@ -6,8 +6,10 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
import java.io.Serializable;
+import java.util.List;
/**
* 运营工具-商家转账请求参数
@@ -37,11 +39,11 @@ public class BusinessOperationTransferRequest implements Serializable {
private String outBillNo;
/**
- * 运营工具转账场景ID
- * 必须,用于标识运营工具转账的具体业务场景
+ * 转账场景ID
+ * 必须,该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。如:1000(现金营销),1006(企业报销)等
*/
- @SerializedName("operation_scene_id")
- private String operationSceneId;
+ @SerializedName("transfer_scene_id")
+ private String transferSceneId;
/**
* 用户在直连商户应用下的用户标示
@@ -86,4 +88,36 @@ public class BusinessOperationTransferRequest implements Serializable {
*/
@SerializedName("notify_url")
private String notifyUrl;
-}
\ No newline at end of file
+
+ /**
+ * 转账场景报备信息
+ * 必须,需按转账场景准确填写报备信息,参考 转账场景报备信息字段说明
+ */
+ @SerializedName("transfer_scene_report_infos")
+ private List transferSceneReportInfos;
+
+ /**
+ * 转账场景报备信息
+ */
+ @Data
+ @Accessors(chain = true)
+ public static class TransferSceneReportInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 信息类型
+ * 必须,不能超过15个字符,商户所属转账场景下的信息类型,此字段内容为固定值,需严格按照 转账场景报备信息字段说明 传参。
+ */
+ @SerializedName("info_type")
+ private String infoType;
+
+ /**
+ * 信息内容
+ * 必须,不能超过32个字符,商户所属转账场景下的信息内容,商户可按实际业务场景自定义传参,需严格按照 转账场景报备信息字段说明 传参。
+ */
+ @SerializedName("info_content")
+ private String infoContent;
+
+ }
+
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
index a380d6133e..91771b43e1 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
@@ -31,15 +31,27 @@ public class BusinessOperationTransferResult implements Serializable {
private String transferBillNo;
/**
- * 转账状态
- * WAIT_PAY:等待确认
- * PROCESSING:转账中
- * SUCCESS:转账成功
- * FAIL:转账失败
- * REFUND:已退款
+ * 单据状态
+ * 商家转账订单状态
+ * ACCEPTED:转账已受理,可原单重试(非终态)。
+ * PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试(非终态)。
+ * WAIT_USER_CONFIRM: 待收款用户确认,当前转账单据资金已锁定,可拉起微信收款确认页面进行收款确认(非终态)。
+ * TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款(非终态)。
+ * SUCCESS: 转账成功,表示转账单据已成功(终态)。
+ * FAIL: 转账失败,表示该笔转账单据已失败。若需重新向用户转账,请重新生成单据并再次发起(终态)。
+ * CANCELING: 转账撤销中,商户撤销请求受理成功,该笔转账正在撤销中,需查单确认撤销的转账单据状态(非终态)。
+ * CANCELLED: 转账撤销完成,代表转账单据已撤销成功(终态)。
*/
- @SerializedName("transfer_state")
- private String transferState;
+ @SerializedName("state")
+ private String state;
+
+ /**
+ * 跳转领取页面的package信息
+ * 跳转微信支付收款页的package信息, APP调起用户确认收款 或者 JSAPI调起用户确认收款 时需要使用的参数。仅当转账单据状态为WAIT_USER_CONFIRM时返回。
+ * 单据创建后,用户24小时内不领取将过期关闭,建议拉起用户确认收款页面前,先查单据状态:如单据状态为WAIT_USER_CONFIRM,可用之前的package信息拉起;单据到终态时需更换单号重新发起转账。
+ */
+ @SerializedName("package_info")
+ private String packageInfo;
/**
* 发起转账的时间
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
index d11738816b..117395ba62 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
@@ -8,6 +8,8 @@
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import java.util.Arrays;
+
/**
* 运营工具-商家转账API使用示例
*
@@ -41,10 +43,15 @@ public void init() {
public void createOperationTransferExample() {
try {
// 构建转账请求
+ BusinessOperationTransferRequest.TransferSceneReportInfo reportInfo = new BusinessOperationTransferRequest.TransferSceneReportInfo();
+ reportInfo.setInfoType("活动名称");
+ reportInfo.setInfoContent("新会员有礼");
+
BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
.appid("your_app_id") // 应用ID
.outBillNo("OT" + System.currentTimeMillis()) // 商户转账单号
- .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID
+ .transferSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID
+ .transferSceneReportInfos(Arrays.asList(reportInfo)) // 转账场景报备信息
.openid("user_openid") // 用户openid
.userName("张三") // 用户姓名(可选)
.transferAmount(100) // 转账金额,单位分
@@ -59,7 +66,8 @@ public void createOperationTransferExample() {
System.out.println("转账成功!");
System.out.println("商户单号: " + result.getOutBillNo());
System.out.println("微信转账单号: " + result.getTransferBillNo());
- System.out.println("转账状态: " + result.getTransferState());
+ System.out.println("单据状态: " + result.getState());
+ System.out.println("跳转领取页面的package信息: " + result.getPackageInfo());
System.out.println("创建时间: " + result.getCreateTime());
} catch (WxPayException e) {
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
index 4107be4347..672483f96b 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
@@ -7,6 +7,8 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
+import java.util.Arrays;
+
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -36,10 +38,17 @@ public void testServiceInitialization() {
@Test
public void testRequestBuilder() {
+
+ // 构建转账请求
+ BusinessOperationTransferRequest.TransferSceneReportInfo reportInfo = new BusinessOperationTransferRequest.TransferSceneReportInfo();
+ reportInfo.setInfoType("test_info_type");
+ reportInfo.setInfoContent("test_info_content");
+
BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
.appid("test_app_id")
.outBillNo("OT" + System.currentTimeMillis())
- .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING)
+ .transferSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING)
+ .transferSceneReportInfos(Arrays.asList(reportInfo))
.openid("test_openid")
.transferAmount(100)
.transferRemark("测试转账")
@@ -47,7 +56,7 @@ public void testRequestBuilder() {
.build();
assertThat(request.getAppid()).isEqualTo("test_app_id");
- assertThat(request.getOperationSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING);
+ assertThat(request.getTransferSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING);
assertThat(request.getTransferAmount()).isEqualTo(100);
assertThat(request.getTransferRemark()).isEqualTo("测试转账");
}
@@ -77,11 +86,13 @@ public void testResultClasses() {
BusinessOperationTransferResult result = new BusinessOperationTransferResult();
result.setOutBillNo("test_out_bill_no");
result.setTransferBillNo("test_transfer_bill_no");
- result.setTransferState("SUCCESS");
+ result.setState("SUCCESS");
+ result.setPackageInfo("test_package_info");
assertThat(result.getOutBillNo()).isEqualTo("test_out_bill_no");
assertThat(result.getTransferBillNo()).isEqualTo("test_transfer_bill_no");
- assertThat(result.getTransferState()).isEqualTo("SUCCESS");
+ assertThat(result.getState()).isEqualTo("SUCCESS");
+ assertThat(result.getPackageInfo()).isEqualTo("test_package_info");
BusinessOperationTransferQueryResult queryResult = new BusinessOperationTransferQueryResult();
queryResult.setOperationSceneId("2001");
From 987001214ddf126d748853445d0b8859abd34258 Mon Sep 17 00:00:00 2001
From: buaazyl
Date: Tue, 6 Jan 2026 11:19:28 +0800
Subject: [PATCH 087/111] =?UTF-8?q?:art:=20#3830=20=E3=80=90=E5=85=AC?=
=?UTF-8?q?=E5=85=B1=E9=97=AE=E9=A2=98=E3=80=91=E4=BF=AE=E5=A4=8D=E4=BD=BF?=
=?UTF-8?q?=E7=94=A8HttpComponents=E6=97=B6=E4=B8=8D=E9=85=8D=E7=BD=AEprox?=
=?UTF-8?q?y=20password=E5=90=AF=E5=8A=A8=E6=8A=A5=E9=94=99=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/impl/WxChannelServiceHttpComponentsImpl.java | 3 +--
.../weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java | 3 ++-
.../service/impl/WxCpCgServiceHttpComponentsImpl.java | 3 ++-
.../tp/service/impl/WxCpTpServiceHttpComponentsImpl.java | 8 ++++----
.../weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java | 3 ++-
.../api/impl/WxQidianServiceHttpComponentsImpl.java | 6 ++++--
6 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java
index 6cf2d38503..f4cbb04755 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java
@@ -5,7 +5,6 @@
import me.chanjar.weixin.channel.config.WxChannelConfig;
import me.chanjar.weixin.channel.util.JsonUtils;
import me.chanjar.weixin.common.util.http.HttpClientType;
-import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
@@ -41,7 +40,7 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(config.getHttpProxyHost())
.httpProxyPort(config.getHttpProxyPort())
.httpProxyUsername(config.getHttpProxyUsername())
- .httpProxyPassword(config.getHttpProxyPassword().toCharArray());
+ .httpProxyPassword(config.getHttpProxyPassword() == null ? null : config.getHttpProxyPassword().toCharArray());
if (config.getHttpProxyHost() != null && config.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(config.getHttpProxyHost(), config.getHttpProxyPort());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
index 92fd2dbd9b..4b6a1e36ff 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
@@ -82,7 +82,8 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
.httpProxyPort(this.configStorage.getHttpProxyPort())
.httpProxyUsername(this.configStorage.getHttpProxyUsername())
- .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPassword(this.configStorage.getHttpProxyPassword() == null ? null :
+ this.configStorage.getHttpProxyPassword().toCharArray());
if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
index d5c60ad037..4c8fa5d131 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
@@ -35,7 +35,8 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
.httpProxyPort(this.configStorage.getHttpProxyPort())
.httpProxyUsername(this.configStorage.getHttpProxyUsername())
- .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPassword(this.configStorage.getHttpProxyPassword() == null ? null :
+ this.configStorage.getHttpProxyPassword().toCharArray());
if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
index bba597a3ee..44b5fd8693 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
@@ -10,7 +10,6 @@
import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
import me.chanjar.weixin.common.util.json.GsonParser;
-import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
@@ -87,9 +86,10 @@ public void initHttp() {
HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
- .httpProxyPort(this.configStorage.getHttpProxyPort())
- .httpProxyUsername(this.configStorage.getHttpProxyUsername())
- .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPort(this.configStorage.getHttpProxyPort())
+ .httpProxyUsername(this.configStorage.getHttpProxyUsername())
+ .httpProxyPassword(this.configStorage.getHttpProxyPassword() == null ? null :
+ this.configStorage.getHttpProxyPassword().toCharArray());
if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
index bbf065acfc..c54202ad2f 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
@@ -51,7 +51,8 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
.httpProxyPort(configStorage.getHttpProxyPort())
.httpProxyUsername(configStorage.getHttpProxyUsername())
- .httpProxyPassword(configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null :
+ configStorage.getHttpProxyPassword().toCharArray());
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
index a5cc23f0a2..90486efc8f 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
@@ -48,8 +48,10 @@ public void initHttp() {
HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
- .httpProxyPort(configStorage.getHttpProxyPort()).httpProxyUsername(configStorage.getHttpProxyUsername())
- .httpProxyPassword(configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPort(configStorage.getHttpProxyPort())
+ .httpProxyUsername(configStorage.getHttpProxyUsername())
+ .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null :
+ configStorage.getHttpProxyPassword().toCharArray());
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
From e46da01cc802d0d925a89db6427c415dd9347e56 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 6 Jan 2026 11:20:58 +0800
Subject: [PATCH 088/111] =?UTF-8?q?:art:=20#3824=20=E3=80=90=E5=9F=BA?=
=?UTF-8?q?=E7=A1=80=E6=9E=B6=E6=9E=84=E3=80=91=E5=8D=87=E7=BA=A7=E5=88=B0?=
=?UTF-8?q?=20Apache=20HttpClient=205.x=20=E4=BD=9C=E4=B8=BA=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=20HTTP=20=E5=AE=A2=E6=88=B7=E7=AB=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 46 ++++
docs/HTTPCLIENT_UPGRADE_GUIDE.md | 203 ++++++++++++++++++
pom.xml | 6 +-
.../wxjava/channel/enums/HttpClientType.java | 8 +-
.../properties/WxChannelProperties.java | 2 +-
.../wxjava/miniapp/enums/HttpClientType.java | 4 +
.../solon/wxjava/mp/enums/HttpClientType.java | 4 +
.../wxjava/mp/properties/WxMpProperties.java | 2 +-
.../wxjava/qidian/enums/HttpClientType.java | 4 +
.../qidian/properties/WxQidianProperties.java | 2 +-
.../AbstractWxChannelConfiguration.java | 4 +
.../wxjava/channel/enums/HttpClientType.java | 6 +-
.../wxjava/channel/enums/HttpClientType.java | 8 +-
.../properties/WxChannelProperties.java | 2 +-
.../wxjava/miniapp/enums/HttpClientType.java | 6 +-
.../wx-java-mp-spring-boot-starter/README.md | 2 +-
.../wxjava/mp/properties/WxMpProperties.java | 2 +-
.../wxjava/qidian/enums/HttpClientType.java | 4 +
.../qidian/properties/WxQidianProperties.java | 2 +-
weixin-java-common/pom.xml | 20 +-
20 files changed, 313 insertions(+), 24 deletions(-)
create mode 100644 docs/HTTPCLIENT_UPGRADE_GUIDE.md
diff --git a/README.md b/README.md
index 79a19f10b1..54af600beb 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,52 @@
- **微信开放平台**(`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理
+---------------------------------
+### HTTP 客户端支持
+
+本项目同时支持多种 HTTP 客户端实现,默认推荐使用 **Apache HttpClient 5.x**(最新稳定版本)。
+
+#### 支持的 HTTP 客户端类型
+
+| HTTP 客户端 | 说明 | 配置值 | 推荐程度 |
+|------------|------|--------|---------|
+| Apache HttpClient 5.x | Apache HttpComponents Client 5.x,最新版本 | `HttpComponents` | ⭐⭐⭐⭐⭐ 推荐 |
+| Apache HttpClient 4.x | Apache HttpClient 4.x,向后兼容 | `HttpClient` | ⭐⭐⭐⭐ 兼容 |
+| OkHttp | Square OkHttp 客户端 | `OkHttp` | ⭐⭐⭐ 可选 |
+| Jodd-http | Jodd 轻量级 HTTP 客户端 | `JoddHttp` | ⭐⭐ 可选 |
+
+#### 配置方式
+
+**Spring Boot 配置示例:**
+
+```properties
+# 使用 HttpClient 5.x(推荐,MP/CP/Channel/QiDian 模块默认)
+wx.mp.config-storage.http-client-type=HttpComponents
+
+# 使用 HttpClient 4.x(兼容模式,MiniApp 模块默认)
+wx.mp.config-storage.http-client-type=HttpClient
+
+# 使用 OkHttp
+wx.mp.config-storage.http-client-type=OkHttp
+
+# 使用 Jodd-http
+wx.mp.config-storage.http-client-type=JoddHttp
+```
+
+**注意**:如果使用 Multi-Starter(如 `wx-java-mp-multi-spring-boot-starter`),枚举值需使用大写下划线格式:
+```properties
+# Multi-Starter 配置格式
+wx.mp.config-storage.http-client-type=HTTP_COMPONENTS # 注意使用大写下划线
+```
+
+**注意事项:**
+1. **MiniApp 模块**已提供 `HttpComponents`(HttpClient 5.x)类型的配置选项,但当前默认仍为 HttpClient 4.x;如需启用 HttpClient 5.x,请确保所使用的集成模块(如 `wx-java-miniapp-spring-boot-starter`、`wx-java-miniapp-solon-plugin`)版本已支持该选项
+2. **MP、Channel、QiDian 模块**已完整支持 HttpClient 5.x,默认推荐使用
+3. **CP 模块**的支持情况取决于具体使用的 Starter 版本,请参考对应模块文档
+4. 如需使用 OkHttp 或 Jodd-http,需在项目中添加对应的依赖(scope为provided)
+5. HttpClient 4.x 和 HttpClient 5.x 可以共存,按需配置即可
+
+
---------------------------------
### 版本说明
diff --git a/docs/HTTPCLIENT_UPGRADE_GUIDE.md b/docs/HTTPCLIENT_UPGRADE_GUIDE.md
new file mode 100644
index 0000000000..08726fa03f
--- /dev/null
+++ b/docs/HTTPCLIENT_UPGRADE_GUIDE.md
@@ -0,0 +1,203 @@
+# HttpClient 升级指南
+
+## 概述
+
+从 WxJava 4.7.x 版本开始,项目开始支持并推荐使用 **Apache HttpClient 5.x**(HttpComponents Client 5),同时保持对 HttpClient 4.x 的向后兼容。
+
+## 为什么升级?
+
+1. **Apache HttpClient 5.x 是最新稳定版本**:提供更好的性能和更多的功能
+2. **HttpClient 4.x 已经进入维护模式**:不再积极开发新功能
+3. **更好的安全性**:HttpClient 5.x 包含最新的安全更新和改进
+4. **向前兼容**:为未来的开发做好准备
+
+## 支持的 HTTP 客户端
+
+| HTTP 客户端 | 版本 | 配置值 | 状态 | 说明 |
+|------------|------|--------|------|------|
+| Apache HttpClient 5.x | 5.5 | `HttpComponents` | ⭐ 推荐 | 最新稳定版本 |
+| Apache HttpClient 4.x | 4.5.13 | `HttpClient` | ✅ 支持 | 向后兼容 |
+| OkHttp | 4.12.0 | `OkHttp` | ✅ 支持 | 需自行添加依赖 |
+| Jodd-http | 6.3.0 | `JoddHttp` | ✅ 支持 | 需自行添加依赖 |
+
+## 模块支持情况
+
+| 模块 | HttpClient 5.x 支持 | 默认客户端 |
+|------|-------------------|-----------|
+| weixin-java-mp(公众号) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-cp(企业微信) | ⚠️ 视集成方式而定 | 参考对应 starter 配置 |
+| weixin-java-channel(视频号) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-qidian(企点) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-miniapp(小程序) | ✅ 是 | HttpClient (4.x) |
+| weixin-java-pay(支付) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-open(开放平台) | ✅ 是 | HttpComponents (5.x) |
+
+**注意**:
+- **weixin-java-miniapp 模块**已在核心 SDK 中提供 HttpClient 5.x(`HttpComponents`)支持,但默认仍使用 HttpClient 4.x(`HttpClient`)。如需启用 HttpClient 5.x,可通过配置 `http-client-type=HttpComponents` 显式指定。
+- **weixin-java-cp 模块**的支持情况取决于具体使用的 Starter 版本,请参考对应模块文档。
+
+## 对现有项目的影响
+
+### 对新项目
+- **无需任何修改**,直接使用最新版本即可
+- 支持 HttpClient 5.x 的模块会自动使用 HttpComponents (5.x)
+
+### 对现有项目
+- **向后兼容**:不需要修改任何代码
+- HttpClient 4.x 依然可用,项目同时包含两个版本的依赖
+- 如果希望继续使用 HttpClient 4.x,只需在配置中显式指定
+
+## 迁移步骤
+
+### 1. 更新 WxJava 版本
+
+在 `pom.xml` 中更新版本:
+
+```xml
+
+ com.github.binarywang
+ weixin-java-mp
+ 最新版本
+
+```
+
+### 2. 检查配置(可选)
+
+#### Spring Boot 项目
+
+在 `application.properties` 或 `application.yml` 中:
+
+```properties
+# 使用 HttpClient 5.x(推荐,无需配置,已经是默认值)
+wx.mp.config-storage.http-client-type=HttpComponents
+
+# 或者继续使用 HttpClient 4.x
+wx.mp.config-storage.http-client-type=HttpClient
+```
+
+#### 纯 Java 项目
+
+```java
+// 使用 HttpClient 5.x(推荐)
+WxMpService wxMpService = new WxMpServiceHttpComponentsImpl();
+
+// 或者继续使用 HttpClient 4.x
+WxMpService wxMpService = new WxMpServiceHttpClientImpl();
+```
+
+### 3. 测试应用
+
+升级后,建议进行全面测试以确保一切正常工作。
+
+## 常见问题
+
+### Q: 升级后会不会破坏现有代码?
+A: 不会。项目保持完全向后兼容,HttpClient 4.x 的所有实现都保持不变。
+
+### Q: 我需要修改代码吗?
+A: 大多数情况下不需要。如果希望继续使用 HttpClient 4.x,只需在配置中指定 `http-client-type=HttpClient` 即可。
+
+### Q: MiniApp 模块支持 HttpClient 5.x 吗?
+A: 支持。MiniApp 模块在核心 SDK 中已经提供了基于 HttpClient 5.x(`HttpComponents`)的支持,但默认仍会使用 HttpClient 4.x(`HttpClient`)以保持向后兼容。如果你使用的是框架集成(例如 Spring Boot Starter 或 Solon Plugin),可以通过显式配置 `http-client-type=HttpComponents` 来启用 HttpClient 5.x。
+
+### Q: 我可以在同一个项目中同时使用两个版本吗?
+A: 可以。不同的模块可以配置使用不同的 HTTP 客户端。例如,MP 模块使用 HttpClient 5.x,MiniApp 模块默认使用 HttpClient 4.x,但也可以按需配置为 HttpClient 5.x。
+
+### Q: 如何排除不需要的依赖?
+A: 如果只想使用一个版本,可以在 `pom.xml` 中排除另一个:
+
+```xml
+
+ com.github.binarywang
+ weixin-java-mp
+ 最新版本
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpmime
+
+
+
+```
+
+## 配置参考
+
+### Spring Boot 完整配置示例
+
+```properties
+# 公众号配置
+wx.mp.app-id=your_app_id
+wx.mp.secret=your_secret
+wx.mp.token=your_token
+wx.mp.aes-key=your_aes_key
+
+# HTTP 客户端配置
+wx.mp.config-storage.http-client-type=HttpComponents # HttpComponents, HttpClient, OkHttp, JoddHttp
+
+# HTTP 代理配置(可选)
+wx.mp.config-storage.http-proxy-host=proxy.example.com
+wx.mp.config-storage.http-proxy-port=8080
+wx.mp.config-storage.http-proxy-username=proxy_user
+wx.mp.config-storage.http-proxy-password=proxy_pass
+
+# 超时配置(可选)
+wx.mp.config-storage.connection-timeout=5000
+wx.mp.config-storage.so-timeout=5000
+wx.mp.config-storage.connection-request-timeout=5000
+```
+
+## 技术细节
+
+### HttpClient 4.x 与 5.x 的主要区别
+
+1. **包名变更**:
+ - HttpClient 4.x: `org.apache.http.*`
+ - HttpClient 5.x: `org.apache.hc.client5.*`, `org.apache.hc.core5.*`
+
+2. **API 改进**:
+ - HttpClient 5.x 提供更现代的 API 设计
+ - 更好的异步支持
+ - 改进的连接池管理
+
+3. **性能优化**:
+ - HttpClient 5.x 包含多项性能优化
+ - 更好的资源管理
+
+### 项目中的实现
+
+WxJava 项目通过策略模式支持多种 HTTP 客户端:
+
+```
+weixin-java-common/
+├── util/http/
+│ ├── apache/ # HttpClient 4.x 实现
+│ ├── hc/ # HttpClient 5.x (HttpComponents) 实现
+│ ├── okhttp/ # OkHttp 实现
+│ └── jodd/ # Jodd-http 实现
+```
+
+每个模块都有对应的 Service 实现类:
+- `*ServiceHttpClientImpl` - 使用 HttpClient 4.x
+- `*ServiceHttpComponentsImpl` - 使用 HttpClient 5.x
+- `*ServiceOkHttpImpl` - 使用 OkHttp
+- `*ServiceJoddHttpImpl` - 使用 Jodd-http
+
+## 反馈与支持
+
+如果在升级过程中遇到问题,请:
+
+1. 查看 [项目 Wiki](https://github.com/binarywang/WxJava/wiki)
+2. 在 [GitHub Issues](https://github.com/binarywang/WxJava/issues) 中搜索或提交问题
+3. 加入技术交流群(见 README.md)
+
+## 总结
+
+- ✅ **推荐使用 HttpClient 5.x**:性能更好,功能更强
+- ✅ **向后兼容**:可以继续使用 HttpClient 4.x
+- ✅ **灵活配置**:支持多种 HTTP 客户端,按需选择
+- ✅ **平滑迁移**:无需修改代码,仅需配置即可
diff --git a/pom.xml b/pom.xml
index 0a33fbdf16..5740a27790 100644
--- a/pom.xml
+++ b/pom.xml
@@ -136,6 +136,7 @@
UTF-8
4.5.13
+ 5.5
9.4.57.v20241219
@@ -157,13 +158,14 @@
4.12.0
provided
+
org.apache.httpcomponents.client5
httpclient5
- 5.5
- provided
+ ${httpclient5.version}
+
org.apache.httpcomponents
httpclient
diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java
index 0c00dbcaa7..5614f63e86 100644
--- a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java
@@ -7,7 +7,11 @@
*/
public enum HttpClientType {
/**
- * HttpClient
+ * HttpClient.
*/
- HttpClient
+ HttpClient,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java
index 6562a02e9d..89b81b7d9f 100644
--- a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java
+++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java
@@ -73,7 +73,7 @@ public static class ConfigStorage {
/**
* http客户端类型
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机
diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java
index a4475a02c7..d116a30cf6 100644
--- a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java
index 9b1a8ccbf4..2858d77e43 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java
index cda0aa88e7..0dcc6b41d3 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java
@@ -79,7 +79,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java
index 0b94821da7..5729ab8fda 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java
index 67c4dba543..e99f882e6f 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java
+++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java
@@ -74,7 +74,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java
index 3462bbc25e..e2f9f87f5a 100644
--- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java
+++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java
@@ -9,6 +9,7 @@
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.channel.api.WxChannelService;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpClientImpl;
+import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpComponentsImpl;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl;
import me.chanjar.weixin.channel.config.WxChannelConfig;
import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl;
@@ -84,6 +85,9 @@ public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig, WxChan
case HTTP_CLIENT:
wxChannelService = new WxChannelServiceHttpClientImpl();
break;
+ case HTTP_COMPONENTS:
+ wxChannelService = new WxChannelServiceHttpComponentsImpl();
+ break;
default:
wxChannelService = new WxChannelServiceImpl();
break;
diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
index 6ca09354a3..adc8a2b52b 100644
--- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
@@ -8,7 +8,7 @@
*/
public enum HttpClientType {
/**
- * HttpClient
+ * HttpClient.
*/
HTTP_CLIENT,
// WxChannelServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式
@@ -16,4 +16,8 @@ public enum HttpClientType {
// * OkHttp.
// */
// OK_HTTP,
+ /**
+ * HttpComponents.
+ */
+ HTTP_COMPONENTS,
}
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
index 63a7bf0c24..e4b3f3ad16 100644
--- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
@@ -7,7 +7,11 @@
*/
public enum HttpClientType {
/**
- * HttpClient
+ * HttpClient.
*/
- HttpClient
+ HttpClient,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java
index 43c35fbd1d..f43d297e0b 100644
--- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java
+++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java
@@ -85,7 +85,7 @@ public static class ConfigStorage {
/**
* http客户端类型
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
index b3e4b464fe..48549e4399 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
@@ -8,7 +8,7 @@
*/
public enum HttpClientType {
/**
- * HttpClient.
+ * HttpClient (Apache HttpClient 4.x).
*/
HttpClient,
/**
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents (Apache HttpClient 5.x).
+ */
+ HttpComponents,
}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md
index 3e14f499d9..091912cfad 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md
@@ -27,7 +27,7 @@
#wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379
#wx.mp.config-storage.redis.sentinel-name=mymaster
# http客户端配置
- wx.mp.config-storage.http-client-type=httpclient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp
+ wx.mp.config-storage.http-client-type=HttpComponents # http客户端类型: HttpComponents(Apache HttpClient 5.x,推荐), HttpClient(Apache HttpClient 4.x), OkHttp, JoddHttp
wx.mp.config-storage.http-proxy-host=
wx.mp.config-storage.http-proxy-port=
wx.mp.config-storage.http-proxy-username=
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java
index 377fb5b6ab..a6c6e3b6bd 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java
@@ -80,7 +80,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java
index 1a927211cc..04589a911b 100644
--- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java
index ddecefb7e2..bec5dfcce0 100644
--- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java
+++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java
@@ -72,7 +72,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml
index e5427942c4..9032af97da 100644
--- a/weixin-java-common/pom.xml
+++ b/weixin-java-common/pom.xml
@@ -24,20 +24,13 @@
okhttp
provided
+
org.apache.httpcomponents.client5
httpclient5
- provided
-
- org.slf4j
- slf4j-api
-
-
- com.thoughtworks.xstream
- xstream
-
+
org.apache.httpcomponents
httpclient
@@ -52,6 +45,15 @@
org.apache.httpcomponents
httpmime
+
+
+ org.slf4j
+ slf4j-api
+
+
+ com.thoughtworks.xstream
+ xstream
+
org.slf4j
jcl-over-slf4j
From 37c2db9672f9b84ba33c4618571d088717d91373 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 6 Jan 2026 13:01:40 +0800
Subject: [PATCH 089/111] =?UTF-8?q?:art:=20#3832=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=20WxSign?=
=?UTF-8?q?QueryResult=20=E4=B8=AD=20contract=5Fexpired=5Ftime=20=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5=E8=A7=A3=E6=9E=90=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/bean/result/WxSignQueryResult.java | 2 +-
.../bean/result/WxSignQueryResultTest.java | 125 ++++++++++++++++++
2 files changed, 126 insertions(+), 1 deletion(-)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
index 5241597194..af19aec60a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
@@ -112,7 +112,7 @@ protected void loadXml(Document d) {
contractDisplayAccount = readXmlString(d, "contract_display_account");
contractState = readXmlInteger(d, "contract_state");
contractSignedTime = readXmlString(d, "contract_signed_time");
- contractExpiredTime = readXmlString(d, "contrace_Expired_time");
+ contractExpiredTime = readXmlString(d, "contract_expired_time");
contractTerminatedTime = readXmlString(d, "contract_terminated_time");
contractTerminatedMode = readXmlInteger(d, "contract_termination_mode");
contractTerminationRemark = readXmlString(d, "contract_termination_remark");
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java
new file mode 100644
index 0000000000..52df2b6e2b
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java
@@ -0,0 +1,125 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.github.binarywang.wxpay.util.XmlConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * WxSignQueryResult 单元测试
+ *
+ * @author Binary Wang
+ */
+public class WxSignQueryResultTest {
+
+ /**
+ * 测试 XML 解析,特别是 contract_expired_time 字段
+ */
+ @Test
+ public void testFromXML() {
+ /*
+ * xml样例字符串来自于官方文档
+ * https://pay.weixin.qq.com/doc/v2/merchant/4011987640
+ */
+ String xmlString = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " 203 \n" +
+ " 66 \n" +
+ " \n" +
+ " 123 \n" +
+ " \n" +
+ " \n" +
+ " 1 \n" +
+ " 2015-07-01 10:00:00 \n" +
+ " 2015-07-01 10:00:00 \n" +
+ " 2015-07-01 10:00:00 \n" +
+ " 3 \n" +
+ " \n" +
+ " 0 \n" +
+ " \n" +
+ " \n" +
+ " ";
+
+ // 启用 fastMode 以覆盖 WxSignQueryResult#loadXml 分支
+ XmlConfig.fastMode = true;
+ try {
+ WxSignQueryResult result = WxSignQueryResult.fromXML(xmlString, WxSignQueryResult.class);
+
+ // 验证基本字段
+ Assert.assertEquals(result.getReturnCode(), "SUCCESS");
+ Assert.assertEquals(result.getResultCode(), "SUCCESS");
+ Assert.assertEquals(result.getMchId(), "80000000");
+ Assert.assertEquals(result.getAppid(), "wx426b3015555b46be");
+
+ // 验证签约相关字段
+ Assert.assertEquals(result.getContractId(), "203");
+ Assert.assertEquals(result.getPlanId(), "66");
+ Assert.assertEquals(result.getOpenId(), "oHZx6uMbIG46UXQ3SKxVYEgw1LZs");
+ Assert.assertEquals(result.getRequestSerial().longValue(), 123L);
+ Assert.assertEquals(result.getContractCode(), "1005");
+ Assert.assertEquals(result.getContractDisplayAccount(), "test");
+ Assert.assertEquals(result.getContractState().intValue(), 1);
+
+ // 重点测试时间字段,特别是 contract_expired_time
+ Assert.assertEquals(result.getContractSignedTime(), "2015-07-01 10:00:00");
+ Assert.assertEquals(result.getContractExpiredTime(), "2015-07-01 10:00:00");
+ Assert.assertEquals(result.getContractTerminatedTime(), "2015-07-01 10:00:00");
+
+ // 验证其他字段
+ Assert.assertEquals(result.getContractTerminatedMode().intValue(), 3);
+ Assert.assertEquals(result.getContractTerminationRemark(), "delete ....");
+ } finally {
+ // 恢复默认值
+ XmlConfig.fastMode = false;
+ }
+ }
+
+ /**
+ * 测试 XML 解析 - 只包含必填字段
+ */
+ @Test
+ public void testFromXML_RequiredFieldsOnly() {
+ String xmlString = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " Wx15463511252015071056489715 \n" +
+ " 123 \n" +
+ " 1695 \n" +
+ " \n" +
+ " \n" +
+ " 0 \n" +
+ " 2015-07-01 10:00:00 \n" +
+ " 2016-07-01 10:00:00 \n" +
+ " \n" +
+ " \n" +
+ " ";
+
+ // 启用 fastMode 以覆盖 WxSignQueryResult#loadXml 分支
+ XmlConfig.fastMode = true;
+ try {
+ WxSignQueryResult result = WxSignQueryResult.fromXML(xmlString, WxSignQueryResult.class);
+
+ // 验证必填字段
+ Assert.assertEquals(result.getReturnCode(), "SUCCESS");
+ Assert.assertEquals(result.getResultCode(), "SUCCESS");
+ Assert.assertEquals(result.getContractId(), "Wx15463511252015071056489715");
+ Assert.assertEquals(result.getPlanId(), "123");
+ Assert.assertEquals(result.getContractState().intValue(), 0);
+
+ // 验证 contract_expired_time 字段能正确解析
+ Assert.assertEquals(result.getContractExpiredTime(), "2016-07-01 10:00:00");
+
+ // 验证非必填字段为 null
+ Assert.assertNull(result.getContractTerminatedTime());
+ Assert.assertNull(result.getContractTerminatedMode());
+ Assert.assertNull(result.getContractTerminationRemark());
+ } finally {
+ // 恢复默认值
+ XmlConfig.fastMode = false;
+ }
+ }
+}
From 51d2ed7a3c707d6269c686dab15fcf0a27183c80 Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Tue, 6 Jan 2026 16:51:30 +0800
Subject: [PATCH 090/111] =?UTF-8?q?:art:=20=E6=89=B9=E9=87=8F=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=E9=83=A8=E5=88=86javadoc=E9=87=8C=E7=9A=84=E9=94=99?=
=?UTF-8?q?=E8=AF=AF=E6=A0=87=E7=AD=BE=E6=88=96=E4=B8=8D=E8=A7=84=E8=8C=83?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../chanjar/weixin/common/api/WxConsts.java | 2 +-
.../bean/oauth2/WxOAuth2AccessToken.java | 9 +-
.../weixin/common/error/WxCpErrorMsgEnum.java | 4 +-
.../chanjar/weixin/common/error/WxError.java | 4 +-
.../weixin/common/error/WxMaErrorMsgEnum.java | 47 +++----
.../common/error/WxOpenErrorMsgEnum.java | 24 ++--
.../weixin/common/service/WxOcrService.java | 4 +-
.../session/InternalSessionManager.java | 5 +-
.../chanjar/weixin/common/util/SignUtils.java | 1 +
.../weixin/common/util/crypto/SHA1.java | 5 +-
.../common/util/crypto/WxCryptUtil.java | 1 +
.../common/util/http/InputStreamData.java | 3 +-
.../http/apache/ApacheHttpClientBuilder.java | 26 +++-
.../common/util/json/WxGsonBuilder.java | 5 +
.../common/util/xml/XStreamInitializer.java | 5 +
.../weixin/cp/api/WxCpCorpGroupService.java | 2 +-
.../weixin/cp/api/WxCpExportService.java | 2 +-
.../cp/api/WxCpExternalContactService.java | 129 ++++++++++--------
.../weixin/cp/api/WxCpGroupRobotService.java | 7 +-
.../chanjar/weixin/cp/api/WxCpKfService.java | 4 +-
.../weixin/cp/api/WxCpLivingService.java | 2 +-
.../weixin/cp/api/WxCpMediaService.java | 4 +-
.../weixin/cp/api/WxCpMessageService.java | 3 +-
.../weixin/cp/api/WxCpOAuth2Service.java | 13 +-
.../cp/api/WxCpOaMeetingRoomService.java | 11 +-
.../chanjar/weixin/cp/api/WxCpOaService.java | 5 +-
.../weixin/cp/api/WxCpSchoolService.java | 5 +-
.../weixin/cp/api/WxCpSchoolUserService.java | 45 +++---
.../me/chanjar/weixin/cp/api/WxCpService.java | 2 +-
.../weixin/cp/api/WxCpUserService.java | 4 +-
.../cp/api/impl/WxCpCorpGroupServiceImpl.java | 2 +-
.../impl/WxCpExternalContactServiceImpl.java | 2 +-
.../chanjar/weixin/cp/bean/WxCpBaseResp.java | 3 +-
.../weixin/cp/bean/WxCpTpAuthInfo.java | 2 +-
.../cp/bean/WxCpTpPermanentCodeInfo.java | 2 +-
.../me/chanjar/weixin/cp/bean/WxCpTpTag.java | 2 +-
.../bean/WxCpTpTagAddOrRemoveUsersResult.java | 2 +-
.../weixin/cp/bean/WxCpTpTagGetResult.java | 2 +-
.../cp/bean/external/WxCpMsgTemplate.java | 5 +-
.../WxCpUserExternalUnassignList.java | 3 +-
.../WxCpCustomerAcquisitionStatistic.java | 2 +-
.../interceptrule/WxCpInterceptRuleInfo.java | 7 +-
.../interceptrule/WxCpInterceptRuleList.java | 7 +-
.../cp/bean/external/msg/Attachment.java | 5 +
.../weixin/cp/bean/kf/WxCpKfAccountLink.java | 4 +-
.../cp/bean/media/MediaUploadByUrlReq.java | 3 +-
.../cp/bean/media/MediaUploadByUrlResult.java | 3 +-
.../bean/message/WxCpLinkedCorpMessage.java | 14 +-
.../cp/bean/message/WxCpXmlMessage.java | 24 ++--
.../messagebuilder/TemplateCardBuilder.java | 3 +-
.../cp/bean/oa/WxCpCheckinGroupBase.java | 4 +-
.../weixin/cp/bean/oa/WxCpOaSchedule.java | 2 +-
.../WxCpOaMeetingRoomBookResult.java | 2 +-
.../cp/bean/oa/templatedata/TemplateTips.java | 2 +-
.../oa/templatedata/TemplateTipsContent.java | 2 +-
.../oa/templatedata/TemplateTipsSubText.java | 2 +-
.../TemplateTipsSubTextContent.java | 2 +-
.../TemplateTipsSubTextContentLink.java | 2 +-
.../TemplateTipsSubTextContentPlainText.java | 6 +-
.../oa/templatedata/TemplateTipsText.java | 6 +-
.../cp/config/WxCpCorpGroupConfigStorage.java | 23 ++--
.../weixin/cp/config/WxCpTpConfigStorage.java | 2 +-
.../impl/AbstractWxCpTpInRedisConfigImpl.java | 4 +-
.../config/impl/WxCpTpDefaultConfigImpl.java | 6 +-
.../weixin/cp/constant/WxCpConsts.java | 2 +-
.../cp/corpgroup/service/WxCpCgService.java | 38 ++++--
.../service/impl/BaseWxCpCgServiceImpl.java | 4 +
.../cp/tp/service/WxCpTpEditionService.java | 2 +-
.../cp/tp/service/WxCpTpIdConvertService.java | 32 +++--
.../weixin/cp/tp/service/WxCpTpOAService.java | 3 +-
.../cp/tp/service/WxCpTpOrderService.java | 4 +-
.../weixin/cp/tp/service/WxCpTpService.java | 14 +-
.../cp/tp/service/WxCpTpTagService.java | 2 +-
.../impl/WxCpTpEditionServiceImpl.java | 2 +-
.../service/impl/WxCpTpOrderServiceImpl.java | 4 +-
.../tp/service/impl/WxCpTpTagServiceImpl.java | 2 +-
.../cp/util/xml/XStreamTransformer.java | 4 +-
.../wx/miniapp/config/WxMaConfig.java | 26 ++++
.../wx/miniapp/util/crypt/WxMaCryptUtils.java | 2 +
.../miniapp/util/xml/XStreamTransformer.java | 22 ++-
.../weixin/mp/api/WxMpCardService.java | 4 +-
.../weixin/mp/api/WxMpCommentService.java | 4 +-
.../weixin/mp/api/WxMpDataCubeService.java | 98 +++++++++----
.../me/chanjar/weixin/mp/api/WxMpService.java | 86 ++++++------
.../mp/api/impl/BaseWxMpServiceImpl.java | 5 +-
.../mp/bean/WxMpMassOpenIdsMessage.java | 12 +-
.../mp/bean/WxMpMassPreviewMessage.java | 10 +-
.../weixin/mp/bean/WxMpMassTagMessage.java | 10 +-
.../chanjar/weixin/mp/bean/WxMpUserQuery.java | 5 +-
.../card/WxMpCardMpnewsGethtmlResult.java | 4 +-
.../MemberCardActivateUserFormRequest.java | 8 +-
.../card/membercard/MemberCardUserForm.java | 3 +
.../bean/card/membercard/NotifyOptional.java | 9 +-
.../WxMpMemberCardUpdateResult.java | 6 +-
.../WxMpMemberCardUserInfoResult.java | 7 +-
.../weixin/mp/bean/kefu/WxMpKefuMessage.java | 31 ++++-
.../mp/bean/message/WxMpXmlMessage.java | 31 +++--
.../mp/bean/message/WxMpXmlOutMessage.java | 27 ++++
.../mp/bean/result/WxMpMassGetResult.java | 3 +-
.../weixin/mp/config/WxMpConfigStorage.java | 6 +-
.../mp/config/impl/WxMpRedisConfigImpl.java | 2 +
.../config/impl/WxMpRedissonConfigImpl.java | 2 +
.../mp/constant/WxMpEventConstants.java | 2 +-
.../weixin/mp/util/crypto/WxMpCryptUtil.java | 2 +-
.../mp/util/xml/XStreamTransformer.java | 22 ++-
.../bean/notify/MiPayNotifyV3Result.java | 2 +-
.../bean/notify/WxPayBaseNotifyV3Result.java | 13 +-
.../bean/notify/WxPayNotifyV3Response.java | 6 +-
.../binarywang/wxpay/config/WxPayConfig.java | 5 +-
.../binarywang/wxpay/util/RequestUtils.java | 2 +-
.../binarywang/wxpay/util/ResourcesUtils.java | 11 ++
.../binarywang/wxpay/util/SignUtils.java | 24 +++-
112 files changed, 751 insertions(+), 419 deletions(-)
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
index 70c4e47933..d7e8936e62 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
@@ -12,7 +12,7 @@
/**
* 微信开发所使用到的常量类.
*
- * @author Daniel Qian & binarywang & Wang_Wong
+ * @author Daniel Qian, binarywang, Wang_Wong
*/
@UtilityClass
public class WxConsts {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
index c08a49063d..b339844ad6 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
@@ -7,7 +7,10 @@
import java.io.Serializable;
/**
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
+ * OAuth2 AccessToken
+ *
+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842}
+ *
*
* @author Daniel Qian
*/
@@ -36,8 +39,10 @@ public class WxOAuth2AccessToken implements Serializable {
private Integer snapshotUser;
/**
- * https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN.
* 本接口在scope参数为snsapi_base时不再提供unionID字段。
+ *
+ * 参考:{@code https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN}
+ *
*/
@SerializedName("unionid")
private String unionId;
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java
index ea1e9e7c68..356d1dbbf9 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java
@@ -453,7 +453,7 @@ public enum WxCpErrorMsgEnum {
*/
CODE_60008(60008, "部门已存在;部门ID或者部门名称已存在"),
/**
- * 部门名称含有非法字符;不能含有 \\:?*“< >| 等字符.
+ * {@code 部门名称含有非法字符;不能含有 \\:?*"< >| 等字符.}
*/
CODE_60009(60009, "部门名称含有非法字符;不能含有 \\ :?*“< >| 等字符"),
/**
@@ -521,7 +521,7 @@ public enum WxCpErrorMsgEnum {
*/
CODE_60124(60124, "无效的父部门id;父部门不存在通讯录中"),
/**
- * 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符.
+ * {@code 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?"< >|等字符.}
*/
CODE_60125(60125, "非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符"),
/**
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java
index b45fba3411..1aab7f1f20 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java
@@ -12,11 +12,13 @@
/**
* 微信错误码.
+ *
* 请阅读:
* 公众平台:全局返回码说明
* 企业微信:全局错误码
+ *
*
- * @author Daniel Qian & Binary Wang
+ * @author Daniel Qian, Binary Wang
*/
@Data
@NoArgsConstructor
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java
index 1bb3f6472b..ffe9b5e3ea 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java
@@ -46,23 +46,23 @@ public enum WxMaErrorMsgEnum {
*/
CODE_40003(40003, "openid 不正确"),
/**
- *
* 无效媒体文件类型
- * 对应操作:uploadTempMedia
+ *
+ * 对应操作:{@code uploadTempMedia}
* 对应地址:
- * POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
+ * {@code POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE}
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/uploadTempMedia.html
- *
+ *
*/
CODE_40004(40004, "无效媒体文件类型"),
/**
- *
* 无效媒体文件 ID.
- * 对应操作:getTempMedia
+ *
+ * 对应操作:{@code getTempMedia}
* 对应地址:
- * GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
+ * {@code GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID}
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/getTempMedia.html
- *
+ *
*/
CODE_40007(40007, "无效媒体文件 ID"),
/**
@@ -99,29 +99,29 @@ public enum WxMaErrorMsgEnum {
*/
CODE_41028(41028, "form_id 不正确,或者过期"),
/**
- *
* code 或 template_id 不正确.
- * 对应操作:code2Session, sendUniformMessage, sendTemplateMessage
+ *
+ * 对应操作:{@code code2Session}, {@code sendUniformMessage}, {@code sendTemplateMessage}
* 对应地址:
- * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
+ * {@code GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code}
* POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
* POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html
* https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html
* https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html
- *
+ *
*/
CODE_41029(41029, "请求的参数不正确"),
/**
- *
* form_id 已被使用,或者所传page页面不存在,或者小程序没有发布
- * 对应操作:sendUniformMessage, getWXACodeUnlimit
+ *
+ * 对应操作:{@code sendUniformMessage}, {@code getWXACodeUnlimit}
* 对应地址:
* POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
* POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html
- * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html
- *
+ * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html
+ *
*/
CODE_41030(41030, "请求的参数不正确"),
/**
@@ -138,13 +138,13 @@ public enum WxMaErrorMsgEnum {
*/
CODE_45009(45009, "调用分钟频率受限"),
/**
- *
* 频率限制,每个用户每分钟100次.
- * 对应操作:code2Session
+ *
+ * 对应操作:{@code code2Session}
* 对应地址:
- * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
+ * {@code GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code}
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html
- *
+ *
*/
CODE_45011(45011, "频率限制,每个用户每分钟100次"),
/**
@@ -190,12 +190,13 @@ public enum WxMaErrorMsgEnum {
*/
CODE_45072(45072, "command字段取值不对"),
/**
- *
* 下发输入状态,需要之前30秒内跟用户有过消息交互.
- * 对应操作:customerTyping
+ *
+ * 对应操作:{@code customerTyping}
* 对应地址:
* POST https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/customerTyping.html
+ *
*/
CODE_45080(45080, "下发输入状态,需要之前30秒内跟用户有过消息交互"),
/**
@@ -686,7 +687,7 @@ public enum WxMaErrorMsgEnum {
/**
* 89252
- * 法人&企业信息一致性校验中 front checking
+ * {@code 法人&企业信息一致性校验中 front checking}
*/
CODE_89252(89252, "法人&企业信息一致性校验中"),
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java
index 28fb5de8ad..ba910e988b 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java
@@ -527,7 +527,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40099(40099, "invalid code, this code has consumed."),
/**
- * invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime
+ * {@code invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime}
*/
CODE_40100(40100, "invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime"),
@@ -572,7 +572,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40108(40108, "invalid client version"),
/**
- * too many code size, must <= 100
+ * {@code too many code size, must <= 100}
*/
CODE_40109(40109, "too many code size, must <= 100"),
@@ -702,7 +702,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40135(40135, "invalid not supply bonus, can not change card_id which supply bonus to be not supply"),
/**
- * invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity
+ * {@code invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity}
*/
CODE_40136(40136, "invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity"),
@@ -1082,7 +1082,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40211(40211, "invalid scope_data"),
/**
- * paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query
+ * {@code paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query}
*/
CODE_40212(40212, "paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2"),
@@ -4242,7 +4242,7 @@ public enum WxOpenErrorMsgEnum {
CODE_71005(71005, "limit exe count"),
/**
- * limit coin count, 1 <= coin_count <= 100000
+ * {@code limit coin count, 1 <= coin_count <= 100000}
*/
CODE_71006(71006, "limit coin count, 1 <= coin_count <= 100000"),
@@ -4347,7 +4347,7 @@ public enum WxOpenErrorMsgEnum {
CODE_72018(72018, "duplicate order id, invoice had inserted to user"),
/**
- * limit msg operation card list size, must <= 5
+ * {@code limit msg operation card list size, must <= 5}
*/
CODE_72019(72019, "limit msg operation card list size, must <= 5"),
@@ -6432,7 +6432,7 @@ public enum WxOpenErrorMsgEnum {
CODE_88009(88009, "reply is not exists"),
/**
- * count range error. cout <= 0 or count > 50
+ * {@code count range error. cout <= 0 or count > 50}
*/
CODE_88010(88010, "count range error. cout <= 0 or count > 50"),
@@ -6682,7 +6682,7 @@ public enum WxOpenErrorMsgEnum {
CODE_89251(89251, "模板消息已下发,待法人人脸核身校验"),
/**
- * 法人&企业信息一致性校验中 front checking
+ * {@code 法人&企业信息一致性校验中 front checking}
*/
CODE_89253(89253, "法人&企业信息一致性校验中"),
@@ -7257,7 +7257,7 @@ public enum WxOpenErrorMsgEnum {
CODE_200021(200021, "场景描述 sceneDesc 参数错误"),
/**
- * 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间
+ * {@code 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间}
*/
CODE_300001(300001, "禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间"),
@@ -8382,7 +8382,7 @@ public enum WxOpenErrorMsgEnum {
CODE_9300003(9300003, "begin_time must less than end_time"),
/**
- * end_time - begin_time > 1year
+ * {@code end_time - begin_time > 1year}
*/
CODE_9300004(9300004, "end_time - begin_time > 1year"),
@@ -8397,7 +8397,7 @@ public enum WxOpenErrorMsgEnum {
CODE_9300006(9300006, "invalid activity status"),
/**
- * gift_num must >0 and <=15
+ * {@code gift_num must >0 and <=15}
*/
CODE_9300007(9300007, "gift_num must >0 and <=15"),
@@ -8412,7 +8412,7 @@ public enum WxOpenErrorMsgEnum {
CODE_9300009(9300009, "activity can not finish"),
/**
- * card_info_list must >= 2
+ * {@code card_info_list must >= 2}
*/
CODE_9300010(9300010, "card_info_list must >= 2"),
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
index 39a8a93754..d0aeef8491 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
@@ -12,7 +12,9 @@
/**
* 基于小程序或 H5 的身份证、银行卡、行驶证 OCR 识别.
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712284rHWMX
+ *
+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712284rHWMX}
+ *
*
* @author Binary Wang
* created on 2019-06-22
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
index e3d9ab8351..24ea58ef38 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
@@ -7,13 +7,12 @@ public interface InternalSessionManager {
/**
* Return the active Session, associated with this Manager, with the
- * specified session id (if any); otherwise return null.
+ * specified session id (if any); otherwise return {@code null}.
*
* @param id The session id for the session to be returned
+ * @return the session or null
* @throws IllegalStateException if a new session cannot be
* instantiated for any reason
- * @throws java.io.IOException if an input/output error occurs while
- * processing this request
*/
InternalSession findSession(String id);
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
index fc3579d45c..1886209f98 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
@@ -25,6 +25,7 @@ public class SignUtils {
*
* @param message 签名数据
* @param key 签名密钥
+ * @return 签名结果
*/
public static String createHmacSha256Sign(String message, String key) {
try {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
index 9b9f776768..43cc54b43d 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
@@ -29,7 +29,10 @@ public static String gen(String... arr) {
}
/**
- * 用&串接arr参数,生成sha1 digest.
+ * {@code 用&串接arr参数,生成sha1 digest.}
+ *
+ * @param arr 参数数组
+ * @return sha1摘要
*/
public static String genWithAmple(String... arr) {
if (StringUtils.isAnyEmpty(arr)) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
index 0b0590b1e6..50362636fc 100755
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
@@ -197,6 +197,7 @@ public EncryptContext encryptContext(String plainText) {
/**
* 对明文进行加密.
*
+ * @param randomStr 随机字符串
* @param plainText 需要加密的明文
* @return 加密后base64编码的字符串
*/
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java
index d07873f3c4..f03932984f 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java
@@ -10,8 +10,9 @@
/**
* 输入流数据.
- *
+ *
* InputStreamData
+ *
*
* @author zichuan.zhou91@gmail.com
* created on 2022/2/15
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
index de34ca5bd1..5b13e7cc17 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
@@ -21,42 +21,66 @@ public interface ApacheHttpClientBuilder {
/**
* 代理服务器地址.
+ *
+ * @param httpProxyHost 代理服务器地址
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyHost(String httpProxyHost);
/**
* 代理服务器端口.
+ *
+ * @param httpProxyPort 代理服务器端口
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyPort(int httpProxyPort);
/**
* 代理服务器用户名.
+ *
+ * @param httpProxyUsername 代理服务器用户名
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername);
/**
* 代理服务器密码.
+ *
+ * @param httpProxyPassword 代理服务器密码
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword);
/**
* 重试策略.
+ *
+ * @param httpRequestRetryHandler 重试处理器
+ * @return ApacheHttpClientBuilder
*/
- ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler );
+ ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler);
/**
* 超时时间.
+ *
+ * @param keepAliveStrategy 保持连接策略
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy);
/**
* ssl连接socket工厂.
+ *
+ * @param sslConnectionSocketFactory SSL连接Socket工厂
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory);
/**
* 支持的TLS协议版本.
* Supported TLS protocol versions.
+ *
+ * @param supportedProtocols 支持的协议版本数组
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols);
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
index 6ea269f7e4..8f3dafe48a 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
@@ -43,6 +43,11 @@ public boolean shouldSkipClass(Class> aClass) {
});
}
+ /**
+ * 创建Gson实例
+ *
+ * @return Gson实例
+ */
public static Gson create() {
if (Objects.isNull(GSON_INSTANCE)) {
synchronized (INSTANCE) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
index 3fa91fa70e..51cd1e980c 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
@@ -22,6 +22,11 @@ public class XStreamInitializer {
public static ClassLoader classLoader;
+ /**
+ * 设置类加载器
+ *
+ * @param classLoaderInfo 类加载器
+ */
public static void setClassLoader(ClassLoader classLoaderInfo) {
classLoader = classLoaderInfo;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java
index 4da13d3fde..69aea4bca7 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java
@@ -9,7 +9,7 @@
* 企业互联相关接口
*
* @author libo <422423229@qq.com>
- * Created on 27/2/2023 9:57 PM
+ * @since 2023-02-27 9:57 PM
*/
public interface WxCpCorpGroupService {
/**
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java
index 24c6ea9dc1..a2c7adabea 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java
@@ -85,7 +85,7 @@ public interface WxCpExportService {
* 获取导出结果
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx}
*
* 文档地址:https://developer.work.weixin.qq.com/document/path/94854
* 返回的url文件下载解密可参考 CSDN
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java
index 7f3cdeab7c..6de9f9226d 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java
@@ -146,7 +146,7 @@ public interface WxCpExternalContactService {
* 企业可通过此接口,根据外部联系人的userid(如何获取?),拉取客户详情。
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID}
*
* 权限说明:
*
@@ -252,9 +252,9 @@ public interface WxCpExternalContactService {
*
* 权限说明:
*
- * 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限
+ * {@code 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限}
* 该客户的跟进人必须在应用的可见范围之内
- * 应用需具备“企业客户权限->客户基础信息”权限
+ * {@code 应用需具备“企业客户权限->客户基础信息”权限}
*
*
* @param externalUserid 代开发自建应用获取到的外部联系人ID
@@ -276,8 +276,8 @@ public interface WxCpExternalContactService {
*
* @param externalUserid 服务商主体的external_userid,必须是source_agentid对应的应用所获取
* @param sourceAgentId 企业授权的代开发自建应用或第三方应用的agentid
- * @return
- * @throws WxErrorException
+ * @return 企业的external_userid
+ * @throws WxErrorException 微信错误异常
*/
String fromServiceExternalUserid(String externalUserid, String sourceAgentId) throws WxErrorException;
@@ -362,7 +362,7 @@ public interface WxCpExternalContactService {
* 权限说明:
*
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)
- * 第三方应用需具有“企业客户权限->客户基础信息”权限
+ * {@code 第三方应用需具有“企业客户权限->客户基础信息”权限}
* 对于第三方/自建应用,群主必须在应用的可见范围
* 仅支持企业服务人员创建的客户群
* 仅可转换出自己企业下的客户群chat_id
@@ -410,11 +410,12 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
* 文档地址: https://developer.work.weixin.qq.com/document/path/99434
*
*
+ * 注意:企业可通过外部联系人临时ID排除重复数据,外部联系人临时ID有效期为4小时。
+ *
* @param cursor the cursor
* @param limit the limit
* @return 已服务的外部联系人列表
* @throws WxErrorException .
- * @apiNote 企业可通过外部联系人临时ID排除重复数据,外部联系人临时ID有效期为4小时。
*/
WxCpExternalContactListInfo getContactList(String cursor, Integer limit) throws WxErrorException;
@@ -438,7 +439,7 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
* 企业可通过此接口获取指定成员添加的客户列表。客户是指配置了客户联系功能的成员所添加的外部联系人。没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID}
*
* 权限说明:
*
@@ -469,7 +470,8 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
/**
* 获取待分配的离职成员列表
* 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用分配离职成员的客户接口将这些客户重新分配给其他企业成员。
- *
+
+ *
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_unassigned_list?access_token=ACCESS_TOKEN
*
@@ -496,17 +498,17 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
/**
* 企业可通过此接口,转接在职成员的客户给其他成员。
- *
+ *
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
+ *
* 权限说明:
- * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+ *
*
* @param req 转接在职成员的客户给其他成员请求实体
* @return wx cp base resp
@@ -516,13 +518,13 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
/**
* 企业和第三方可通过此接口查询在职成员的客户转接情况。
- *
* 权限说明:
- *
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
* handover_userid必须是已离职用户。
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
* 注意::
- *
+
+ *
* 群主离职了的客户群,才可继承
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
* 继承给的新群主,必须有激活企业微信
* 同一个人的群,限制每天最多分配300个给新群主
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限}
* 对于第三方/自建应用,群主必须在应用的可见范围。
- *
* 注意:
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
@@ -716,11 +724,14 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
* 企业可通过此接口添加企业群发消息的任务并通知客服人员发送给相关客户或客户群。(注:企业微信终端需升级到2.7.5版本及以上)
* 注意:调用该接口并不会直接发送消息给客户/客户群,需要相关的客服人员操作以后才会实际发送(客服人员的企业微信需要升级到2.7.5及以上版本)
* 同一个企业每个自然月内仅可针对一个客户/客户群发送4条消息,超过限制的用户将会被忽略。
- *
+
+ *
* 请求方式: POST(HTTP)
- *
+
+ *
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=ACCESS_TOKEN
- *
+
+ *
* 文档地址
*
* @param wxCpMsgTemplate the wx cp msg template
@@ -733,15 +744,18 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
/**
* 提醒成员群发
* 企业和第三方应用可调用此接口,重新触发群发通知,提醒成员完成群发任务,24小时内每个群发最多触发三次提醒。
- *
+
+ *
* 请求方式: POST(HTTPS)
- *
+
+ *
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remind_groupmsg_send?access_token=ACCESS_TOKEN
- *
+ *
* 文档地址
*
* @param msgId 群发消息的id,通过获取群发记录列表接口返回
* @return the wx cp msg template add result
+ * @throws WxErrorException 微信错误异常
*/
WxCpBaseResp remindGroupMsgSend(String msgId) throws WxErrorException;
@@ -753,11 +767,12 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
* 请求方式: POST(HTTPS)
*
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/cancel_groupmsg_send?access_token=ACCESS_TOKEN
- *
+ *
* 文档地址
*
* @param msgId 群发消息的id,通过获取群发记录列表接口返回
* @return the wx cp msg template add result
+ * @throws WxErrorException 微信错误异常
*/
WxCpBaseResp cancelGroupMsgSend(String msgId) throws WxErrorException;
@@ -1002,7 +1017,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 企业和第三方应用可通过此接口获取企业与成员的群发记录。
- * 获取企业群发成员执行结果
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
*
*
* @param msgid 群发消息的id,通过获取群发记录列表接口返回
@@ -1031,7 +1046,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 获取群发成员发送任务列表。
- * 获取群发成员发送任务列表
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
*
*
* @param msgid 群发消息的id,通过获取群发记录列表接口返回
@@ -1045,7 +1060,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 添加入群欢迎语素材。
- * 添加入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param template 素材内容
@@ -1057,7 +1072,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 编辑入群欢迎语素材。
- * 编辑入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param template the template
@@ -1069,7 +1084,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 获取入群欢迎语素材。
- * 获取入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param templateId 群欢迎语的素材id
@@ -1082,7 +1097,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
*
* 删除入群欢迎语素材。
* 企业可通过此API删除入群欢迎语素材,且仅能删除调用方自己创建的入群欢迎语素材。
- * 删除入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param templateId 群欢迎语的素材id
@@ -1094,8 +1109,8 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
- * 获取商品图册
- * 获取商品图册列表
+ * 获取商品图册列表
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
*
*
* @param limit 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取默认值
@@ -1108,7 +1123,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 获取商品图册
- * 获取商品图册
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
*
*
* @param productId 商品id
@@ -1155,7 +1170,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 企业和第三方应用可以通过此接口新建敏感词规则
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_intercept_rule?access_token=ACCESS_TOKEN
- *
+ *
* @param ruleAddRequest the rule add request
* @return 规则id
* @throws WxErrorException the wx error exception
@@ -1169,7 +1184,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 企业和第三方应用可以通过此接口修改敏感词规则
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_intercept_rule?access_token=ACCESS_TOKEN
- *
+ *
* @param interceptRule the rule
* @throws WxErrorException the wx error exception
*/
@@ -1181,7 +1196,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 企业和第三方应用可以通过此接口修改敏感词规则
* 请求方式:POST(HTTPS)
* 请求地址
- *
+ *
* @param ruleId 规则id
* @throws WxErrorException the wx error exception
*/
@@ -1220,7 +1235,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 请求地址:
* https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_product_album?access_token=ACCESS_TOKEN
* 文档地址
- *
+ *
* @param wxCpProductAlbumInfo 商品图册信息
* @return 商品id string
* @throws WxErrorException the wx error exception
@@ -1235,7 +1250,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 请求地址:
* https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_product_album?access_token=ACCESS_TOKEN
* 文档地址
- *
+ *
* @param wxCpProductAlbumInfo 商品图册信息
* @throws WxErrorException the wx error exception
*/
@@ -1250,7 +1265,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* https://qyapi.weixin.qq.com/cgi-bin/externalcontact/delete_product_album?access_token=ACCESS_TOKEN
*
* 文档地址
- *
+ *
* @param productId 商品id
* @throws WxErrorException the wx error exception
*/
@@ -1379,7 +1394,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/statistic?access_token=ACCESS_TOKEN
*
* @author Hugo
- * @date 2023/12/5 14:34
+ * @since 2023/12/5 14:34
* @param linkId 获客链接的id
* @param startTime 统计起始时间
* @param endTime 统计结束时间
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java
index c1a8d56255..b8ccea5e50 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java
@@ -126,9 +126,10 @@ public interface WxCpGroupRobotService {
/**
* 发送模板卡片消息
- * @param webhookUrl
- * @param wxCpGroupRobotMessage
- * @throws WxErrorException
+ *
+ * @param webhookUrl webhook地址
+ * @param wxCpGroupRobotMessage 群机器人消息
+ * @throws WxErrorException 异常
*/
void sendTemplateCardMessage(String webhookUrl, WxCpGroupRobotMessage wxCpGroupRobotMessage) throws WxErrorException;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java
index 5a53829dc0..046cfbc5bb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java
@@ -222,7 +222,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList)
* https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_statistic?access_token=ACCESS_TOKEN
* 文档地址:
* https://developer.work.weixin.qq.com/document/path/95489
- *
+ *
* @param request 查询参数
* @return 客户数据统计 -企业汇总数据
* @throws WxErrorException the wx error exception
@@ -238,7 +238,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList)
* https://qyapi.weixin.qq.com/cgi-bin/kf/get_servicer_statistic?access_token=ACCESS_TOKEN
* 文档地址:
* https://developer.work.weixin.qq.com/document/path/95490
- *
+ *
* @param request 查询参数
* @return 客户数据统计 -企业汇总数据
* @throws WxErrorException the wx error exception
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java
index a2e2344190..63fabad7a1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java
@@ -27,7 +27,7 @@ public interface WxCpLivingService {
/**
* 获取直播详情
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID}
*
* @param livingId 直播id
* @return 获取的直播详情 living info
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
index e874b26f42..dd5ce594b2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
@@ -110,9 +110,9 @@ WxMediaUploadResult upload(String mediaType, String filename, String url)
* 获取高清语音素材.
* 可以使用本接口获取从JSSDK的uploadVoice接口上传的临时语音素材,格式为speex,16K采样率。该音频比上文的临时素材获取接口(格式为amr,8K采样率)更加清晰,适合用作语音识别等对音质要求较高的业务。
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID}
* 仅企业微信2.4及以上版本支持。
- * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90255
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90255
*
*
* @param mediaId 媒体id
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
index e49a36ba50..534cc89b36 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
@@ -72,8 +72,9 @@ public interface WxCpMessageService {
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=ACCESS_TOKEN
* 文档地址: https://developer.work.weixin.qq.com/document/path/94867
*
+ *
* @param msgId 消息id
- * @throws WxErrorException
+ * @throws WxErrorException 异常
*/
void recall(String msgId) throws WxErrorException;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
index b7a44047aa..1824196720 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
@@ -90,9 +90,10 @@ public interface WxCpOAuth2Service {
/**
* 获取家校访问用户身份
* 该接口用于根据code获取家长或者学生信息
- * + *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+ *
*
* @param code the code
* @return school user info
@@ -123,7 +124,7 @@ public interface WxCpOAuth2Service {
/**
*
* 获取用户登录身份
- * https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
* 该接口可使用用户登录成功颁发的code来获取成员信息,适用于自建应用与代开发应用
*
* 注意: 旧的/user/getuserinfo 接口的url已变更为auth/getuserinfo,不过旧接口依旧可以使用,建议是关注新接口即可
@@ -140,13 +141,15 @@ public interface WxCpOAuth2Service {
/**
* 获取用户二次验证信息
- *
+ *
* api: https://qyapi.weixin.qq.com/cgi-bin/auth/get_tfa_info?access_token=ACCESS_TOKEN
* 权限说明:仅『通讯录同步』或者自建应用可调用,如用自建应用调用,用户需要在二次验证范围和应用可见范围内。
* 并发限制:20
+ *
*
* @param code 用户进入二次验证页面时,企业微信颁发的code,每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期
- * @return me.chanjar.weixin.cp.bean.workbench.WxCpSecondVerificationInfo 二次验证授权码,开发者可以调用通过二次验证接口,解锁企业微信终端.tfa_code有效期五分钟,且只能使用一次。
+ * @return 二次验证授权码,开发者可以调用通过二次验证接口,解锁企业微信终端.tfa_code有效期五分钟,且只能使用一次。
+ * @throws WxErrorException 微信错误异常
*/
WxCpSecondVerificationInfo getTfaInfo(String code) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java
index c2e6c5c872..cc039fd9f5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java
@@ -84,6 +84,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookingInfoRequest 会议室预定信息查询对象
+ * @return 会议室预定信息
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookingInfoResult getMeetingRoomBookingInfo(WxCpOaMeetingRoomBookingInfoRequest wxCpOaMeetingRoomBookingInfoRequest) throws WxErrorException;
@@ -99,6 +100,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookRequest 会议室预定对象
+ * @return 预定结果
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookResult bookingMeetingRoom(WxCpOaMeetingRoomBookRequest wxCpOaMeetingRoomBookRequest) throws WxErrorException;
@@ -114,6 +116,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookByScheduleRequest 会议室预定对象
+ * @return 预定结果
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookResult bookingMeetingRoomBySchedule(WxCpOaMeetingRoomBookByScheduleRequest wxCpOaMeetingRoomBookByScheduleRequest) throws WxErrorException;
@@ -129,6 +132,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookByMeetingRequest 会议室预定对象
+ * @return 预定结果
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookResult bookingMeetingRoomByMeeting(WxCpOaMeetingRoomBookByMeetingRequest wxCpOaMeetingRoomBookByMeetingRequest) throws WxErrorException;
@@ -147,10 +151,10 @@ public interface WxCpOaMeetingRoomService {
* @param wxCpOaMeetingRoomCancelBookRequest 取消预定会议室对象
* @throws WxErrorException .
*/
- void cancelBookMeetingRoom(WxCpOaMeetingRoomCancelBookRequest wxCpOaMeetingRoomCancelBookRequest) throws WxErrorException;
+ void cancelBookMeetingRoom(WxCpOaMeetingRoomCancelBookRequest wxCpOaMeetingRoomCancelBookRequest) throws WxErrorException;
- /**
+ /**
* 根据会议室预定ID查询预定详情.
*
* 企业可通过此接口根据预定id查询相关会议室的预定情况
@@ -161,8 +165,9 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookingInfoByBookingIdRequest 根据会议室预定ID查询预定详情对象
+ * @return 预定详情
* @throws WxErrorException .
*/
- WxCpOaMeetingRoomBookingInfoByBookingIdResult getBookingInfoByBookingId(WxCpOaMeetingRoomBookingInfoByBookingIdRequest wxCpOaMeetingRoomBookingInfoByBookingIdRequest) throws WxErrorException;
+ WxCpOaMeetingRoomBookingInfoByBookingIdResult getBookingInfoByBookingId(WxCpOaMeetingRoomBookingInfoByBookingIdRequest wxCpOaMeetingRoomBookingInfoByBookingIdRequest) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
index ee57107b5c..3494dcfa4e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
@@ -11,7 +11,8 @@
/**
* 企业微信OA相关接口.
*
- * @author Element & Wang_Wong created on 2019-04-06 10:52
+ * @author Element, Wang_Wong
+ * @since 2019-04-06 10:52
*/
public interface WxCpOaService {
@@ -331,7 +332,7 @@ List+ ** @param userId 需要录入的用户id * @param userFace 需要录入的人脸图片数据,需要将图片数据base64处理后填入,对已录入的人脸会进行更新处理 * @throws WxErrorException the wx error exception diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java index 56687c9cb1..5f1d41c197 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java @@ -80,9 +80,10 @@ public interface WxCpSchoolService { /** * 获取直播详情 + *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid
- * =LIVINGID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID}
+ *
*
* @param livingId the living id
* @return living info
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java
index a92bfcc100..d004ca8aa5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java
@@ -19,9 +19,10 @@ public interface WxCpSchoolUserService {
/**
* 获取访问用户身份
* 该接口用于根据code获取成员信息
- * + *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+ *
*
* @param code the code
* @return user info
@@ -32,9 +33,10 @@ public interface WxCpSchoolUserService {
/**
* 获取家校访问用户身份
* 该接口用于根据code获取家长或者学生信息
- * + *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+ *
*
* @param code the code
* @return school user info
@@ -90,8 +92,10 @@ public interface WxCpSchoolUserService {
/**
* 删除学生
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID}
+ *
*
* @param studentUserId the student user id
* @return wx cp base resp
@@ -160,8 +164,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 读取学生或家长
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID}
+ *
*
* @param userId the user id
* @return user
@@ -171,9 +177,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取部门成员详情
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID
- * &fetch_child=FETCH_CHILD
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
+ *
*
* @param departmentId 获取的部门id
* @param fetchChild 1/0:是否递归获取子部门下面的成员
@@ -184,9 +191,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取部门家长详情
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id
- * =DEPARTMENT_ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID}
+ *
*
* @param departmentId 获取的部门id
* @return user list parent
@@ -207,8 +215,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 删除家长
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID}
+ *
*
* @param userId the user id
* @return wx cp base resp
@@ -256,7 +266,7 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 删除部门
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID}
*
* @param id the id
* @return wx cp base resp
@@ -292,10 +302,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取外部联系人详情
* 学校可通过此接口,根据外部联系人的userid(如何获取?),拉取外部联系人详情。
- * + * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid - * =EXTERNAL_USERID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID} * * @param externalUserId 外部联系人的userid,注意不是学校成员的帐号 * @return external contact @@ -306,9 +315,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取可使用的家长范围 * 获取可在微信「学校通知-学校应用」使用该应用的家长范围,以学生或部门列表的形式返回。应用只能给该列表下的家长发送「学校通知」。注意该范围只能由学校的系统管理员在「管理端-家校沟通-配置」配置。 - *
+ * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID} * * @param agentId the agent id * @return allow scope @@ -332,7 +341,7 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取部门列表 * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID} * * @param id 部门id。获取指定部门及其下的子部门。 如果不填,默认获取全量组织架构 * @return wx cp department list diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index 0b601ca502..76012a2812 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -584,7 +584,7 @@ public interface WxCpService extends WxService { /** * 企业互联的服务类对象 * - * @return + * @return 企业互联服务对象 */ WxCpCorpGroupService getCorpGroupService(); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java index 2368386b23..7a7b5f40a8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java @@ -38,7 +38,7 @@ public interface WxCpUserService { *
* 获取部门成员详情
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
*
* 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90201
*
@@ -213,7 +213,7 @@ public interface WxCpUserService {
* 获取加入企业二维码。
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE}
*
* 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91714
*
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
index 48bd952a83..e3dc1cbe1c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
@@ -18,7 +18,7 @@
* 企业互联相关接口实现类
*
* @author libo <422423229@qq.com>
- * Created on 27/2/2023 9:57 PM
+ * @since 2023-02-27 9:57 PM
*/
@RequiredArgsConstructor
public class WxCpCorpGroupServiceImpl implements WxCpCorpGroupService {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
index 8e3a8d7b95..d43589595f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
@@ -38,7 +38,7 @@
/**
* The type Wx cp external contact service.
*
- * @author 曹祖鹏 & yuanqixun & Mr.Pan & Wang_Wong
+ * @author 曹祖鹏, yuanqixun, Mr.Pan, Wang_Wong
*/
@RequiredArgsConstructor
public class WxCpExternalContactServiceImpl implements WxCpExternalContactService {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
index 6bf9a30aeb..a895c38a8f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
@@ -10,7 +10,8 @@
/**
* 返回结果
*
- * @author yqx & WangWong created on 2020/3/16
+ * @author yqx, WangWong
+ * @since 2020/3/16
*/
@Getter
@Setter
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java
index fa50216153..9919fd72b8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java
@@ -216,7 +216,7 @@ public static class Agent implements Serializable {
/**
* 付费状态
- * - * Created by songfan on 2020/7/14. * - * @author songfan & Mr.Pan + * @author songfan, Mr.Pan + * @since 2020/7/14 */ @Data @Builder diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java index 8605760fa7..f3fdd96ce7 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java @@ -12,7 +12,8 @@ /** * 离职员工外部联系人列表 * - * @author yqx & Wang_Wong created on 2020/3/15 + * @author yqx, Wang_Wong + * @since 2020/3/15 */ @Getter @Setter diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java index bb02b039bd..87e3d5580a 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java @@ -10,7 +10,7 @@ * 获客链接的使用详情 * * @author Hugo - * @date 2023/12/11 10:31 + * @since 2023/12/11 10:31 */ @Data @EqualsAndHashCode(callSuper = true) diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java index 20d6b32442..23bb70a240 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java @@ -10,9 +10,10 @@ import java.util.List; /** - * @Date: 2024-03-07 17:02 - * @Author: shenliuming - * @return: + * 防骚扰规则详情 + * + * @author shenliuming + * @since 2024-03-07 17:02 */ @Data @EqualsAndHashCode(callSuper = true) diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java index 6826413e13..543d32fcb9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java @@ -9,9 +9,10 @@ import java.util.List; /** - * @Date: 2024-03-07 15:54 - * @Author: shenliuming - * @return: + * 防骚扰规则列表 + * + * @author shenliuming + * @since 2024-03-07 15:54 */ @Data @EqualsAndHashCode(callSuper = true) diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java index be9dcc9dd0..1fff457f97 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java @@ -33,6 +33,7 @@ public class Attachment implements Serializable { * Sets image. * * @param image the image + * @return this */ public Attachment setImage(Image image) { this.image = image; @@ -44,6 +45,7 @@ public Attachment setImage(Image image) { * Sets link. * * @param link the link + * @return this */ public Attachment setLink(Link link) { this.link = link; @@ -55,6 +57,7 @@ public Attachment setLink(Link link) { * Sets mini program. * * @param miniProgram the mini program + * @return this */ public Attachment setMiniProgram(MiniProgram miniProgram) { this.miniProgram = miniProgram; @@ -66,6 +69,7 @@ public Attachment setMiniProgram(MiniProgram miniProgram) { * Sets video. * * @param video the video + * @return this */ public Attachment setVideo(Video video) { this.video = video; @@ -77,6 +81,7 @@ public Attachment setVideo(Video video) { * Sets file. * * @param file the file + * @return this */ public Attachment setFile(File file) { this.file = file; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java index a903d0fa54..38d25e61cd 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java @@ -27,10 +27,10 @@ public class WxCpKfAccountLink implements Serializable { * 场景值,字符串类型,由开发者自定义。 * 不多于32字节 * 字符串取值范围(正则表达式):[0-9a-zA-Z_-]* - *
+ * * 1. 若scene非空,返回的客服链接开发者可拼接scene_param=SCENE_PARAM参数使用,用户进入会话事件会将SCENE_PARAM原样返回。 * 其中SCENE_PARAM需要urlencode,且长度不能超过128字节。 - * 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2 + * {@code 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2} * 2. 历史调用接口返回的客服链接(包含encScene=XXX参数),不支持scene_param参数。 * 3. 返回的客服链接,不能修改或复制参数到其他链接使用。否则进入会话事件参数校验不通过,导致无法回调。 */ diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java index c5cb21bde5..0149a426f6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java @@ -5,8 +5,9 @@ /** * 生成异步上传任务 + * * @author imyzt - * @date 2025/04/27 + * @since 2025/04/27 */ @Data public class MediaUploadByUrlReq { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java index cc931eed39..595f85dffa 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java @@ -10,8 +10,9 @@ /** * 异步上传企微素材 + * * @author imyzt - * @date 2025/4/27 + * @since 2025/4/27 */ @EqualsAndHashCode(callSuper = true) @Data diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java index 92209fd4e5..7e777384eb 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java @@ -85,13 +85,13 @@ public class WxCpLinkedCorpMessage implements Serializable { /** *
* 请使用.
- * {@link LinkedCorpMsgType#TEXT}
- * {@link LinkedCorpMsgType#IMAGE}
- * {@link LinkedCorpMsgType#VIDEO}
- * {@link LinkedCorpMsgType#NEWS}
- * {@link LinkedCorpMsgType#MPNEWS}
- * {@link LinkedCorpMsgType#MARKDOWN}
- * {@link LinkedCorpMsgType#MINIPROGRAM_NOTICE}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#TEXT}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#IMAGE}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#VIDEO}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#NEWS}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#MARKDOWN}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#MINIPROGRAM_NOTICE}
*
*
* @param msgType 消息类型
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
index 4001c7d0e4..08a0936317 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
@@ -68,19 +68,19 @@ public class WxCpXmlMessage implements Serializable {
/**
*
* 当接受用户消息时,可能会获得以下值:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#LOCATION}
- * {@link WxConsts.XmlMsgType#LINK}
- * {@link WxConsts.XmlMsgType#EVENT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#LOCATION}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#LINK}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#EVENT}
* 当发送消息的时候使用:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#NEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#NEWS}
*
*/
@XStreamAlias("MsgType")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java
index d3cbb89a3d..1ab92630a9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java
@@ -12,7 +12,8 @@
* 用法: WxCustomMessage m = WxCustomMessage.TEMPLATECARD().title(...)....toUser(...).build();
*
*
- * @author yzts a> created on 2019-05-16
+ * @author yzts
+ * @since 2019-05-16
*/
public class TemplateCardBuilder extends BaseBuilder* 文档地址 - *
+ * * 注意: *
- * 获取审批申请详情
+ * 获取审批申请详情
*
* @param spNo 审批单编号。
* @param corpId the corp id
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java
index 3aff90bb56..6e0acb7dee 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java
@@ -18,7 +18,7 @@ public interface WxCpTpOrderService {
* 获取订单详情
*
* 文档地址
- *
+ *
*
* @param orderId 订单号
* @return the order
@@ -31,7 +31,7 @@ public interface WxCpTpOrderService {
* 获取订单列表
*
* 文档地址
- *
+ *
*
* @param startTime 起始时间
* @param endTime 终止时间
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
index b24be535da..93855d1a48 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
@@ -199,7 +199,7 @@ public interface WxCpTpService {
* @return permanent code info
* @throws WxErrorException the wx error exception
* @author yuan
- * @since 2020 -03-18
+ * @since 2020-03-18
*/
WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException;
@@ -227,7 +227,7 @@ public interface WxCpTpService {
* @param authType 授权类型:0 正式授权, 1 测试授权。
* @return pre auth url
* @throws WxErrorException the wx error exception
- * @link https ://work.weixin.qq.com/api/doc/90001/90143/90602
+ * @see 文档地址
*/
String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException;
@@ -558,12 +558,12 @@ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
WxCpTpAppQrcode getAppQrcode(String suiteId, String appId, String state, Integer style, Integer resultType) throws WxErrorException ;
/**
- *
* 明文corpid转换为加密corpid 为更好地保护企业与用户的数据,第三方应用获取的corpid不再是明文的corpid,将升级为第三方服务商级别的加密corpid。文档说明
* 第三方可以将已有的明文corpid转换为第三方的加密corpid。
- * @param corpId
- * @return
- * @throws WxErrorException
+ *
+ * @param corpId 企业ID
+ * @return 加密的企业ID
+ * @throws WxErrorException 微信错误异常
*/
WxCpTpCorpId2OpenCorpId corpId2OpenCorpId(String corpId) throws WxErrorException;
@@ -655,6 +655,8 @@ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
/**
* 构造第三方应用oauth2链接
+ *
+ * @return OAuth2服务
*/
WxCpTpOAuth2Service getWxCpTpOAuth2Service();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java
index b508df59a1..df60041865 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java
@@ -13,7 +13,7 @@
*
*
* @author zhangq * 文档地址 - *
+ * ** 文档地址 - *
+ * * * @param orderId 订单号 * @return the order @@ -49,7 +49,7 @@ public WxCpTpOrderDetails getOrder(String orderId) throws WxErrorException { * 获取订单列表 ** 文档地址 - *
+ * * * @param startTime 起始时间 * @param endTime 终止时间 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java index b81760e72c..1b03f18c79 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java @@ -25,7 +25,7 @@ * * * @author zhangq
* 创建卡券
- *
*
- * @param cardCreateRequest 卡券创建请求对象
+ * @param cardCreateMessage 卡券创建请求对象
* @return 卡券创建结果对象
* @throws WxErrorException 微信API调用异常,可能包括:
*
* 获取图文群发总数据(getarticletotal)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getarticletotal?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -73,10 +76,13 @@ public interface WxMpDataCubeService {
List getArticleTotal(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文统计数据(getuserread)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getuserread?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度3天,endDate不能早于begingDate
@@ -86,10 +92,13 @@ public interface WxMpDataCubeService {
List getUserRead(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文统计分时数据(getuserreadhour)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getuserreadhour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -99,10 +108,13 @@ public interface WxMpDataCubeService {
List getUserReadHour(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文分享转发数据(getusershare)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getusershare?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度7天,endDate不能早于begingDate
@@ -112,10 +124,13 @@ public interface WxMpDataCubeService {
List getUserShare(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文分享转发分时数据(getusersharehour)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getusersharehour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -127,10 +142,13 @@ public interface WxMpDataCubeService {
//*******************消息分析数据接口***********************//
/**
- *
* 获取消息发送概况数据(getupstreammsg)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsg?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度7天,endDate不能早于begingDate
@@ -140,10 +158,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsg(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息分送分时数据(getupstreammsghour)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsghour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -153,10 +174,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgHour(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送周数据(getupstreammsgweek)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgweek?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -166,10 +190,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgWeek(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送月数据(getupstreammsgmonth)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgmonth?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -179,10 +206,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgMonth(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送分布数据(getupstreammsgdist)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgdist?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度15天,endDate不能早于begingDate
@@ -192,10 +222,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgDist(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送分布周数据(getupstreammsgdistweek)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgdistweek?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -205,10 +238,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgDistWeek(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送分布月数据(getupstreammsgdistmonth)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgdistmonth?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -220,10 +256,13 @@ public interface WxMpDataCubeService {
//*******************接口分析数据接口***********************//
/**
- *
* 获取接口分析数据(getinterfacesummary)
- * 详情请见文档:接口分析数据接口
+ *
+ * {@code 详情请见文档:接口分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getinterfacesummary?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -233,10 +272,13 @@ public interface WxMpDataCubeService {
List getInterfaceSummary(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取接口分析分时数据(getinterfacesummaryhour)
- * 详情请见文档:接口分析数据接口
+ *
+ * {@code 详情请见文档:接口分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getinterfacesummaryhour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index 468dced138..2d965bf8de 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -54,10 +54,10 @@ public interface WxMpService extends WxService {
WxMpShortKeyResult fetchShorten(String shortKey) throws WxErrorException;
/**
- *
* 验证消息的确来自微信服务器.
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
- *
+ *
+ * {@code 详情请见: 接入指南}
+ *
*
* @param timestamp 时间戳,字符串格式
* @param nonce 随机串,字符串格式
@@ -76,16 +76,19 @@ public interface WxMpService extends WxService {
String getAccessToken() throws WxErrorException;
/**
- *
* 获取access_token,本方法线程安全.
+ *
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
- *
+ *
+ *
* 另:本service的所有方法都会在access_token过期时调用此方法
- *
+ *
+ *
* 程序员在非必要情况下尽量不要主动调用此方法
- *
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
- *
+ *
+ *
+ * {@code 详情请见: 获取access_token}
+ *
*
* @param forceRefresh 是否强制刷新,true表示强制刷新,false表示使用缓存
* @return token access token,字符串格式
@@ -126,12 +129,13 @@ public interface WxMpService extends WxService {
String getJsapiTicket() throws WxErrorException;
/**
- *
* 获得jsapi_ticket.
+ *
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
- *
- * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
- *
+ *
+ *
+ * {@code 详情请见:JS-SDK使用权限签名算法}
+ *
*
* @param forceRefresh 强制刷新,true表示强制刷新,false表示使用缓存
* @return jsapi ticket,字符串格式
@@ -140,11 +144,10 @@ public interface WxMpService extends WxService {
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;
/**
- *
* 创建调用jsapi时所需要的签名.
- *
- * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
- *
+ *
+ * {@code 详情请见:JS-SDK使用权限签名算法}
+ *
*
* @param url 当前网页的URL,不包括#及其后面部分
* @return 生成的签名对象,包含签名、时间戳、随机串等信息
@@ -153,10 +156,10 @@ public interface WxMpService extends WxService {
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;
/**
- *
* 长链接转短链接接口.
- * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口
- *
+ *
+ * 详情请见: 长链接转短链接接口
+ *
*
* @param longUrl 长url,需要转换的原始URL
* @return 生成的短地址,字符串格式
@@ -167,10 +170,10 @@ public interface WxMpService extends WxService {
String shortUrl(String longUrl) throws WxErrorException;
/**
- *
* 语义查询接口.
- * 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解
- *
+ *
+ * 详情请见:语义理解
+ *
*
* @param semanticQuery 查询条件,包含查询内容、类型等信息
* @return 查询结果,包含语义理解的结果和建议回复
@@ -179,11 +182,13 @@ public interface WxMpService extends WxService {
WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException;
/**
- *
* 构造第三方使用网站应用授权登录的url.
- * 详情请见: 网站应用微信登录开发指南
- * URL格式为:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
- *
+ *
+ * {@code 详情请见: 网站应用微信登录开发指南}
+ *
+ *
+ * {@code URL格式为:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect}
+ *
*
* @param redirectUri 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
@@ -193,10 +198,7 @@ public interface WxMpService extends WxService {
String buildQrConnectUrl(String redirectUri, String scope, String state);
/**
- *
* 获取微信服务器IP地址
- * http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html
- *
*
* @return 微信服务器ip地址数组,包含所有微信服务器IP地址
* @throws WxErrorException 微信API调用异常
@@ -204,11 +206,10 @@ public interface WxMpService extends WxService {
String[] getCallbackIP() throws WxErrorException;
/**
- *
- * 网络检测
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21541575776DtsuT
- * 为了帮助开发者排查回调连接失败的问题,提供这个网络检测的API。它可以对开发者URL做域名解析,然后对所有IP进行一次ping操作,得到丢包率和耗时。
- *
+ * 网络检测
+ *
+ * 为了帮助开发者排查回调连接失败的问题,提供这个网络检测的API。它可以对开发者URL做域名解析,然后对所有IP进行一次ping操作,得到丢包率和耗时。
+ *
*
* @param action 执行的检测动作,可选值:all(全部检测)、dns(仅域名解析)、ping(仅网络连通性检测)
* @param operator 指定平台从某个运营商进行检测,可选值:CHINANET(中国电信)、UNICOM(中国联通)、CAP(中国联通)、CUCC(中国联通)
@@ -239,12 +240,13 @@ public interface WxMpService extends WxService {
WxMpCurrentAutoReplyInfo getCurrentAutoReplyInfo() throws WxErrorException;
/**
- *
- * 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零:
- * HTTP调用:https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN
- * 接口文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433744592
- *
- *
+ * 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零.
+ *
+ * HTTP调用:https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN
+ *
+ *
+ * {@code 接口文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433744592}
+ *
*
* @param appid 公众号的APPID,需要清零调用的公众号的appid
* @throws WxErrorException 微信API调用异常
@@ -252,11 +254,9 @@ public interface WxMpService extends WxService {
void clearQuota(String appid) throws WxErrorException;
/**
- *
* Service没有实现某个API的时候,可以用这个,
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法
- *
*
* @param 返回值类型
* @param 参数类型
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
index 63ca608eba..76ab466157 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
@@ -304,8 +304,9 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
/**
* 通过网络请求获取稳定版接口调用凭据
*
- * @return .
- * @throws IOException .
+ * @param forceRefresh 是否强制刷新
+ * @return access_token字符串
+ * @throws IOException IO异常
*/
protected abstract String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java
index 80e1658c16..936996ef69 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java
@@ -25,11 +25,11 @@ public class WxMpMassOpenIdsMessage implements Serializable {
/**
*
* 请使用
- * {@link WxConsts.MassMsgType#IMAGE}
- * {@link WxConsts.MassMsgType#MPNEWS}
- * {@link WxConsts.MassMsgType#TEXT}
- * {@link WxConsts.MassMsgType#MPVIDEO}
- * {@link WxConsts.MassMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPVIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#VOICE}
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
*
*/
@@ -60,6 +60,8 @@ public String toJson() {
/**
* 添加openid,最多支持10,000个
+ *
+ * @param openid 用户openid
*/
public void addUser(String openid) {
this.toUsers.add(openid);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java
index dca743c9c3..57b34d352a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java
@@ -19,11 +19,11 @@ public class WxMpMassPreviewMessage implements Serializable {
*
* 消息类型
* 请使用
- * {@link WxConsts.MassMsgType#IMAGE}
- * {@link WxConsts.MassMsgType#MPNEWS}
- * {@link WxConsts.MassMsgType#TEXT}
- * {@link WxConsts.MassMsgType#MPVIDEO}
- * {@link WxConsts.MassMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPVIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#VOICE}
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
*
*/
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java
index 598e5754f1..466ef8d96f 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java
@@ -24,11 +24,11 @@ public class WxMpMassTagMessage implements Serializable {
*
* 消息类型.
* 请使用
- * {@link WxConsts.MassMsgType#IMAGE}
- * {@link WxConsts.MassMsgType#MPNEWS}
- * {@link WxConsts.MassMsgType#TEXT}
- * {@link WxConsts.MassMsgType#MPVIDEO}
- * {@link WxConsts.MassMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPVIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#VOICE}
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
*
*/
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java
index 9e73b46159..ac4a596dd8 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java
@@ -63,9 +63,8 @@ public WxMpUserQuery add(String openid, String lang) {
/**
* 添加一个OpenId到列表中,并返回本对象
*
- *
* 该方法默认lang = zh_CN
- *
+ *
*
* @param openid openid
* @return {@link WxMpUserQuery}
@@ -100,6 +99,8 @@ public WxMpUserQuery remove(String openid, String lang) {
/**
* 获取查询参数列表
+ *
+ * @return 查询参数列表
*/
public List getQueryParamList() {
return this.queryParamList;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
index 6d7dde1ad6..34a9c56b99 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
@@ -8,7 +8,9 @@
/**
- * @author S
+ * 卡券图文消息HTML结果
+ *
+ * @author S (sshzh90@gmail.com)
*/
@Data
public class WxMpCardMpnewsGethtmlResult implements Serializable {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java
index d8634cfa3c..0adb413869 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java
@@ -39,8 +39,8 @@ public class MemberCardActivateUserFormRequest implements Serializable {
/**
* 绑定老会员卡信息
*
- * @param name
- * @param url
+ * @param name 名称
+ * @param url 链接地址
*/
public void setBindOldCard(String name, String url) {
if (StringUtils.isAnyEmpty(name, url)) {
@@ -56,8 +56,8 @@ public void setBindOldCard(String name, String url) {
/**
* 设置服务声明,用于放置商户会员卡守则
*
- * @param name
- * @param url
+ * @param name 名称
+ * @param url 链接地址
*/
public void setServiceStatement(String name, String url) {
if (StringUtils.isAnyEmpty(name, url)) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java
index 0c0fae3e2b..b3b0c9be5e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java
@@ -50,6 +50,7 @@ public class MemberCardUserForm implements Serializable {
/**
* 添加富文本类型字段
*
+ * @param field 富文本字段
*/
public void addRichField(MemberCardUserFormRichField field) {
if (field == null) {
@@ -64,6 +65,7 @@ public void addRichField(MemberCardUserFormRichField field) {
/**
* 添加微信选项类型字段
*
+ * @param fieldType 微信字段类型
*/
public void addWechatField(CardWechatFieldType fieldType) {
if (fieldType == null) {
@@ -78,6 +80,7 @@ public void addWechatField(CardWechatFieldType fieldType) {
/**
* 添加文本类型字段
*
+ * @param field 文本字段
*/
public void addCustomField(String field) {
if (StringUtils.isBlank(field)) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java
index 139db68557..1ba8a0e60c 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java
@@ -6,12 +6,13 @@
import java.io.Serializable;
/**
- *
* 控制原生消息结构体,包含各字段的消息控制字段。
- *
+ *
* 用于 `7 更新会员信息` 的接口参数调用
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283
- *
+ *
+ *
+ * {@code 参考:会员卡接口}
+ *
*
* @author YuJian(mgcnrx11@gmail.com)
* @version 2017/7/15
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java
index 663fe1f1e5..b4ad8eb139 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java
@@ -6,10 +6,10 @@
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
/**
- *
* 用于 `7 更新会员信息` 的接口调用后的返回结果
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283
- *
+ *
+ * {@code 参考:会员卡接口}
+ *
*
* @author YuJian(mgcnrx11@gmail.com)
* @version 2017/7/15
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java
index 8fad40ccf8..9a2b47f5bf 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java
@@ -6,11 +6,10 @@
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
/**
- *
* 拉取会员信息返回的结果
- *
- * 字段格式参考https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283 6.2.1小节的步骤5
- *
+ *
+ * {@code 字段格式参考:会员卡接口 6.2.1小节的步骤5}
+ *
*
* @author YuJian
* @version 2017/7/9
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java
index f066c1d934..01be3c08d2 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java
@@ -45,6 +45,8 @@ public class WxMpKefuMessage implements Serializable {
/**
* 获得文本消息builder.
+ *
+ * @return 文本消息builder
*/
public static TextBuilder TEXT() {
return new TextBuilder();
@@ -52,6 +54,8 @@ public static TextBuilder TEXT() {
/**
* 获得图片消息builder.
+ *
+ * @return 图片消息builder
*/
public static ImageBuilder IMAGE() {
return new ImageBuilder();
@@ -59,6 +63,8 @@ public static ImageBuilder IMAGE() {
/**
* 获得语音消息builder.
+ *
+ * @return 语音消息builder
*/
public static VoiceBuilder VOICE() {
return new VoiceBuilder();
@@ -66,6 +72,8 @@ public static VoiceBuilder VOICE() {
/**
* 获得视频消息builder.
+ *
+ * @return 视频消息builder
*/
public static VideoBuilder VIDEO() {
return new VideoBuilder();
@@ -73,6 +81,8 @@ public static VideoBuilder VIDEO() {
/**
* 获得音乐消息builder.
+ *
+ * @return 音乐消息builder
*/
public static MusicBuilder MUSIC() {
return new MusicBuilder();
@@ -80,6 +90,8 @@ public static MusicBuilder MUSIC() {
/**
* 获得图文消息(点击跳转到外链)builder.
+ *
+ * @return 图文消息builder
*/
public static NewsBuilder NEWS() {
return new NewsBuilder();
@@ -87,6 +99,8 @@ public static NewsBuilder NEWS() {
/**
* 获得图文消息(点击跳转到图文消息页面)builder.
+ *
+ * @return 图文消息builder
*/
public static MpNewsBuilder MPNEWS() {
return new MpNewsBuilder();
@@ -94,6 +108,8 @@ public static MpNewsBuilder MPNEWS() {
/**
* 获得卡券消息builder.
+ *
+ * @return 卡券消息builder
*/
public static WxCardBuilder WXCARD() {
return new WxCardBuilder();
@@ -101,6 +117,8 @@ public static WxCardBuilder WXCARD() {
/**
* 获得菜单消息builder.
+ *
+ * @return 菜单消息builder
*/
public static WxMsgMenuBuilder MSGMENU() {
return new WxMsgMenuBuilder();
@@ -108,20 +126,25 @@ public static WxMsgMenuBuilder MSGMENU() {
/**
* 小程序卡片.
+ *
+ * @return 小程序卡片builder
*/
public static MiniProgramPageBuilder MINIPROGRAMPAGE() {
return new MiniProgramPageBuilder();
}
/**
- * 发送图文消息(点击跳转到图文消息页面)使用通过 “发布” 系列接口得到的 article_id(草稿箱功能上线后不再支持客服接口中带 media_id 的 mpnews 类型的图文消息)
+ * 发送图文消息(点击跳转到图文消息页面)使用通过 “发布” 系列接口得到的 article_id
+ *
+ * @return 图文消息builder
*/
public static MpNewsArticleBuilder MPNEWSARTICLE() {
return new MpNewsArticleBuilder();
}
/**
- *
+ * 设置消息类型
+ *
* 请使用
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#TEXT}
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#IMAGE}
@@ -135,7 +158,9 @@ public static MpNewsArticleBuilder MPNEWSARTICLE() {
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#TASKCARD}
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#MSGMENU}
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#MP_NEWS_ARTICLE}
- *
+ *
+ *
+ * @param msgType 消息类型
*/
public void setMsgType(String msgType) {
this.msgType = msgType;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
index 3d5f4ac3a0..dfc88ab13b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
@@ -927,6 +927,7 @@ public static WxMpXmlMessage fromXml(InputStream is) {
* @param timestamp 时间戳
* @param nonce 随机串
* @param msgSignature 签名串
+ * @return 解密后的消息对象
*/
public static WxMpXmlMessage fromEncryptedXml(String encryptedXml, WxMpConfigStorage wxMpConfigStorage,
String timestamp, String nonce, String msgSignature) {
@@ -956,14 +957,16 @@ public WxMpXmlMessage decryptField(WxMpConfigStorage wxMpConfigStorage,
/**
*
* 当接受用户消息时,可能会获得以下值:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#LOCATION}
- * {@link WxConsts.XmlMsgType#LINK}
- * {@link WxConsts.XmlMsgType#EVENT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#LOCATION}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#LINK}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#EVENT}
*
+ *
+ * @return 消息类型
*/
public String getMsgType() {
return this.msgType;
@@ -972,13 +975,15 @@ public String getMsgType() {
/**
*
* 当发送消息的时候使用:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#NEWS}
- * {@link WxConsts.XmlMsgType#MUSIC}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#NEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#MUSIC}
*
+ *
+ * @param msgType 消息类型
*/
public void setMsgType(String msgType) {
this.msgType = msgType;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java
index a44aea740c..1f3143df7e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java
@@ -70,6 +70,8 @@ public abstract class WxMpXmlOutMessage implements Serializable {
/**
* 获得文本消息builder
+ *
+ * @return 文本消息构建器
*/
public static TextBuilder TEXT() {
return new TextBuilder();
@@ -77,6 +79,8 @@ public static TextBuilder TEXT() {
/**
* 获得图片消息builder
+ *
+ * @return 图片消息构建器
*/
public static ImageBuilder IMAGE() {
return new ImageBuilder();
@@ -84,6 +88,8 @@ public static ImageBuilder IMAGE() {
/**
* 获得语音消息builder
+ *
+ * @return 语音消息构建器
*/
public static VoiceBuilder VOICE() {
return new VoiceBuilder();
@@ -91,6 +97,8 @@ public static VoiceBuilder VOICE() {
/**
* 获得视频消息builder
+ *
+ * @return 视频消息构建器
*/
public static VideoBuilder VIDEO() {
return new VideoBuilder();
@@ -98,6 +106,8 @@ public static VideoBuilder VIDEO() {
/**
* 获得音乐消息builder
+ *
+ * @return 音乐消息构建器
*/
public static MusicBuilder MUSIC() {
return new MusicBuilder();
@@ -105,6 +115,8 @@ public static MusicBuilder MUSIC() {
/**
* 获得图文消息builder
+ *
+ * @return 图文消息构建器
*/
public static NewsBuilder NEWS() {
return new NewsBuilder();
@@ -112,6 +124,8 @@ public static NewsBuilder NEWS() {
/**
* 获得客服消息builder
+ *
+ * @return 客服消息构建器
*/
public static TransferCustomerServiceBuilder TRANSFER_CUSTOMER_SERVICE() {
return new TransferCustomerServiceBuilder();
@@ -119,11 +133,18 @@ public static TransferCustomerServiceBuilder TRANSFER_CUSTOMER_SERVICE() {
/**
* 获得设备消息builder
+ *
+ * @return 设备消息builder
*/
public static DeviceBuilder DEVICE() {
return new DeviceBuilder();
}
+ /**
+ * 转换成xml格式
+ *
+ * @return xml格式字符串
+ */
@SuppressWarnings("unchecked")
public String toXml() {
return XStreamTransformer.toXml((Class) this.getClass(), this);
@@ -131,6 +152,9 @@ public String toXml() {
/**
* 转换成加密的结果
+ *
+ * @param wxMpConfigStorage 公众号配置
+ * @return 加密后的消息对象
*/
public WxMpXmlOutMessage toEncrypted(WxMpConfigStorage wxMpConfigStorage) {
String plainXml = toXml();
@@ -146,6 +170,9 @@ public WxMpXmlOutMessage toEncrypted(WxMpConfigStorage wxMpConfigStorage) {
/**
* 转换成加密的xml格式
+ *
+ * @param wxMpConfigStorage 公众号配置
+ * @return 加密后的xml格式字符串
*/
public String toEncryptedXml(WxMpConfigStorage wxMpConfigStorage) {
String plainXml = toXml();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
index fe8f6e4043..58f2ea2693 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
@@ -9,7 +9,8 @@
/**
*
* 查询群发消息发送状态【订阅号与服务号认证后均可用】
- * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#%E6%9F%A5%E8%AF%A2%E7%BE%A4%E5%8F%91%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E7%8A%B6%E6%80%81%E3%80%90%E8%AE%A2%E9%98%85%E5%8F%B7%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%8F%B7%E8%AE%A4%E8%AF%81%E5%90%8E%E5%9D%87%E5%8F%AF%E7%94%A8%E3%80%91
+ * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html
+ *
* @author S
*/
@Data
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java
index 11aeef6124..1bebe86885 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java
@@ -24,7 +24,7 @@ public interface WxMpConfigStorage {
* Is use stable access token api
*
* @return the boolean
- * @link https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/getStableAccessToken.html
+ * @see 文档
*/
boolean isStableAccessToken();
@@ -211,6 +211,8 @@ public interface WxMpConfigStorage {
*
* {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
*
+ *
+ * @return 重试间隔毫秒数
*/
int getRetrySleepMillis();
@@ -219,6 +221,8 @@ public interface WxMpConfigStorage {
*
* {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
*
+ *
+ * @return 最大重试次数
*/
int getMaxRetryTimes();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java
index 870fa1e276..7939d57a18 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java
@@ -39,6 +39,8 @@ public WxMpRedisConfigImpl(WxRedisOps redisOps, String keyPrefix) {
/**
* 每个公众号生成独有的存储key.
+ *
+ * @param appId 公众号appId
*/
@Override
public void setAppId(String appId) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java
index e0d9e92af1..4982336f8a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java
@@ -42,6 +42,8 @@ private WxMpRedissonConfigImpl(@NonNull WxRedisOps redisOps, String keyPrefix) {
/**
* 每个公众号生成独有的存储key.
+ *
+ * @param appId 公众号appId
*/
@Override
public void setAppId(String appId) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
index b2e984b0f9..4dfee6295e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
@@ -20,7 +20,7 @@ public class WxMpEventConstants {
public static final String SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info";
/**
- * 微信摇一摇周边>>摇一摇事件通知.
+ * 微信摇一摇周边-摇一摇事件通知.
*/
public static final String SHAKEAROUND_USER_SHAKE = "ShakearoundUserShake";
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
index 99d759f32f..7757ad78bf 100755
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
@@ -27,7 +27,7 @@ public class WxMpCryptUtil extends me.chanjar.weixin.common.util.crypto.WxCryptU
/**
* 构造函数
*
- * @param wxMpConfigStorage
+ * @param wxMpConfigStorage 公众号配置存储
*/
public WxMpCryptUtil(WxMpConfigStorage wxMpConfigStorage) {
/*
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java
index ace711a236..55e92700de 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java
@@ -33,7 +33,12 @@ public class XStreamTransformer {
}
/**
- * xml -> pojo.
+ * {@code xml -> pojo.}
+ *
+ * @param 返回类型
+ * @param clazz 类型
+ * @param xml xml字符串
+ * @return 转换后的对象
*/
@SuppressWarnings("unchecked")
public static T fromXml(Class clazz, String xml) {
@@ -41,6 +46,14 @@ public static T fromXml(Class clazz, String xml) {
return object;
}
+ /**
+ * {@code xml -> pojo.}
+ *
+ * @param 返回类型
+ * @param clazz 类型
+ * @param is 输入流
+ * @return 转换后的对象
+ */
@SuppressWarnings("unchecked")
public static T fromXml(Class clazz, InputStream is) {
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
@@ -48,7 +61,12 @@ public static T fromXml(Class clazz, InputStream is) {
}
/**
- * pojo -> xml.
+ * {@code pojo -> xml.}
+ *
+ * @param 类型参数
+ * @param clazz 类型
+ * @param object 对象
+ * @return xml字符串
*/
public static String toXml(Class clazz, T object) {
return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java
index a0641379fb..346d427fc4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java
@@ -13,7 +13,7 @@
*
*
* @author xgl
- * @date 2025/12/20
+ * @since 2025/12/20
*/
@Data
@NoArgsConstructor
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java
index 86915d0956..364c9080d8 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java
@@ -5,7 +5,8 @@
*
* @author Pursuer
* @version 1.0
- * @date 2023/6/15
+ * @since 2023/6/15
+ * @param 解密后的数据类型
*/
public interface WxPayBaseNotifyV3Result {
/**
@@ -13,9 +14,8 @@ public interface WxPayBaseNotifyV3Result {
*
* @param rawData 原始数据
* @author Pursuer
- * @date 2023/6/15
- * @since 1.0
- **/
+ * @since 2023/6/15
+ */
void setRawData(OriginNotifyResponse rawData);
/**
@@ -23,8 +23,7 @@ public interface WxPayBaseNotifyV3Result {
*
* @param data 解密后的数据
* @author Pursuer
- * @date 2023/6/15
- * @since 1.0
- **/
+ * @since 2023/6/15
+ */
void setResult(T data);
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java
index b9d7f4d9f6..eac5f7c9de 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java
@@ -28,8 +28,8 @@ public class WxPayNotifyV3Response {
/**
* 返回成功
*
- * @param msg
- * @return
+ * @param msg 返回消息
+ * @return 成功响应的JSON字符串
*/
public static String success(String msg) {
WxPayNotifyV3Response response = new WxPayNotifyV3Response(SUCCESS, msg);
@@ -40,7 +40,7 @@ public static String success(String msg) {
* 返回失败
*
* @param msg 返回信息,如非空,为错误原因
- * @return
+ * @return 失败响应的JSON字符串
*/
public static String fail(String msg) {
WxPayNotifyV3Response response = new WxPayNotifyV3Response(FAIL, msg);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index a3a9dc7a92..b42451a666 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -325,7 +325,8 @@ public SSLContext initSSLContext() throws WxPayException {
*
* @return org.apache.http.impl.client.CloseableHttpClient
* @author doger.wang
- **/
+ * @throws WxPayException 微信支付异常
+ */
public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
if (StringUtils.isBlank(this.getApiV3Key())) {
throw new WxPayException("请确保apiV3Key值已设置");
@@ -663,6 +664,8 @@ public CloseableHttpClient initSslHttpClient() throws WxPayException {
/**
* 配置HTTP代理
+ *
+ * @param httpClientBuilder HttpClient构建器
*/
private void configureProxy(org.apache.http.impl.client.HttpClientBuilder httpClientBuilder) {
if (StringUtils.isNotBlank(this.getHttpProxyHost()) && this.getHttpProxyPort() > 0) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java
index b641cbe537..c4ad966415 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java
@@ -17,7 +17,7 @@ public class RequestUtils {
/**
* 获取请求头数据,微信V3版本回调使用
*
- * @param request
+ * @param request HTTP请求对象
* @return 字符串
*/
public static String readData(HttpServletRequest request) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java
index ac68b00bb4..51dd8fbbb6 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java
@@ -23,6 +23,10 @@ public class ResourcesUtils {
* - {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()}
* - if
callingClass is provided: {@link Class#getClassLoader() callingClass.getClassLoader()}
* - * 默认接口实现类,使用apache httpclient实现 + * 默认接口实现类,使用apache httpClient 5实现 * Created by Binary Wang on 2017-5-27. ** * @author Binary Wang */ -public class WxMpServiceImpl extends WxMpServiceHttpClientImpl { +public class WxMpServiceImpl extends WxMpServiceHttpComponentsImpl { } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceHttpComponentsImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceHttpComponentsImpl.java new file mode 100644 index 0000000000..913c1971cb --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceHttpComponentsImpl.java @@ -0,0 +1,74 @@ +package me.chanjar.weixin.open.api.impl; + +import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpClientType; +import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; + +/** + * httpclient 5 实现 + * + * @author zhangyl + */ +public class WxOpenServiceHttpComponentsImpl extends WxOpenServiceAbstractImpl
- * 微信支付接口请求实现类,默认使用Apache HttpClient实现 + * 微信支付接口请求实现类,默认使用Apache HttpClient 5实现 * Created by Binary Wang on 2017-7-8. ** * @author Binary Wang */ -public class WxPayServiceImpl extends WxPayServiceApacheHttpImpl { +public class WxPayServiceImpl extends WxPayServiceHttpComponentsImpl { } diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java index 45e87204cb..2e1314b3b1 100644 --- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java +++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java @@ -2,11 +2,11 @@ /** *
- * 默认接口实现类,使用apache httpclient实现 + * 默认接口实现类,使用apache httpClient 5实现 * Created by Binary Wang on 2017-5-27. ** * @author Binary Wang */ -public class WxQidianServiceImpl extends WxQidianServiceHttpClientImpl { +public class WxQidianServiceImpl extends WxQidianServiceHttpComponentsImpl { } From 72fa3b3301967c29031fabdad1574951a19b1c48 Mon Sep 17 00:00:00 2001 From: buaazyl
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 2e896cda7e..5347099a0b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -263,6 +263,9 @@ public WxPayRefundResult refundV2(WxPayRefundRequest request) throws WxPayExcept
@Override
public WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayException {
+ if (StringUtils.isBlank(request.getNotifyUrl())) {
+ request.setNotifyUrl(this.getConfig().getRefundNotifyUrl());
+ }
String url = String.format("%s/v3/refund/domestic/refunds", this.getPayBaseUrl());
String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(response, WxPayRefundV3Result.class);
@@ -270,6 +273,9 @@ public WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayEx
@Override
public WxPayRefundV3Result partnerRefundV3(WxPayPartnerRefundV3Request request) throws WxPayException {
+ if (StringUtils.isBlank(request.getNotifyUrl())) {
+ request.setNotifyUrl(this.getConfig().getRefundNotifyUrl());
+ }
if (StringUtils.isBlank(request.getSubMchid())) {
request.setSubMchid(this.getConfig().getSubMchId());
}
From e27325f58090db4e8801dfb433795668f1fe23bd Mon Sep 17 00:00:00 2001
From: Noctis_nkt
Date: Fri, 9 Jan 2026 14:18:23 +0800
Subject: [PATCH 094/111] =?UTF-8?q?=E3=80=90=E4=BC=81=E4=B8=9A=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E3=80=91=E6=96=B0=E5=A2=9E=E7=AE=A1=E7=90=86=E8=A1=A8?=
=?UTF-8?q?=E6=A0=BC=E5=86=85=E5=AE=B9=E7=9B=B8:new:=20#3837=20=E3=80=90?=
=?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E5=BE=AE=E6=96=87?=
=?UTF-8?q?=E6=A1=A3=EF=BC=88WeDoc=EF=BC=89=E6=9C=8D=E5=8A=A1=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=E4=BA=86=E4=B8=89=E4=B8=AA=E7=AE=A1=E7=90=86=E8=A1=A8?=
=?UTF-8?q?=E6=A0=BC=E5=86=85=E5=AE=B9=E7=9A=84=20API=20=E6=8E=A5=E5=8F=A3?=
=?UTF-8?q?=EF=BC=8C=E5=8C=85=E6=8B=AC=E6=89=B9=E9=87=8F=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E8=A1=A8=E6=A0=BC=E5=86=85=E5=AE=B9=E3=80=81=E8=8E=B7=E5=8F=96?=
=?UTF-8?q?=E8=A1=A8=E6=A0=BC=E8=A1=8C=E5=88=97=E4=BF=A1=E6=81=AF=E4=BB=A5?=
=?UTF-8?q?=E5=8F=8A=E8=8E=B7=E5=8F=96=E6=8C=87=E5=AE=9A=E8=8C=83=E5=9B=B4?=
=?UTF-8?q?=E7=9A=84=E8=A1=A8=E6=A0=BC=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../weixin/cp/api/WxCpOaWeDocService.java | 49 ++++
.../cp/api/impl/WxCpOaWeDocServiceImpl.java | 23 ++
.../doc/WxCpDocSheetBatchUpdateRequest.java | 199 ++++++++++++++
.../doc/WxCpDocSheetBatchUpdateResponse.java | 126 +++++++++
.../cp/bean/oa/doc/WxCpDocSheetData.java | 260 ++++++++++++++++++
.../oa/doc/WxCpDocSheetGetDataRequest.java | 64 +++++
.../bean/oa/doc/WxCpDocSheetProperties.java | 79 ++++++
.../weixin/cp/constant/WxCpApiPathConsts.java | 15 +
8 files changed, 815 insertions(+)
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java
index 1356c839b2..d63d32694a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java
@@ -78,4 +78,53 @@ public interface WxCpOaWeDocService {
* @throws WxErrorException the wx error exception
*/
WxCpDocShare docShare(@NonNull String docId) throws WxErrorException;
+
+ /**
+ * 编辑表格内容
+ * 该接口可以对一个在线表格批量执行多个更新操作
+ *
+ * 注意:
+ * 1.批量更新请求中的各个操作会逐个按顺序执行,直到全部执行完成则请求返回,或者其中一个操作报错则不再继续执行后续的操作
+ * 2.每一个更新操作在执行之前都会做请求校验(包括权限校验、参数校验等等),如果校验未通过则该更新操作会报错并返回,不再执行后续操作
+ * 3.单次批量更新请求的操作数量 <= 5
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/batch_update?access_token=ACCESS_TOKEN
+ *
+ * @param request 编辑表格内容请求参数
+ * @return 编辑表格内容批量更新的响应结果
+ * @throws WxErrorException the wx error exception
+ */
+ WxCpDocSheetBatchUpdateResponse docBatchUpdate(@NonNull WxCpDocSheetBatchUpdateRequest request) throws WxErrorException;
+
+ /**
+ * 获取表格行列信息
+ * 该接口用于获取在线表格的工作表、行数、列数等。
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/get_sheet_properties?access_token=ACCESS_TOKEN
+ *
+ * @param docId 在线表格的docid
+ * @return 返回表格行列信息
+ * @throws WxErrorException
+ */
+ WxCpDocSheetProperties getSheetProperties(@NonNull String docId) throws WxErrorException;
+
+
+ /**
+ * 本接口用于获取指定范围内的在线表格信息,单次查询的范围大小需满足以下限制:
+ *
+ * 查询范围行数 <=1000
+ * 查询范围列数 <=200
+ * 范围内的总单元格数量 <=10000
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/get_sheet_range_data?access_token=ACCESS_TOKEN
+ *
+ * @param request 获取指定范围内的在线表格信息请求参数
+ * @return 返回指定范围内的在线表格信息
+ * @throws WxErrorException
+ */
+ WxCpDocSheetData getSheetRangeData(@NonNull WxCpDocSheetGetDataRequest request) throws WxErrorException;
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java
index 81de32453d..fc5379dc73 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java
@@ -63,4 +63,27 @@ public WxCpDocShare docShare(@NonNull String docId) throws WxErrorException {
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
return WxCpDocShare.fromJson(responseContent);
}
+
+ @Override
+ public WxCpDocSheetBatchUpdateResponse docBatchUpdate(@NonNull WxCpDocSheetBatchUpdateRequest request) throws WxErrorException {
+ String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_BATCH_UPDATE);
+ String responseContent = this.cpService.post(apiUrl, request.toJson());
+ return WxCpDocSheetBatchUpdateResponse.fromJson(responseContent);
+ }
+
+ @Override
+ public WxCpDocSheetProperties getSheetProperties(@NonNull String docId) throws WxErrorException {
+ String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES);
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("docid", docId);
+ String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
+ return WxCpDocSheetProperties.fromJson(responseContent);
+ }
+
+ @Override
+ public WxCpDocSheetData getSheetRangeData(@NonNull WxCpDocSheetGetDataRequest request) throws WxErrorException {
+ String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA);
+ String responseContent = this.cpService.post(apiUrl, request.toJson());
+ return WxCpDocSheetData.fromJson(responseContent);
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java
new file mode 100644
index 0000000000..37c42717e7
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java
@@ -0,0 +1,199 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.cp.bean.oa.doc.WxCpDocSheetData.GridData;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 编辑表格内容请求
+ *
+ * @author zhongying
+ * @since 2026-01-07
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxCpDocSheetBatchUpdateRequest implements Serializable {
+ private static final long serialVersionUID = 584565591133421347L;
+
+ /**
+ * 文档的docid.必填
+ */
+ @SerializedName("docid")
+ private String docId;
+
+ /**
+ * 更新操作列表.必填
+ */
+ @SerializedName("requests")
+ private List requests;
+
+ /**
+ * From json wx cp doc sheet batch update request.
+ *
+ * @param json the json
+ * @return the wx cp doc sheet batch update request
+ */
+ public static WxCpDocSheetBatchUpdateRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetBatchUpdateRequest.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 更新操作
+ */
+ @Getter
+ @Setter
+ public static class Request implements Serializable {
+ private static final long serialVersionUID = 253933038745894628L;
+
+ /**
+ * 新增工作表
+ */
+ @SerializedName("add_sheet_request")
+ private AddSheetRequest addSheetRequest;
+
+ /**
+ * 删除工作表请求
+ */
+ @SerializedName("delete_sheet_request")
+ private DeleteSheetRequest deleteSheetRequest;
+
+ /**
+ * 更新范围内单元格内容
+ */
+ @SerializedName("update_range_request")
+ private UpdateRangeRequest updateRangeRequest;
+
+ /**
+ * 删除表格连续的行或列
+ */
+ @SerializedName("delete_dimension_request")
+ private DeleteDimensionRequest deleteDimensionRequest;
+
+ /**
+ * 新增工作表,新增需满足以下限制
+ * 范围列数 <=200
+ * 范围内的总单元格数量 <=10000
+ */
+ @Getter
+ @Setter
+ public static class AddSheetRequest implements Serializable {
+ private static final long serialVersionUID = 523704967699486288L;
+
+ /**
+ * 工作表名称
+ */
+ @SerializedName("title")
+ private String title;
+
+ /**
+ * 新增工作表的初始行数
+ */
+ @SerializedName("row_count")
+ private int rowCount;
+
+ /**
+ * 新增工作表的初始列数
+ */
+ @SerializedName("column_count")
+ private int columnCount;
+ }
+
+ /**
+ * 删除工作表请求
+ */
+ @Getter
+ @Setter
+ public static class DeleteSheetRequest implements Serializable {
+ private static final long serialVersionUID = 767974765396168274L;
+
+ /**
+ * 工作表唯一标识
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+ }
+
+ /**
+ * 更新范围内单元格内容
+ */
+ @Getter
+ @Setter
+ public static class UpdateRangeRequest implements Serializable {
+ private static final long serialVersionUID = 433859595039061888L;
+
+ /**
+ * 工作表唯一标识
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+
+ /**
+ * 表格数据
+ */
+ @SerializedName("grid_data")
+ private GridData gridData;
+ }
+
+ /**
+ * 删除表格连续的行或列
+ * 注意:
+ * 1.该操作会导致表格缩表
+ * 2.删除的范围遵循 左闭右开 ———— [start_index,end_index) ,如果 end_index <= start_index
+ * 则该请求报错。
+ */
+ @Getter
+ @Setter
+ public static class DeleteDimensionRequest implements Serializable {
+ private static final long serialVersionUID = 107245521502978033L;
+
+ /**
+ * 工作表唯一标识
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+
+ /**
+ * 删除的维度类型.
+ * ROW:行
+ * COLUMN:列
+ */
+ @SerializedName("dimension")
+ private String dimension;
+
+ /**
+ * 删除行列的起始序号(从1开始)
+ */
+ @SerializedName("start_index")
+ private int startIndex;
+
+ /**
+ * 删除行列的终止序号(从1开始)
+ */
+ @SerializedName("end_index")
+ private int endIndex;
+ }
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java
new file mode 100644
index 0000000000..8e31e9022f
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java
@@ -0,0 +1,126 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.oa.doc.WxCpDocSheetProperties.Properties;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 更新操作(UpdateRequest])对应的响应结构体类型
+ *
+ * @author zhongying
+ * @since 2026-01-08
+ */
+@Data
+public class WxCpDocSheetBatchUpdateResponse implements Serializable {
+ private static final long serialVersionUID = 781694717017131015L;
+
+ /**
+ * 新增工作表响应
+ */
+ @SerializedName("add_sheet_response")
+ private AddSheetResponse addSheetResponse;
+
+ /**
+ * 删除工作表响应
+ */
+ @SerializedName("delete_sheet_response")
+ private DeleteSheetResponse deleteSheetResponse;
+
+ /**
+ * 更新范围内单元格内容响应
+ */
+ @SerializedName("update_range_response")
+ private UpdateRangeResponse updateRangeResponse;
+
+ /**
+ * 删除表格连续的行或列响应
+ */
+ @SerializedName("delete_dimension_response")
+ private DeleteDimensionResponse deleteDimensionResponse;
+
+ /**
+ * 从 JSON 字符串反序列化为 WxCpDocSheetBatchUpdateResponse 对象。
+ *
+ * @param json the json
+ * @return 反序列化得到的 WxCpDocSheetBatchUpdateResponse 对象
+ */
+ public static WxCpDocSheetBatchUpdateResponse fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetBatchUpdateResponse.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 新增工作表响应
+ */
+ @Getter
+ @Setter
+ public static class AddSheetResponse implements Serializable {
+ private static final long serialVersionUID = 618814209485621336L;
+
+ /**
+ * 新增子表的属性
+ */
+ @SerializedName("properties")
+ private Properties properties;
+ }
+
+ /**
+ * 删除工作表响应
+ */
+ @Getter
+ @Setter
+ public static class DeleteSheetResponse implements Serializable {
+ private static final long serialVersionUID = 625927337093938411L;
+
+ /**
+ * 被删除的工作表的唯一标识
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+ }
+
+ /**
+ * 更新范围内单元格内容响应
+ */
+ @Getter
+ @Setter
+ public static class UpdateRangeResponse implements Serializable {
+ private static final long serialVersionUID = 180842641209787414L;
+
+ /**
+ * 数据更新的成功的单元格数量
+ */
+ @SerializedName("updated_cells")
+ private int updatedCells;
+ }
+
+ /**
+ * 删除表格连续的行(或列),请求响应体结构
+ */
+ @Getter
+ @Setter
+ public static class DeleteDimensionResponse implements Serializable {
+ private static final long serialVersionUID = 107245521502978033L;
+
+ /**
+ * 被删除的行数(或列数)
+ */
+ @SerializedName("deleted")
+ private int deleted;
+ }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java
new file mode 100644
index 0000000000..15e76430b7
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java
@@ -0,0 +1,260 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 获取表格数据
+ *
+ * @author zhongying
+ * @since 2026-01-07
+ */
+@Data
+public class WxCpDocSheetData extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = 498054945993671723L;
+
+ /**
+ * 返回data
+ */
+ @SerializedName("grid_data")
+ private GridData gridData;
+
+ /**
+ * From json sheet data.
+ *
+ * @param json the json
+ * @return the sheet data
+ */
+ public static WxCpDocSheetData fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetData.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ @Override
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 表格数据
+ */
+ @Getter
+ @Setter
+ public static class GridData implements Serializable {
+ private static final long serialVersionUID = 389887488098521821L;
+
+ /**
+ * 起始行编号 (从0开始计算)
+ */
+ @SerializedName("start_row")
+ private int startRow;
+
+ /**
+ * 起始列编号 (从0开始计算)
+ */
+ @SerializedName("start_column")
+ private int startColumn;
+
+ /**
+ * 各行的数据
+ */
+ @SerializedName("rows")
+ private List rows;
+
+ /**
+ * 行数据
+ */
+ @Getter
+ @Setter
+ public static class RowData implements Serializable {
+ private static final long serialVersionUID = 225648868492722337L;
+
+ /**
+ * 各个单元格的数据内容
+ */
+ @SerializedName("values")
+ private List values;
+
+ /**
+ * 单元格的信息
+ */
+ @Getter
+ @Setter
+ public static class CellData implements Serializable {
+ private static final long serialVersionUID = 656471192719707304L;
+
+ /**
+ * 单元格的数据内容
+ */
+ @SerializedName("cell_value")
+ private CellValue cellValue;
+
+ /**
+ * 单元格的样式信息
+ */
+ @SerializedName("cell_format")
+ private CellFormat cellFormat;
+
+ /**
+ * 单元格的数据内容,暂时只支持文本、链接,一个CellValue中只能选填一个字段
+ */
+ @Getter
+ @Setter
+ public static class CellValue implements Serializable {
+ private static final long serialVersionUID = 656471192719707304L;
+
+ /**
+ * 文本
+ */
+ @SerializedName("text")
+ private String text;
+
+ /**
+ * 链接
+ */
+ @SerializedName("link")
+ private Link link;
+
+ /**
+ * 链接
+ */
+ @Getter
+ @Setter
+ public static class Link implements Serializable {
+ private static final long serialVersionUID = 912896452879347178L;
+
+ /**
+ * 链接url
+ */
+ @SerializedName("url")
+ private String url;
+
+ /**
+ * 链接标题
+ */
+ @SerializedName("text")
+ private String text;
+ }
+ }
+
+ /**
+ * 单元格的样式信息
+ */
+ @Getter
+ @Setter
+ public static class CellFormat implements Serializable {
+ private static final long serialVersionUID = 656471192719707304L;
+
+ /**
+ * 文字样式
+ */
+
+ @SerializedName("text_format")
+ private TextFormat textFormat;
+
+ /**
+ * 文字样式
+ */
+ @Getter
+ @Setter
+ public static class TextFormat implements Serializable {
+ private static final long serialVersionUID = 184358104921122206L;
+
+ /**
+ * 字体
+ */
+ @SerializedName("font")
+ private String font;
+
+ /**
+ * 字体大小,最大72
+ */
+ @SerializedName("font_size")
+ private int fontSize;
+
+ /**
+ * 字体加粗
+ */
+ @SerializedName("bold")
+ private boolean bold;
+
+ /**
+ * 斜体
+ */
+ @SerializedName("italic")
+ private boolean italic;
+
+ /**
+ * 字体删除线
+ */
+ @SerializedName("strikethrough")
+ private boolean strikethrough;
+
+ /**
+ * 字体下划线
+ */
+ @SerializedName("underline")
+ private boolean underline;
+
+ /**
+ * 字体颜色
+ */
+ @SerializedName("color")
+ private Color color;
+
+ /**
+ * 字体颜色
+ */
+ @Getter
+ @Setter
+ public static class Color implements Serializable {
+ private static final long serialVersionUID = 140418085882147315L;
+
+ /**
+ * 红色,取值范围:[0,255]
+ */
+ @SerializedName("red")
+ private int red;
+
+ /**
+ * 绿色,取值范围:[0,255]
+ */
+ @SerializedName("green")
+ private int green;
+
+ /**
+ * 蓝色,取值范围:[0,255]
+ */
+ @SerializedName("blue")
+ private int blue;
+
+ /**
+ * alpha通道,取值范围:[0,255],默认值为255完全不透明
+ */
+ @SerializedName("alpha")
+ private int alpha;
+
+ }
+
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java
new file mode 100644
index 0000000000..df2907aa31
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java
@@ -0,0 +1,64 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 获取表格数据请求
+ *
+ * @author zhongying
+ * @since 2026-01-07
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxCpDocSheetGetDataRequest implements Serializable {
+ private static final long serialVersionUID = 827718590347822812L;
+
+ /**
+ * 在线表格唯一标识.必填
+ */
+ @SerializedName("docid")
+ private String docId;
+
+ /**
+ * 工作表ID,工作表的唯一标识.必填
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+
+ /**
+ * 查询的范围,格式为"A1:B2",表示获取A1到B2单元格的数据.必填
+ */
+ @SerializedName("range")
+ private String range;
+
+ /**
+ * From json to {@link WxCpDocSheetGetDataRequest}.
+ *
+ * @param json the json string representing {@link WxCpDocSheetGetDataRequest}
+ * @return the {@link WxCpDocSheetGetDataRequest} object
+ */
+ public static WxCpDocSheetGetDataRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetGetDataRequest.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java
new file mode 100644
index 0000000000..6e67965fe0
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java
@@ -0,0 +1,79 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 获取表格行列信息
+ *
+ * @author zhongying
+ * @since 2026-01-07
+ */
+@Data
+public class WxCpDocSheetProperties extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = 666707252457243065L;
+
+ @SerializedName("properties")
+ private List properties;
+
+ /**
+ * From json sheet properties.
+ *
+ * @param json the json
+ * @return the sheet properties
+ */
+ public static WxCpDocSheetProperties fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetProperties.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 工作表属性
+ */
+ @Getter
+ @Setter
+ public static class Properties implements Serializable {
+ private static final long serialVersionUID = 640301090538839892L;
+
+ /**
+ * 工作表ID,工作表的唯一标识
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+
+ /**
+ * 工作表名称
+ */
+ @SerializedName("title")
+ private String title;
+
+ /**
+ * 表格的总行数
+ */
+ @SerializedName("row_count")
+ private int rowCount;
+
+ /**
+ * 表格的总列数
+ */
+ @SerializedName("column_count")
+ private int columnCount;
+
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index ade813ef5c..82bb811b3c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -575,6 +575,21 @@ interface Oa {
*/
String WEDOC_DOC_SHARE = "/cgi-bin/wedoc/doc_share";
+ /**
+ * The constant WEDOC_SPREADSHEET_BATCH_UPDATE.
+ */
+ String WEDOC_SPREADSHEET_BATCH_UPDATE = "/cgi-bin/wedoc/spreadsheet/batch_update";
+
+ /**
+ * The constant WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES.
+ */
+ String WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES = "/cgi-bin/wedoc/spreadsheet/get_sheet_properties";
+
+ /**
+ * The constant WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA.
+ */
+ String WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA = "/cgi-bin/wedoc/spreadsheet/get_sheet_range_data";
+
/**
* 邮件
* https://developer.work.weixin.qq.com/document/path/95486
From 397bbe04c4376ab6f73717acd10a2558c6728906 Mon Sep 17 00:00:00 2001
From: buaazyl
Date: Sat, 10 Jan 2026 14:23:38 +0800
Subject: [PATCH 095/111] =?UTF-8?q?:art:=20#3844=20=E3=80=90=E5=9F=BA?=
=?UTF-8?q?=E7=A1=80=E6=9E=B6=E6=9E=84=E3=80=91=E5=B0=86=20httpclient4=20?=
=?UTF-8?q?=E4=BE=9D=E8=B5=96=E7=9A=84=20scope=20=E4=BB=8E=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E7=9A=84=20compile=20=E6=94=B9=E4=B8=BA=20provided?=
=?UTF-8?q?=EF=BC=8C=E4=BB=A5=E4=BE=BF=E8=AE=A9=E4=BD=BF=E7=94=A8=E8=80=85?=
=?UTF-8?q?=E5=8F=AF=E4=BB=A5=E6=9B=B4=E7=81=B5=E6=B4=BB=E5=9C=B0=E9=80=89?=
=?UTF-8?q?=E6=8B=A9=20HTTP=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/HTTPCLIENT_UPGRADE_GUIDE.md | 10 +++++-----
pom.xml | 2 +-
weixin-java-channel/pom.xml | 10 ++++++++++
weixin-java-common/pom.xml | 2 ++
weixin-java-cp/pom.xml | 5 +++++
weixin-java-miniapp/pom.xml | 10 ++++++++++
weixin-java-mp/pom.xml | 10 ++++++++++
weixin-java-open/pom.xml | 10 ++++++++++
weixin-java-pay/pom.xml | 8 ++++++++
weixin-java-qidian/pom.xml | 5 +++++
10 files changed, 66 insertions(+), 6 deletions(-)
diff --git a/docs/HTTPCLIENT_UPGRADE_GUIDE.md b/docs/HTTPCLIENT_UPGRADE_GUIDE.md
index 945341966e..5cabb10674 100644
--- a/docs/HTTPCLIENT_UPGRADE_GUIDE.md
+++ b/docs/HTTPCLIENT_UPGRADE_GUIDE.md
@@ -43,8 +43,8 @@
### 对现有项目
- **向后兼容**:不需要修改任何代码
-- HttpClient 4.x 依然可用,项目同时包含两个版本的依赖
-- 如果希望继续使用 HttpClient 4.x,只需在配置中显式指定
+- 如果希望继续使用 HttpClient 4.x,只需在配置中显式指定,pay 模块会自动包含 httpclient4 依赖(因为某些接口必须使用 httpclient4)
+ 其他模块(mp、miniapp、cp、open、channel、qidian)如果需要使用 httpclient4,必须显式在项目中添加 httpclient4 依赖
## 迁移步骤
@@ -94,10 +94,10 @@ WxMpService wxMpService = new WxMpServiceHttpClientImpl();
A: 不会。项目保持完全向后兼容,HttpClient 4.x 的所有实现都保持不变。
### Q: 我需要修改代码吗?
-A: 大多数情况下不需要。如果希望继续使用 HttpClient 4.x,只需在配置中指定 `http-client-type=HttpClient` 即可。
+A: 大多数情况下不需要。如果希望继续使用 HttpClient 4.x,只需在配置中指定 `http-client-type=HttpClient` ,并引入 HttpClient 4.x 依赖即可。
### Q: 我可以在同一个项目中同时使用两个版本吗?
-A: 可以。不同的模块可以配置使用不同的 HTTP 客户端。例如,MP 模块使用 HttpClient 5.x,MiniApp 模块默认使用 HttpClient 4.x,但也可以按需配置为 HttpClient 5.x。
+A: 可以。不同的模块可以配置使用不同的 HTTP 客户端。例如,MP 模块使用 HttpClient 5.x,pay 模块部分接口仍使用 HttpClient 4.x,但也可以按需配置为 HttpClient 5.x。
### Q: 如何排除不需要的依赖?
A: 如果只想使用一个版本,可以在 `pom.xml` 中排除另一个:
@@ -196,4 +196,4 @@ weixin-java-common/
- ✅ **推荐使用 HttpClient 5.x**:性能更好,功能更强
- ✅ **向后兼容**:可以继续使用 HttpClient 4.x
- ✅ **灵活配置**:支持多种 HTTP 客户端,按需选择
-- ✅ **平滑迁移**:无需修改代码,仅需配置即可
+- ✅ **平滑迁移**:无需修改代码,仅需配置,若不使用 HttpClient 5.x ,引入其他依赖即可
diff --git a/pom.xml b/pom.xml
index 5740a27790..d58a677b28 100644
--- a/pom.xml
+++ b/pom.xml
@@ -136,7 +136,7 @@
UTF-8
4.5.13
- 5.5
+ 5.5.2
9.4.57.v20241219
diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml
index 13dd26fa98..c15ea302e9 100644
--- a/weixin-java-channel/pom.xml
+++ b/weixin-java-channel/pom.xml
@@ -29,6 +29,16 @@
jodd-http
provided
+ * {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+ * {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+ *
+ */
+ private int maxRetryTimes = 5;
+
+ /**
+ * http 请求重试间隔
+ *
+ * {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+ * {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+ *
+ */
+ private int retrySleepMillis = 1000;
+
+ /**
+ * 连接超时时间,单位毫秒
+ */
+ private int connectionTimeout = 5000;
+
+ /**
+ * 读数据超时时间,即socketTimeout,单位毫秒
+ */
+ private int soTimeout = 5000;
+
+ /**
+ * 从连接池获取链接的超时时间,单位毫秒
+ */
+ private int connectionRequestTimeout = 5000;
+ }
+
+ public enum StorageType {
+ /**
+ * 内存
+ */
+ memory,
+ /**
+ * jedis
+ */
+ jedis,
+ /**
+ * redisson
+ */
+ redisson,
+ /**
+ * redisTemplate
+ */
+ redistemplate
+ }
+
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java
new file mode 100644
index 0000000000..ae6d5368d7
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java
@@ -0,0 +1,57 @@
+package com.binarywang.spring.starter.wxjava.open.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信开放平台多账号Redis配置.
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class WxOpenMultiRedisProperties implements Serializable {
+ private static final long serialVersionUID = -5924815351660074401L;
+
+ /**
+ * 主机地址.
+ */
+ private String host = "127.0.0.1";
+
+ /**
+ * 端口号.
+ */
+ private int port = 6379;
+
+ /**
+ * 密码.
+ */
+ private String password;
+
+ /**
+ * 超时.
+ */
+ private int timeout = 2000;
+
+ /**
+ * 数据库.
+ */
+ private int database = 0;
+
+ /**
+ * sentinel ips
+ */
+ private String sentinelIps;
+
+ /**
+ * sentinel name
+ */
+ private String sentinelName;
+
+ private Integer maxActive;
+ private Integer maxIdle;
+ private Integer maxWaitMillis;
+ private Integer minIdle;
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java
new file mode 100644
index 0000000000..116da323dc
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java
@@ -0,0 +1,49 @@
+package com.binarywang.spring.starter.wxjava.open.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信开放平台单个应用配置.
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class WxOpenSingleProperties implements Serializable {
+ private static final long serialVersionUID = 1980986361098922525L;
+
+ /**
+ * 设置微信开放平台的appid.
+ */
+ private String appId;
+
+ /**
+ * 设置微信开放平台的app secret.
+ */
+ private String secret;
+
+ /**
+ * 设置微信开放平台的token.
+ */
+ private String token;
+
+ /**
+ * 设置微信开放平台的EncodingAESKey.
+ */
+ private String aesKey;
+
+ /**
+ * 自定义API主机地址,用于替换默认的 https://api.weixin.qq.com
+ * 例如:http://proxy.company.com:8080
+ */
+ private String apiHostUrl;
+
+ /**
+ * 自定义获取AccessToken地址,用于向自定义统一服务获取AccessToken
+ * 例如:http://proxy.company.com:8080/oauth/token
+ */
+ private String accessTokenUrl;
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java
new file mode 100644
index 0000000000..9228071a10
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java
@@ -0,0 +1,26 @@
+package com.binarywang.spring.starter.wxjava.open.service;
+
+
+import me.chanjar.weixin.open.api.WxOpenService;
+
+/**
+ * 微信开放平台 {@link WxOpenService} 所有实例存放类.
+ *
+ * @author binarywang
+ */
+public interface WxOpenMultiServices {
+ /**
+ * 通过租户 Id 获取 WxOpenService
+ *
+ * @param tenantId 租户 Id
+ * @return WxOpenService
+ */
+ WxOpenService getWxOpenService(String tenantId);
+
+ /**
+ * 根据租户 Id,从列表中移除一个 WxOpenService 实例
+ *
+ * @param tenantId 租户 Id
+ */
+ void removeWxOpenService(String tenantId);
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java
new file mode 100644
index 0000000000..76fb139e6c
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java
@@ -0,0 +1,35 @@
+package com.binarywang.spring.starter.wxjava.open.service;
+
+import me.chanjar.weixin.open.api.WxOpenService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 微信开放平台 {@link WxOpenMultiServices} 默认实现
+ *
+ * @author Binary Wang
+ */
+public class WxOpenMultiServicesImpl implements WxOpenMultiServices {
+ private final Map+ * 服务商可通过本接口获取服务商所拥有的应用模板列表 + * 文档地址:https://developer.work.weixin.qq.com/document/path/97111 + *+ * + * @return 应用模板列表 + * @throws WxErrorException 微信错误异常 + */ + WxCpTpTemplateList getTemplateList() throws WxErrorException; + + /** + * 获取代开发应用详情 + *
+ * 服务商可通过本接口获取某个授权企业中已经安装的代开发自建应用的详情 + * 文档地址:https://developer.work.weixin.qq.com/document/path/97111 + *+ * + * @param authCorpId 授权企业的corpid + * @param agentId 应用的agentid,为空时则返回该企业所有的代开发自建应用详情 + * @return 代开发应用详情 + * @throws WxErrorException 微信错误异常 + */ + WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java index 93855d1a48..92966c1d03 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java @@ -662,4 +662,18 @@ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml, void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service); + /** + * 获取代开发服务 + * + * @return 代开发服务 + */ + WxCpTpCustomizedService getWxCpTpCustomizedService(); + + /** + * 设置代开发服务 + * + * @param wxCpTpCustomizedService 代开发服务 + */ + void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService); + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java index f8f554b81a..25c1470eb2 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java @@ -60,6 +60,7 @@ public abstract class BaseWxCpTpServiceImpl
+ * 注意:
+ * 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。
+ * 详情可以看官方文档,亦可阅读此接口源码。
+ *
+ * @param sdkfileid 消息体内容中的sdkfileid信息
+ * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null
+ * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null
+ * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
+ * @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
+ * @throws WxErrorException the wx error exception
+ */
+ void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
+ @NonNull String targetFilePath) throws WxErrorException;
+
/**
* 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
@@ -85,10 +152,29 @@ void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, St
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
* @param action 传入一个lambda,each所有的数据分片
* @throws WxErrorException the wx error exception
+ * @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, Consumer)} 代替,
+ * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
*/
+ @Deprecated
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull Consumer
+ * 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx),
+ * 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。
+ *
+ * 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx),
+ * 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。
+ *
+ * 本示例展示了如何使用 wx-java-pay-multi-spring-boot-starter 来管理多个公众号的支付配置。
+ *
+ * 适用场景:系统需要支持多个公众号,根据用户所在的公众号动态选择支付配置
+ *
+ * 适用场景:服务商为多个子商户提供支付服务
+ *
+ * 适用场景:查询不同公众号的订单支付状态
+ *
+ * 适用场景:为不同公众号的订单申请退款
+ *
+ * 适用场景:需要在运行时更新配置(如证书更新后需要重新加载)
+ *
+ * 使用单个 WxMaService 实例管理多个租户配置,通过 switchover 切换租户。
+ * 相比 {@link WxMaMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。
+ *
+ * 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。
+ *
+ * 使用单个 WxMpService 实例管理多个租户配置,通过 switchover 切换租户。
+ * 相比 {@link WxMpMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。
+ *
+ * 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。
+ *
+ *
+ */
+ private MultiTenantMode multiTenantMode = MultiTenantMode.ISOLATED;
}
public enum StorageType {
@@ -151,4 +160,19 @@ public enum HttpClientType {
*/
JODD_HTTP
}
+
+ public enum MultiTenantMode {
+ /**
+ * 隔离模式:为每个租户创建独立的 WxMaService 实例.
+ * 优点:线程安全,不依赖 ThreadLocal
+ * 缺点:每个租户创建独立的 HTTP 客户端,资源占用较多
+ */
+ ISOLATED,
+ /**
+ * 共享模式:使用单个 WxMaService 实例管理所有租户配置.
+ * 优点:共享 HTTP 客户端,节省资源
+ * 缺点:依赖 ThreadLocal 切换配置,异步场景需注意
+ */
+ SHARED
+ }
}
diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesSharedImpl.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesSharedImpl.java
new file mode 100644
index 0000000000..40a01fb52e
--- /dev/null
+++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesSharedImpl.java
@@ -0,0 +1,53 @@
+package com.binarywang.spring.starter.wxjava.miniapp.service;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 微信小程序 {@link WxMaMultiServices} 共享式实现.
+ *
+ *
+ */
+ private MultiTenantMode multiTenantMode = MultiTenantMode.ISOLATED;
}
public enum StorageType {
@@ -155,4 +164,19 @@ public enum HttpClientType {
*/
JODD_HTTP
}
+
+ public enum MultiTenantMode {
+ /**
+ * 隔离模式:为每个租户创建独立的 WxMpService 实例.
+ * 优点:线程安全,不依赖 ThreadLocal
+ * 缺点:每个租户创建独立的 HTTP 客户端,资源占用较多
+ */
+ ISOLATED,
+ /**
+ * 共享模式:使用单个 WxMpService 实例管理所有租户配置.
+ * 优点:共享 HTTP 客户端,节省资源
+ * 缺点:依赖 ThreadLocal 切换配置,异步场景需注意
+ */
+ SHARED
+ }
}
diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesSharedImpl.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesSharedImpl.java
new file mode 100644
index 0000000000..ca9123c572
--- /dev/null
+++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesSharedImpl.java
@@ -0,0 +1,53 @@
+package com.binarywang.spring.starter.wxjava.mp.service;
+
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.mp.api.WxMpService;
+
+/**
+ * 微信公众号 {@link WxMpMultiServices} 共享式实现.
+ *
+ * 微工卡批量转账API请求参数
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_8.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
+ * 请求方式:POST
+ *
+ *
+ * @author binarywang
+ * created on 2025/01/19
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayrollTransferBatchesRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:应用ID
+ * 变量名:appid
+ * 是否必填:二选一
+ * 类型:string[1, 32]
+ * 描述:
+ * 服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID
+ * 示例值:wxa1111111
+ *
+ */
+ @SerializedName(value = "appid")
+ private String appid;
+
+ /**
+ *
+ * 字段名:子商户应用ID
+ * 变量名:sub_appid
+ * 是否必填:二选一
+ * 类型:string[1, 32]
+ * 描述:
+ * 特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID
+ * 示例值:wxa1111111
+ *
+ */
+ @SerializedName(value = "sub_appid")
+ private String subAppid;
+
+ /**
+ *
+ * 字段名:子商户号
+ * 变量名:sub_mchid
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 微信服务商下特约商户的商户号,由微信支付生成并下发
+ * 示例值:1111111
+ *
+ */
+ @SerializedName(value = "sub_mchid")
+ private String subMchid;
+
+ /**
+ *
+ * 字段名:商家批次单号
+ * 变量名:out_batch_no
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
+ * 示例值:plfk2020042013
+ *
+ */
+ @SerializedName(value = "out_batch_no")
+ private String outBatchNo;
+
+ /**
+ *
+ * 字段名:批次名称
+ * 变量名:batch_name
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 该笔批量转账的名称
+ * 示例值:2019年1月深圳分部报销单
+ *
+ */
+ @SerializedName(value = "batch_name")
+ private String batchName;
+
+ /**
+ *
+ * 字段名:批次备注
+ * 变量名:batch_remark
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 转账说明,UTF8编码,最多允许32个字符
+ * 示例值:2019年1月深圳分部报销单
+ *
+ */
+ @SerializedName(value = "batch_remark")
+ private String batchRemark;
+
+ /**
+ *
+ * 字段名:转账总金额
+ * 变量名:total_amount
+ * 是否必填:是
+ * 类型:int64
+ * 描述:
+ * 转账金额单位为"分"。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作
+ * 示例值:4000000
+ *
+ */
+ @SerializedName(value = "total_amount")
+ private Long totalAmount;
+
+ /**
+ *
+ * 字段名:转账总笔数
+ * 变量名:total_num
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 一个转账批次单最多发起一千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作
+ * 示例值:200
+ *
+ */
+ @SerializedName(value = "total_num")
+ private Integer totalNum;
+
+ /**
+ *
+ * 字段名:转账明细列表
+ * 变量名:transfer_detail_list
+ * 是否必填:是
+ * 类型:array
+ * 描述:
+ * 发起批量转账的明细列表,最多一千笔
+ *
+ */
+ @SerializedName(value = "transfer_detail_list")
+ private List
+ * 字段名:商家明细单号
+ * 变量名:out_detail_no
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 商户系统内部区分转账批次单下不同转账明细单的唯一标识
+ * 示例值:x23zy545Bd5436
+ *
+ */
+ @SerializedName(value = "out_detail_no")
+ private String outDetailNo;
+
+ /**
+ *
+ * 字段名:转账金额
+ * 变量名:transfer_amount
+ * 是否必填:是
+ * 类型:int64
+ * 描述:
+ * 转账金额单位为"分"
+ * 示例值:200000
+ *
+ */
+ @SerializedName(value = "transfer_amount")
+ private Long transferAmount;
+
+ /**
+ *
+ * 字段名:转账备注
+ * 变量名:transfer_remark
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
+ * 示例值:2020年4月报销
+ *
+ */
+ @SerializedName(value = "transfer_remark")
+ private String transferRemark;
+
+ /**
+ *
+ * 字段名:用户标识
+ * 变量名:openid
+ * 是否必填:是
+ * 类型:string[1, 64]
+ * 描述:
+ * 用户在商户对应appid下的唯一标识
+ * 示例值:o-MYE42l80oelYMDE34nYD456Xoy
+ *
+ */
+ @SerializedName(value = "openid")
+ private String openid;
+
+ /**
+ *
+ * 字段名:收款用户姓名
+ * 变量名:user_name
+ * 是否必填:否
+ * 类型:string[1, 1024]
+ * 描述:
+ * 收款用户真实姓名。该字段需进行加密处理,加密方法详见敏感信息加密说明
+ * 示例值:757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45
+ *
+ */
+ @SpecEncrypt
+ @SerializedName(value = "user_name")
+ private String userName;
+ }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesResult.java
new file mode 100644
index 0000000000..628c75d5f7
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesResult.java
@@ -0,0 +1,241 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 微工卡批量转账API返回结果
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_8.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
+ * 请求方式:POST
+ *
+ *
+ * @author binarywang
+ * created on 2025/01/19
+ */
+@Data
+@NoArgsConstructor
+public class PayrollTransferBatchesResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * 字段名:商家批次单号
+ * 变量名:out_batch_no
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 商户系统内部的商家批次单号
+ * 示例值:plfk2020042013
+ *
+ */
+ @SerializedName(value = "out_batch_no")
+ private String outBatchNo;
+
+ /**
+ *
+ * 字段名:微信批次单号
+ * 变量名:batch_id
+ * 是否必填:是
+ * 类型:string[1, 64]
+ * 描述:
+ * 微信批次单号,微信商家转账系统返回的唯一标识
+ * 示例值:1030000071100999991182020050700019480001
+ *
+ */
+ @SerializedName(value = "batch_id")
+ private String batchId;
+
+ /**
+ *
+ * 字段名:批次状态
+ * 变量名:batch_status
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * ACCEPTED:已受理,批次已受理成功,若发起批量转账的30分钟后,转账批次单仍处于该状态,可能原因是商户账户余额不足等。商户可查询账户资金流水,若该笔转账批次单的扣款已经发生,则表示批次已经进入转账中,请再次查单确认
+ * PROCESSING:转账中,已开始处理批次内的转账明细单
+ * FINISHED:已完成,批次内的所有转账明细单都已处理完成
+ * CLOSED:已关闭,可查询具体的批次关闭原因确认
+ * 示例值:ACCEPTED
+ *
+ */
+ @SerializedName(value = "batch_status")
+ private String batchStatus;
+
+ /**
+ *
+ * 字段名:批次类型
+ * 变量名:batch_type
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 批次类型
+ * API:API方式发起
+ * WEB:WEB方式发起
+ * 示例值:API
+ *
+ */
+ @SerializedName(value = "batch_type")
+ private String batchType;
+
+ /**
+ *
+ * 字段名:批次名称
+ * 变量名:batch_name
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 该笔批量转账的名称
+ * 示例值:2019年1月深圳分部报销单
+ *
+ */
+ @SerializedName(value = "batch_name")
+ private String batchName;
+
+ /**
+ *
+ * 字段名:批次备注
+ * 变量名:batch_remark
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 转账说明,UTF8编码,最多允许32个字符
+ * 示例值:2019年1月深圳分部报销单
+ *
+ */
+ @SerializedName(value = "batch_remark")
+ private String batchRemark;
+
+ /**
+ *
+ * 字段名:批次关闭原因
+ * 变量名:close_reason
+ * 是否必填:否
+ * 类型:string[1, 32]
+ * 描述:
+ * 如果批次单状态为"CLOSED"(已关闭),则有关闭原因
+ * 示例值:OVERDUE_CLOSE
+ *
+ */
+ @SerializedName(value = "close_reason")
+ private String closeReason;
+
+ /**
+ *
+ * 字段名:转账总金额
+ * 变量名:total_amount
+ * 是否必填:是
+ * 类型:int64
+ * 描述:
+ * 转账金额单位为"分"
+ * 示例值:4000000
+ *
+ */
+ @SerializedName(value = "total_amount")
+ private Long totalAmount;
+
+ /**
+ *
+ * 字段名:转账总笔数
+ * 变量名:total_num
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 一个转账批次单最多发起一千笔转账
+ * 示例值:200
+ *
+ */
+ @SerializedName(value = "total_num")
+ private Integer totalNum;
+
+ /**
+ *
+ * 字段名:批次创建时间
+ * 变量名:create_time
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 批次受理成功时返回,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE
+ * 示例值:2015-05-20T13:29:35.120+08:00
+ *
+ */
+ @SerializedName(value = "create_time")
+ private String createTime;
+
+ /**
+ *
+ * 字段名:批次更新时间
+ * 变量名:update_time
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 批次最近一次状态变更的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE
+ * 示例值:2015-05-20T13:29:35.120+08:00
+ *
+ */
+ @SerializedName(value = "update_time")
+ private String updateTime;
+
+ /**
+ *
+ * 字段名:转账成功金额
+ * 变量名:success_amount
+ * 是否必填:否
+ * 类型:int64
+ * 描述:
+ * 转账成功的金额,单位为"分"
+ * 示例值:3900000
+ *
+ */
+ @SerializedName(value = "success_amount")
+ private Long successAmount;
+
+ /**
+ *
+ * 字段名:转账成功笔数
+ * 变量名:success_num
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 转账成功的笔数
+ * 示例值:199
+ *
+ */
+ @SerializedName(value = "success_num")
+ private Integer successNum;
+
+ /**
+ *
+ * 字段名:转账失败金额
+ * 变量名:fail_amount
+ * 是否必填:否
+ * 类型:int64
+ * 描述:
+ * 转账失败的金额,单位为"分"
+ * 示例值:100000
+ *
+ */
+ @SerializedName(value = "fail_amount")
+ private Long failAmount;
+
+ /**
+ *
+ * 字段名:转账失败笔数
+ * 变量名:fail_num
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 转账失败的笔数
+ * 示例值:1
+ *
+ */
+ @SerializedName(value = "fail_num")
+ private Integer failNum;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayrollService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayrollService.java
index b3f788815c..581e3230b7 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayrollService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayrollService.java
@@ -101,4 +101,16 @@ public interface PayrollService {
*/
WxPayApplyBillV3Result merchantFundWithdrawBillType(String billType, String billDate, String tarType) throws WxPayException;
+ /**
+ * 微工卡批量转账API
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
+ * 请求方式:POST
+ *
+ * @param request 请求参数
+ * @return 返回数据
+ * @throws WxPayException the wx pay exception
+ */
+ PayrollTransferBatchesResult payrollCardTransferBatches(PayrollTransferBatchesRequest request) throws WxPayException;
+
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImpl.java
index 3d8c831271..85f7ee23dd 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImpl.java
@@ -193,4 +193,27 @@ public WxPayApplyBillV3Result merchantFundWithdrawBillType(String billType, Stri
return GSON.fromJson(response, WxPayApplyBillV3Result.class);
}
+ /**
+ * 微工卡批量转账API
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
+ * 请求方式:POST
+ *
+ * @param request 请求参数
+ * @return 返回数据
+ * @throws WxPayException the wx pay exception
+ */
+ @Override
+ public PayrollTransferBatchesResult payrollCardTransferBatches(PayrollTransferBatchesRequest request) throws WxPayException {
+ String url = String.format("%s/v3/payroll-card/transfer-batches", payService.getPayBaseUrl());
+ // 对敏感信息进行加密
+ if (request.getTransferDetailList() != null && !request.getTransferDetailList().isEmpty()) {
+ for (PayrollTransferBatchesRequest.TransferDetail detail : request.getTransferDetailList()) {
+ RsaCryptoUtil.encryptFields(detail, payService.getConfig().getVerifier().getValidCertificate());
+ }
+ }
+ String response = payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
+ return GSON.fromJson(response, PayrollTransferBatchesResult.class);
+ }
+
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java
index 03bbc8c593..ce43aa3d04 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java
@@ -14,6 +14,8 @@
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
+import java.util.Collections;
+
/**
* 微工卡(服务商)
*
@@ -125,4 +127,29 @@ public void merchantFundWithdrawBillType() throws WxPayException {
log.info(result.toString());
}
+ @Test
+ public void payrollCardTransferBatches() throws WxPayException {
+ PayrollTransferBatchesRequest request = PayrollTransferBatchesRequest.builder()
+ .appid("wxa1111111")
+ .subMchid("1111111")
+ .subAppid("wxa1111111")
+ .outBatchNo("plfk2020042013" + System.currentTimeMillis())
+ .batchName("2019年1月深圳分部报销单")
+ .batchRemark("2019年1月深圳分部报销单")
+ .totalAmount(200000L)
+ .totalNum(1)
+ .transferDetailList(Collections.singletonList(
+ PayrollTransferBatchesRequest.TransferDetail.builder()
+ .outDetailNo("x23zy545Bd5436" + System.currentTimeMillis())
+ .transferAmount(200000L)
+ .transferRemark("2020年4月报销")
+ .openid("o-MYE42l80oelYMDE34nYD456Xoy")
+ .userName("张三")
+ .build()
+ ))
+ .build();
+ PayrollTransferBatchesResult result = wxPayService.getPayrollService().payrollCardTransferBatches(request);
+ log.info(result.toString());
+ }
+
}
From ae2aa43190b53ac69c3618b117b815949517d61f Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 20 Jan 2026 13:36:30 +0800
Subject: [PATCH 109/111] =?UTF-8?q?:art:=20#3861=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E5=BE=AE=E5=B7=A5=E5=8D=A1?=
=?UTF-8?q?=E6=A0=B8=E8=BA=AB=E9=A2=84=E4=B8=8B=E5=8D=95=E6=8E=A5=E5=8F=A3?=
=?UTF-8?q?=E7=9A=84=E8=AF=B7=E6=B1=82=E7=B1=BB=E6=B7=BB=E5=8A=A0=E7=BC=BA?=
=?UTF-8?q?=E5=A4=B1=E7=9A=84=E6=A0=B8=E8=BA=AB=E7=B1=BB=E5=9E=8B=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../payroll/PreOrderWithAuthRequest.java | 18 ++++++++++++++++++
.../service/impl/PayrollServiceImplTest.java | 1 +
2 files changed, 19 insertions(+)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthRequest.java
index 1556fbc343..0e20fc8fa6 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthRequest.java
@@ -166,4 +166,22 @@ public class PreOrderWithAuthRequest implements Serializable {
*/
@SerializedName(value = "employment_type")
private String employmentType;
+
+ /**
+ *
+ * 字段名:核身类型
+ * 变量名:authenticate_type
+ * 是否必填:否
+ * 类型:string[1,32]
+ * 描述:
+ * 核身类型,用于标识本次核身的业务类型;枚举值:
+ * NORMAL_AUTHENTICATE:普通核身
+ * LOGIN_AUTHENTICATE:登录核身
+ * INSURANCE_AUTHENTICATE:保险核身
+ * CONTRACT_AUTHENTICATE:合同核身
+ * 示例值:NORMAL_AUTHENTICATE
+ *
+ */
+ @SerializedName(value = "authenticate_type")
+ private String authenticateType;
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java
index ce43aa3d04..20bb33d7fd 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java
@@ -114,6 +114,7 @@ public void payrollCardPreOrderWithAuth() throws WxPayException {
request.setIdCardNumber("7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5");
request.setProjectName("某项目");
request.setUserName("LP7bT4hQXUsOZCEvK2YrSiqFsnP0oRMfeoLN0vBg");
+ request.setAuthenticateType("NORMAL_AUTHENTICATE");
PreOrderWithAuthResult preOrderWithAuthResult = wxPayService.getPayrollService().payrollCardPreOrderWithAuth(request);
log.info(preOrderWithAuthResult.toString());
From 28a0d6e8eea226b15d4f1ea7354cca8244a03beb Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 20 Jan 2026 13:37:39 +0800
Subject: [PATCH 110/111] =?UTF-8?q?:art:=20#3860=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E8=BD=AC=E8=B4=A6=E5=88=B0?=
=?UTF-8?q?=E9=93=B6=E8=A1=8C=E5=8D=A1=E7=9A=84=E6=9F=A5=E8=AF=A2=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E5=A2=9E=E5=8A=A0=20bank=5Fname=20=E5=92=8C=20bank=5F?=
=?UTF-8?q?card=5Fnumber=5Ftail=20=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../transfer/BatchDetailsResult.java | 26 +++
.../transfer/BatchDetailsResultTest.java | 182 ++++++++++++++++++
2 files changed, 208 insertions(+)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResultTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
index 4ca7958ed5..854fd6ba5a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
@@ -235,4 +235,30 @@ public String toString() {
*/
@SerializedName(value = "update_time")
private String updateTime;
+ /**
+ *
+ * 字段名:开户银行全称(含支行)
+ * 变量名:bank_name
+ * 是否必填:否
+ * 类型:string[1, 128]
+ * 描述:
+ * 转账到银行卡时返回,开户银行全称(含支行)
+ * 示例值:中国农业银行股份有限公司深圳分行
+ *
+ */
+ @SerializedName(value = "bank_name")
+ private String bankName;
+ /**
+ *
+ * 字段名:银行卡号后四位
+ * 变量名:bank_card_number_tail
+ * 是否必填:否
+ * 类型:string[4, 4]
+ * 描述:
+ * 转账到银行卡时返回,用于标识银行卡的后四位
+ * 示例值:1234
+ *
+ */
+ @SerializedName(value = "bank_card_number_tail")
+ private String bankCardNumberTail;
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResultTest.java
new file mode 100644
index 0000000000..c2347300a6
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResultTest.java
@@ -0,0 +1,182 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * 测试 BatchDetailsResult 的字段序列化和反序列化功能
+ *
+ * @author Binary Wang
+ */
+public class BatchDetailsResultTest {
+
+ private static final Gson GSON = new GsonBuilder().create();
+
+ @Test
+ public void testBankFieldsDeserialization() {
+ // 模拟微信API返回的JSON(包含银行相关字段)
+ String mockJson = "{\n" +
+ " \"sp_mchid\": \"1900001109\",\n" +
+ " \"out_batch_no\": \"plfk2020042013\",\n" +
+ " \"batch_id\": \"1030000071100999991182020050700019480001\",\n" +
+ " \"appid\": \"wxf636efh567hg4356\",\n" +
+ " \"out_detail_no\": \"x23zy545Bd5436\",\n" +
+ " \"detail_id\": \"1040000071100999991182020050700019500100\",\n" +
+ " \"detail_status\": \"SUCCESS\",\n" +
+ " \"transfer_amount\": 200000,\n" +
+ " \"transfer_remark\": \"2020年4月报销\",\n" +
+ " \"openid\": \"o-MYE42l80oelYMDE34nYD456Xoy\",\n" +
+ " \"username\": \"757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45\",\n" +
+ " \"initiate_time\": \"2015-05-20T13:29:35.120+08:00\",\n" +
+ " \"update_time\": \"2015-05-20T13:29:35.120+08:00\",\n" +
+ " \"bank_name\": \"中国农业银行股份有限公司深圳分行\",\n" +
+ " \"bank_card_number_tail\": \"1234\"\n" +
+ "}";
+
+ // 反序列化JSON
+ BatchDetailsResult result = GSON.fromJson(mockJson, BatchDetailsResult.class);
+
+ // 验证基本字段正常解析
+ assertEquals(result.getSpMchid(), "1900001109");
+ assertEquals(result.getOutBatchNo(), "plfk2020042013");
+ assertEquals(result.getBatchId(), "1030000071100999991182020050700019480001");
+ assertEquals(result.getAppId(), "wxf636efh567hg4356");
+ assertEquals(result.getOutDetailNo(), "x23zy545Bd5436");
+ assertEquals(result.getDetailId(), "1040000071100999991182020050700019500100");
+ assertEquals(result.getDetailStatus(), "SUCCESS");
+ assertEquals(result.getTransferAmount(), Integer.valueOf(200000));
+ assertEquals(result.getTransferRemark(), "2020年4月报销");
+ assertEquals(result.getOpenid(), "o-MYE42l80oelYMDE34nYD456Xoy");
+ assertEquals(result.getUserName(), "757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45");
+ assertEquals(result.getInitiateTime(), "2015-05-20T13:29:35.120+08:00");
+ assertEquals(result.getUpdateTime(), "2015-05-20T13:29:35.120+08:00");
+
+ // 验证新增的银行相关字段
+ assertEquals(result.getBankName(), "中国农业银行股份有限公司深圳分行");
+ assertEquals(result.getBankCardNumberTail(), "1234");
+ }
+
+ @Test
+ public void testBankFieldsWithNull() {
+ // 测试不包含银行字段的情况(转账到零钱)
+ String mockJsonWithoutBank = "{\n" +
+ " \"sp_mchid\": \"1900001109\",\n" +
+ " \"out_batch_no\": \"plfk2020042013\",\n" +
+ " \"batch_id\": \"1030000071100999991182020050700019480001\",\n" +
+ " \"out_detail_no\": \"x23zy545Bd5436\",\n" +
+ " \"detail_id\": \"1040000071100999991182020050700019500100\",\n" +
+ " \"detail_status\": \"SUCCESS\",\n" +
+ " \"transfer_amount\": 200000,\n" +
+ " \"transfer_remark\": \"2020年4月报销\",\n" +
+ " \"openid\": \"o-MYE42l80oelYMDE34nYD456Xoy\",\n" +
+ " \"username\": \"757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45\",\n" +
+ " \"initiate_time\": \"2015-05-20T13:29:35.120+08:00\",\n" +
+ " \"update_time\": \"2015-05-20T13:29:35.120+08:00\"\n" +
+ "}";
+
+ BatchDetailsResult result = GSON.fromJson(mockJsonWithoutBank, BatchDetailsResult.class);
+
+ // 验证其他字段正常
+ assertEquals(result.getSpMchid(), "1900001109");
+ assertEquals(result.getDetailStatus(), "SUCCESS");
+
+ // 验证银行字段为null(转账到零钱场景下不返回这些字段)
+ assertNull(result.getBankName());
+ assertNull(result.getBankCardNumberTail());
+ }
+
+ @Test
+ public void testBankFieldsSerialization() {
+ // 测试序列化
+ BatchDetailsResult result = new BatchDetailsResult();
+ result.setSpMchid("1900001109");
+ result.setOutBatchNo("plfk2020042013");
+ result.setBatchId("1030000071100999991182020050700019480001");
+ result.setDetailStatus("SUCCESS");
+ result.setBankName("中国工商银行股份有限公司北京分行");
+ result.setBankCardNumberTail("5678");
+
+ String json = GSON.toJson(result);
+
+ // 验证JSON包含银行字段
+ assertTrue(json.contains("\"bank_name\":\"中国工商银行股份有限公司北京分行\""));
+ assertTrue(json.contains("\"bank_card_number_tail\":\"5678\""));
+ }
+
+ @Test
+ public void testToString() {
+ // 测试toString方法
+ BatchDetailsResult result = new BatchDetailsResult();
+ result.setSpMchid("1900001109");
+ result.setBankName("中国建设银行股份有限公司上海分行");
+ result.setBankCardNumberTail("9012");
+
+ String resultString = result.toString();
+
+ // 验证toString包含所有字段
+ assertNotNull(resultString);
+ assertTrue(resultString.contains("1900001109"));
+ assertTrue(resultString.contains("中国建设银行股份有限公司上海分行"));
+ assertTrue(resultString.contains("9012"));
+ }
+
+ @Test
+ public void testBankNameWithSpecialCharacters() {
+ // 测试银行名称包含特殊字符的情况
+ String mockJson = "{\n" +
+ " \"sp_mchid\": \"1900001109\",\n" +
+ " \"out_batch_no\": \"plfk2020042013\",\n" +
+ " \"batch_id\": \"1030000071100999991182020050700019480001\",\n" +
+ " \"out_detail_no\": \"x23zy545Bd5436\",\n" +
+ " \"detail_id\": \"1040000071100999991182020050700019500100\",\n" +
+ " \"detail_status\": \"SUCCESS\",\n" +
+ " \"transfer_amount\": 200000,\n" +
+ " \"transfer_remark\": \"2020年4月报销\",\n" +
+ " \"openid\": \"o-MYE42l80oelYMDE34nYD456Xoy\",\n" +
+ " \"username\": \"757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45\",\n" +
+ " \"initiate_time\": \"2015-05-20T13:29:35.120+08:00\",\n" +
+ " \"update_time\": \"2015-05-20T13:29:35.120+08:00\",\n" +
+ " \"bank_name\": \"中国农业银行股份有限公司北京市朝阳区(支行)\",\n" +
+ " \"bank_card_number_tail\": \"0000\"\n" +
+ "}";
+
+ BatchDetailsResult result = GSON.fromJson(mockJson, BatchDetailsResult.class);
+
+ // 验证特殊字符正确解析
+ assertEquals(result.getBankName(), "中国农业银行股份有限公司北京市朝阳区(支行)");
+ assertEquals(result.getBankCardNumberTail(), "0000");
+ }
+
+ @Test
+ public void testFailedTransferWithoutBankFields() {
+ // 测试转账失败的情况
+ String mockJson = "{\n" +
+ " \"sp_mchid\": \"1900001109\",\n" +
+ " \"out_batch_no\": \"plfk2020042013\",\n" +
+ " \"batch_id\": \"1030000071100999991182020050700019480001\",\n" +
+ " \"out_detail_no\": \"x23zy545Bd5436\",\n" +
+ " \"detail_id\": \"1040000071100999991182020050700019500100\",\n" +
+ " \"detail_status\": \"FAIL\",\n" +
+ " \"transfer_amount\": 200000,\n" +
+ " \"transfer_remark\": \"2020年4月报销\",\n" +
+ " \"fail_reason\": \"ACCOUNT_FROZEN\",\n" +
+ " \"openid\": \"o-MYE42l80oelYMDE34nYD456Xoy\",\n" +
+ " \"username\": \"757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45\",\n" +
+ " \"initiate_time\": \"2015-05-20T13:29:35.120+08:00\",\n" +
+ " \"update_time\": \"2015-05-20T13:29:35.120+08:00\"\n" +
+ "}";
+
+ BatchDetailsResult result = GSON.fromJson(mockJson, BatchDetailsResult.class);
+
+ // 验证失败状态
+ assertEquals(result.getDetailStatus(), "FAIL");
+ assertEquals(result.getFailReason(), "ACCOUNT_FROZEN");
+
+ // 失败的情况下银行字段应为null
+ assertNull(result.getBankName());
+ assertNull(result.getBankCardNumberTail());
+ }
+}
From a23429c144e38e50966a84857555bd95e7e5e17b Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 20 Jan 2026 13:39:41 +0800
Subject: [PATCH 111/111] =?UTF-8?q?:art:=20#3859=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E5=AE=A1=E6=89=B9=E8=AF=A6?=
=?UTF-8?q?=E6=83=85=E6=8E=A5=E5=8F=A3=E5=A2=9E=E5=8A=A0=E6=80=BB=E8=B4=B9?=
=?UTF-8?q?=E7=94=A8=E9=87=91=E9=A2=9D=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/bean/oa/WxCpApprovalDetailResult.java | 6 ++
.../cp/api/impl/WxCpOaServiceImplTest.java | 74 +++++++++++++++++++
2 files changed, 80 insertions(+)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetailResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetailResult.java
index 7d55ff878f..fe77fcaeac 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetailResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetailResult.java
@@ -91,6 +91,12 @@ public static class WxCpApprovalDetail implements Serializable {
@SerializedName("comments")
private List