17 Commits

Author SHA1 Message Date
Binary Wang
3e48dc7f83 🔖 发布 4.7.2.B 测试版本 2025-01-24 22:18:57 +08:00
superffan
f3c1422354 :art:【微信支付】spring-boot-starter模块增加公钥ID和证书文件路径等配置项 2025-01-24 21:27:34 +08:00
giveup
d65e2f27a9 🎨 WxMpSubscribeMessage实现序列化接口 2025-01-24 21:26:02 +08:00
Neror
c3b16228c0 🎨 【微信支付】完善微信商家转账功能API !151 2025-01-21 11:49:38 +00:00
Neror
24ebc91dca 🎨 【微信支付】增加多配置切换功能 2025-01-21 19:36:25 +08:00
GadflyFang
1e003ee571 🎨 修复 94aaff4 引入的错误读取证书的问题 2025-01-21 18:44:33 +08:00
giveup
8e5ee97378 🎨 #3458 【企业微信】家校沟通获取部门列表接口优化,如果部门id为空则获取全量组织架构 2025-01-21 18:41:11 +08:00
julb
704fba4d85 🎨 【微信支付】兼容公钥模式下请求头序列号 !148 2025-01-20 10:52:35 +08:00
Molzx
b0daf8428e 🆕 #3479 【小程序】新增订单管理的配置和查询订单配置路径的接口 2025-01-17 12:02:48 +08:00
mrsiu
ab1c150feb 🎨 #3478 【企业微信】OA审批接口增加对说明文字控件(control参数为Tips)的支持 2025-01-16 13:50:24 +08:00
潘安
b4b73ad40a 🎨 #3467 【微信支付】商户进件开户意愿接口相关字段调整 2025-01-16 13:47:55 +08:00
allovine
be0dd8b692 🆕 #3474 【微信支付】增加2025.1.15正式上线的商户转账新版本的相关接口 2025-01-16 13:46:02 +08:00
Binary Wang
83bd92d260 🔖 发布 4.7.1.B 测试版本 2025-01-13 08:51:16 +08:00
huangj
6d9fadf86d :art:【企业微信】会话内容存档 - 解密后的会话内容实体类里去掉多余的字段 2025-01-09 16:37:40 +08:00
lzq52066
9730e9a387 🎨 #3054 【小程序】优化代码,避免高版本jdk环境下出现序列化报错问题 2025-01-06 15:22:21 +08:00
Binary Wang
6decd2839b 📝 更新版本信息 2025-01-04 10:39:13 +08:00
junbaor
a29e00f00f 🎨 #3461【小程序】spring boot starter 和 solon plugin 增加获取稳定版接口调用凭据的参数 2025-01-03 14:23:20 +08:00
79 changed files with 1273 additions and 128 deletions

View File

@@ -59,7 +59,7 @@
### 重要信息
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。
2. 项目合作洽谈请联系微信`binary0000`在微信里自行搜索并添加好友请注明来意如有关于SDK问题需讨论请参考下文入群讨论不要加此微信
3. **2023-12-28 发布 [【4.6.0正式版】](https://mp.weixin.qq.com/s/9Hhc_8w-v7ogS_TEAsqfAg)**
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))的常见问题部分,可以少走很多弯路,节省不少时间。
@@ -90,7 +90,7 @@
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>(不同模块参考下文)</artifactId>
<version>4.6.0</version>
<version>4.7.0</version>
</dependency>
```

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
<packaging>pom</packaging>
<name>WxJava - Weixin/Wechat Java SDK</name>
<description>微信开发Java SDK</description>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<packaging>pom</packaging>
<artifactId>wx-java-solon-plugins</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -15,6 +15,7 @@ public abstract class AbstractWxMaConfigStorageConfiguration {
config.setToken(StringUtils.trimToNull(properties.getToken()));
config.setAesKey(StringUtils.trimToNull(properties.getAesKey()));
config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat()));
config.useStableAccessToken(properties.isUseStableAccessToken());
WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());

View File

@@ -45,6 +45,11 @@ public class WxMaProperties {
*/
private String msgDataFormat;
/**
* 是否使用稳定版 Access Token
*/
private boolean useStableAccessToken = false;
/**
* 存储策略
*/

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<packaging>pom</packaging>
<artifactId>wx-java-spring-boot-starters</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -10,12 +10,13 @@
```
2. 添加配置(application.properties)
```properties
# 公众号配置(必填)
# 小程序配置(必填)
wx.miniapp.appid = appId
wx.miniapp.secret = @secret
wx.miniapp.token = @token
wx.miniapp.aesKey = @aesKey
wx.miniapp.msgDataFormat = @msgDataFormat # 消息格式XML或者JSON.
wx.miniapp.use-stable-access-token=@useStableAccessToken
# 存储配置redis(可选)
# 注意: 指定redis.host值后不会使用容器注入的redis连接(JedisPool)
wx.miniapp.config-storage.type = Jedis # 配置类型: Memory(默认), Jedis, RedisTemplate

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -15,6 +15,7 @@ public abstract class AbstractWxMaConfigStorageConfiguration {
config.setToken(StringUtils.trimToNull(properties.getToken()));
config.setAesKey(StringUtils.trimToNull(properties.getAesKey()));
config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat()));
config.useStableAccessToken(properties.isUseStableAccessToken());
WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());

View File

@@ -44,6 +44,11 @@ public class WxMaProperties {
*/
private String msgDataFormat;
/**
* 是否使用稳定版 Access Token
*/
private boolean useStableAccessToken = false;
/**
* 存储策略
*/

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -57,6 +57,8 @@ public class WxPayAutoConfiguration {
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()));
wxPayService.setConfig(payConfig);
return wxPayService;

View File

@@ -73,7 +73,17 @@ public class WxPayProperties {
* apiv3 商户apiclient_cert.pem
*/
private String privateCertPath;
/**
* 公钥ID
*/
private String publicKeyId;
/**
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String publicKeyPath;
/**
* 微信支付是否使用仿真测试环境.
* 默认不使用

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-graal</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-channel</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-common</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-cp</artifactId>

View File

@@ -334,7 +334,7 @@ public interface WxCpSchoolUserService {
* 请求方式GETHTTPS
* 请求地址https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID
*
* @param id the id
* @param id 部门id。获取指定部门及其下的子部门。 如果不填,默认获取全量组织架构
* @return wx cp department list
* @throws WxErrorException the wx error exception
*/

View File

@@ -16,6 +16,7 @@ import me.chanjar.weixin.cp.bean.school.user.*;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Objects;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.*;
@@ -246,7 +247,7 @@ public class WxCpSchoolUserServiceImpl implements WxCpSchoolUserService {
@Override
public WxCpDepartmentList listDepartment(Integer id) throws WxErrorException {
String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST) + id;
String apiUrl = Objects.isNull(id) ? this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST) : String.format("%s?id=%s", this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), id);
String responseContent = this.cpService.get(apiUrl, null);
return WxCpDepartmentList.fromJson(responseContent);
}

View File

@@ -25,9 +25,6 @@ public class WxCpChatModel implements Serializable {
@SerializedName("action")
private String action;
@SerializedName("send")
private String send;
@SerializedName("from")
private String from;

View File

@@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateTips;
import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateTitle;
import me.chanjar.weixin.cp.bean.oa.templatedata.control.*;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
@@ -94,6 +95,9 @@ public class WxCpOaApprovalTemplateResult implements Serializable {
@SerializedName("vacation_list")
private TemplateVacation vacationList;
@SerializedName("tips")
private TemplateTips tips;
}
@Data

View File

@@ -37,4 +37,6 @@ public class TemplateConfig implements Serializable {
@SerializedName("vacation_list")
private TemplateVacation vacationList;
private TemplateTips tips;
}

View File

@@ -0,0 +1,18 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.util.List;
/**
* @author mrsiu@msn.com
* @version 1.0
* @date 2025/1/16 09:40
*/
@Data
public class TemplateTips {
@SerializedName("tips_content")
private List<TemplateTipsContent> tipsContent;
}

View File

@@ -0,0 +1,15 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import lombok.Data;
/**
* @author mrsiu@msn.com
* @version 1.0
* @date 2025/1/16 09:42
*/
@Data
public class TemplateTipsContent {
private TemplateTipsText text;
private String lang;
}

View File

@@ -0,0 +1,14 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import lombok.Data;
/**
* @author mrsiu@msn.com
* @version 1.0
* @date 2025/1/16 09:45
*/
@Data
public class TemplateTipsSubText {
private Integer type;
private TemplateTipsSubTextContent content;
}

View File

@@ -0,0 +1,16 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
/**
* @author mrsiu@msn.com
* @version 1.0
* @date 2025/1/16 09:46
*/
@Data
public class TemplateTipsSubTextContent {
@SerializedName("plain_text")
private TemplateTipsSubTextContentPlainText plainText;
private TemplateTipsSubTextContentLink link;
}

View File

@@ -0,0 +1,14 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import lombok.Data;
/**
* @author mrsiu@msn.com
* @version 1.0
* @date 2025/1/16 09:49
*/
@Data
public class TemplateTipsSubTextContentLink {
private String title;
private String url;
}

View File

@@ -0,0 +1,13 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import lombok.Data;
/**
* @author mrsiu@msn.com
* @date 2025/1/16 09:47
* @version 1.0
*/
@Data
public class TemplateTipsSubTextContentPlainText {
private String content;
}

View File

@@ -0,0 +1,17 @@
package me.chanjar.weixin.cp.bean.oa.templatedata;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.util.List;
/**
* @author mrsiu@msn.com
* @date 2025/1/16 09:43
* @version 1.0
*/
@Data
public class TemplateTipsText {
@SerializedName("sub_text")
private List<TemplateTipsSubText> subText;
}

View File

@@ -691,7 +691,7 @@ public interface WxCpApiPathConsts {
/**
* The constant DEPARTMENT_LIST.
*/
String DEPARTMENT_LIST = "/cgi-bin/school/department/list?id=";
String DEPARTMENT_LIST = "/cgi-bin/school/department/list";
/**
* The constant GET_PAYMENT_RESULT.

View File

@@ -0,0 +1,139 @@
package me.chanjar.weixin.cp.api.impl;
import com.google.gson.Gson;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.school.user.WxCpDepartmentList;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.mockito.Mockito;
import org.testng.annotations.Test;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.DEPARTMENT_LIST;
import static org.testng.Assert.assertEquals;
public class WxCpSchoolUserServiceImplTest {
String allDeptListJson = "{\n" +
"\t\"errcode\": 0,\n" +
"\t\"errmsg\": \"ok\",\n" +
"\t\"departments\": [\n" +
"\t\t{\n" +
"\t\t\t\"name\": \"一年级\",\n" +
"\t\t\t\"parentid\": 1,\n" +
"\t\t\t\"id\": 2,\n" +
"\t\t\t\"type\":2,\n" +
"\t\t\t\"register_year\":2018,\n" +
"\t\t\t\"standard_grade\":1,\n" +
"\t\t\t\"order\":1,\n" +
"\t\t\t\"department_admins\": [\n" +
"\t\t\t\t{\n" +
"\t\t\t\t\t\"userid\": \"zhangsan\",\n" +
"\t\t\t\t\t\"type\": 1\n" +
"\t\t\t\t},\n" +
"\t\t\t\t{\n" +
"\t\t\t\t\t\"userid\": \"lisi\",\n" +
"\t\t\t\t\t\"type\": 2\n" +
"\t\t\t\t}\n" +
"\t\t\t],\n" +
" \"is_graduated\": 0\n" +
"\t\t},\n" +
"\t\t{\n" +
"\t\t\t\"name\": \"一年级一班\",\n" +
"\t\t\t\"parentid\": 1,\n" +
"\t\t\t\"id\": 3,\n" +
"\t\t\t\"type\": 1,\n" +
"\t\t\t\"department_admins\": [\n" +
"\t\t\t\t{\n" +
"\t\t\t\t\t\"userid\": \"zhangsan\",\n" +
"\t\t\t\t\t\"type\": 3,\n" +
"\t\t\t\t\t\"subject\":\"语文\"\n" +
"\t\t\t\t},\n" +
"\t\t\t\t{\n" +
"\t\t\t\t\t\"userid\": \"lisi\",\n" +
"\t\t\t\t\t\"type\": 4,\n" +
"\t\t\t\t\t\"subject\":\"数学\"\n" +
"\t\t\t\t}\n" +
"\t\t\t],\n" +
"\t\t\t\"open_group_chat\": 1,\n" +
" \"group_chat_id\": \"group_chat_id\"\n" +
"\t\t}\n" +
"\t]\n" +
"}\n";
String deptId3Json = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"departments\": [\n" +
" {\n" +
" \"name\": \"一年级一班\",\n" +
" \"parentid\": 1,\n" +
" \"id\": 3,\n" +
" \"type\": 1,\n" +
" \"department_admins\": [\n" +
" {\n" +
" \"userid\": \"zhangsan\",\n" +
" \"type\": 3,\n" +
" \"subject\":\"语文\"\n" +
" },\n" +
" {\n" +
" \"userid\": \"lisi\",\n" +
" \"type\": 4,\n" +
" \"subject\":\"数学\"\n" +
" }\n" +
" ],\n" +
" \"open_group_chat\": 1,\n" +
" \"group_chat_id\": \"group_chat_id\"\n" +
" }\n" +
" ]\n" +
"}";
String deptId2Json = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"departments\": []\n" +
"}\n";
@Test
public void testListDepartmentWhenIdIsNull() throws WxErrorException {
WxCpService mockCpService = Mockito.mock(WxCpService.class);
WxCpSchoolUserServiceImpl wxCpSchoolUserService = new WxCpSchoolUserServiceImpl(mockCpService);
Mockito.when(mockCpService.getWxCpConfigStorage()).thenReturn(new WxCpDefaultConfigImpl());
Mockito.when(mockCpService.get(mockCpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), null)).thenReturn(allDeptListJson);
WxCpDepartmentList wxCpDepartmentList = wxCpSchoolUserService.listDepartment(null);
//WxCpDepartmentList没有重写Equals和Hashcode,不能直接比较
Gson gson = new Gson();
assertEquals(gson.toJson(wxCpDepartmentList), gson.toJson(gson.fromJson(allDeptListJson, WxCpDepartmentList.class)), "should be equal");
}
@Test
public void testListDepartmentWhenIdIs2() throws WxErrorException {
WxCpService mockCpService = Mockito.mock(WxCpService.class);
WxCpSchoolUserServiceImpl wxCpSchoolUserService = new WxCpSchoolUserServiceImpl(mockCpService);
Mockito.when(mockCpService.getWxCpConfigStorage()).thenReturn(new WxCpDefaultConfigImpl());
Gson gson = new Gson();
int deptId = 2;
Mockito.when(mockCpService.get(String.format("%s?id=%s", mockCpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), deptId), null)).thenReturn(deptId2Json);
//WxCpDepartmentList没有重写Equals和Hashcode,不能直接比较
assertEquals(gson.toJson(wxCpSchoolUserService.listDepartment(deptId)), gson.toJson(gson.fromJson(deptId2Json, WxCpDepartmentList.class)), "should be equal");
}
@Test
public void testListDepartmentWhenIdIs3() throws WxErrorException {
WxCpService mockCpService = Mockito.mock(WxCpService.class);
WxCpSchoolUserServiceImpl wxCpSchoolUserService = new WxCpSchoolUserServiceImpl(mockCpService);
Mockito.when(mockCpService.getWxCpConfigStorage()).thenReturn(new WxCpDefaultConfigImpl());
Gson gson = new Gson();
int deptId = 3;
Mockito.when(mockCpService.get(String.format("%s?id=%s", mockCpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), deptId), null)).thenReturn(deptId3Json);
//WxCpDepartmentList没有重写Equals和Hashcode,不能直接比较
assertEquals(gson.toJson(wxCpSchoolUserService.listDepartment(deptId)), gson.toJson(gson.fromJson(deptId3Json, WxCpDepartmentList.class)), "should be equal");
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-miniapp</artifactId>

View File

@@ -0,0 +1,40 @@
package cn.binarywang.wx.miniapp.api;
import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementGetOrderDetailPath;
import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementResult;
import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
import me.chanjar.weixin.common.error.WxErrorException;
/**
* @author xzh
* @Description
* @createTime 2025/01/16 15:20
*/
public interface WxMaOrderManagementService {
/**
* 查询订单详情路径
* 注意事项
* 如果没有配置过订单详情路径会返回成功其中path为''。
*
* @return WxMaOrderManagementGetOrderDetailPath
* @throws WxErrorException e
*/
WxMaOrderManagementGetOrderDetailPath getOrderDetailPath()
throws WxErrorException;
/**
* 配置订单详情路径
* 注意事项
* 调用接口前需要先完成订单中心授权协议签署。
* 请确保配置的path可正常跳转到小程序并且path必须包含字符串“${商品订单号}”。
*
* @param path 订单详情路径
* @return WxMaOrderManagementResult
* @throws WxErrorException e
*/
WxMaOrderManagementResult updateOrderDetailPath(String path)
throws WxErrorException;
}

View File

@@ -550,6 +550,12 @@ public interface WxMaService extends WxService {
* @return getWxMaOrderShippingService
*/
WxMaOrderShippingService getWxMaOrderShippingService();
/**
* 小程序订单管理服务
*
* @return WxMaOrderManagementService
*/
WxMaOrderManagementService getWxMaOrderManagementService();
/**
* 小程序openApi管理

View File

@@ -153,6 +153,9 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
private final WxMaOrderShippingService wxMaOrderShippingService =
new WxMaOrderShippingServiceImpl(this);
private final WxMaOrderManagementService wxMaOrderManagementService =
new WxMaOrderManagementServiceImpl(this);
private final WxMaOpenApiService wxMaOpenApiService = new WxMaOpenApiServiceImpl(this);
private final WxMaVodService wxMaVodService = new WxMaVodServiceImpl(this);
private final WxMaXPayService wxMaXPayService = new WxMaXPayServiceImpl(this);
@@ -499,7 +502,10 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
@Override
public void setWxMaConfig(WxMaConfig maConfig) {
final String appid = maConfig.getAppid();
this.setMultiConfigs(ImmutableMap.of(appid, maConfig), appid);
Map<String, WxMaConfig> map = new HashMap<>();
map.put(appid, maConfig);
Map<String, WxMaConfig> configMap = Collections.unmodifiableMap(map);
this.setMultiConfigs(configMap, appid);
}
@Override
@@ -814,6 +820,16 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
return this.wxMaOrderShippingService;
}
/**
* 小程序订单管理服务
*
* @return WxMaOrderManagementService
*/
@Override
public WxMaOrderManagementService getWxMaOrderManagementService() {
return this.wxMaOrderManagementService;
}
@Override
public WxMaOpenApiService getWxMaOpenApiService() {
return this.wxMaOpenApiService;

View File

@@ -0,0 +1,72 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaOrderManagementService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementGetOrderDetailPath;
import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementResult;
import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.GsonHelper;
import me.chanjar.weixin.common.util.json.GsonParser;
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.OrderManagement.*;
/**
* @author xzh
* @Description
* @createTime 2025/01/16 15:31
*/
@Slf4j
@RequiredArgsConstructor
public class WxMaOrderManagementServiceImpl implements WxMaOrderManagementService {
private final WxMaService wxMaService;
/**
* 查询订单详情路径
* 注意事项
* 如果没有配置过订单详情路径会返回成功其中path为''。
*
* @return WxMaOrderManagementGetOrderDetailPath
* @throws WxErrorException e
*/
@Override
public WxMaOrderManagementGetOrderDetailPath getOrderDetailPath() throws WxErrorException {
return request(GET_ORDER_DETAIL_PATH, new Object(), WxMaOrderManagementGetOrderDetailPath.class);
}
/**
* 配置订单详情路径
* 注意事项
* 调用接口前需要先完成订单中心授权协议签署。
* 请确保配置的path可正常跳转到小程序并且path必须包含字符串“${商品订单号}”。
*
* @param path 订单详情路径
* @return WxMaOrderManagementResult
* @throws WxErrorException e
*/
@Override
public WxMaOrderManagementResult updateOrderDetailPath(String path) throws WxErrorException {
JsonObject jsonObject = GsonHelper.buildJsonObject("path", path);
return request(UPDATE_ORDER_DETAIL_PATH, jsonObject, WxMaOrderManagementResult.class);
}
private <T> T request(String url, Object request, Class<T> resultT) throws WxErrorException {
String responseContent = this.wxMaService.post(url, request);
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(responseContent, resultT);
}
}

View File

@@ -0,0 +1,24 @@
package cn.binarywang.wx.miniapp.bean.order;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* @author xzh
* @Description
* @createTime 2025/01/16 15:27
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class WxMaOrderManagementGetOrderDetailPath extends WxMaOrderManagementResult {
private static final long serialVersionUID = -5288666524298706169L;
/**
* 订单详情路径
*/
@SerializedName("path")
private String path;
}

View File

@@ -0,0 +1,27 @@
package cn.binarywang.wx.miniapp.bean.order;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/**
* @author xzh
* @Description
* @createTime 2025/01/16 15:27
*/
@Data
public class WxMaOrderManagementResult implements Serializable {
private static final long serialVersionUID = 1468925151935770503L;
/**
* 错误码
*/
@SerializedName("errcode")
private Integer errCode;
/**
* 错误原因
*/
@SerializedName("errmsg")
private String errMsg;
}

View File

@@ -751,7 +751,7 @@ public class WxMaApiUrlConstants {
* </pre>
*/
String UPLOAD_COMBINED_SHIPPING_INFO =
"https://api.weixin.qq.com/wxa/sec/order/upload_combined_shipping_info";
"https://api.weixin.qq.com/wxa/sec/order/upload_combined_shipping_info";
/**
* 查询订单发货状态.
@@ -779,7 +779,7 @@ public class WxMaApiUrlConstants {
* </pre>
*/
String NOTIFY_CONFIRM_RECEIVE =
"https://api.weixin.qq.com/wxa/sec/order/notify_confirm_receive";
"https://api.weixin.qq.com/wxa/sec/order/notify_confirm_receive";
/**
* 消息跳转路径设置接口.
@@ -809,6 +809,35 @@ public class WxMaApiUrlConstants {
}
/**
* 小程序订单管理
*
* <pre>
* 文档地址: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order_center/order_center.html
* </pre>
*/
public interface OrderManagement {
/**
* 配置订单详情路径.
*
* <pre>
* 文档地址https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order_center/order_center.html
* </pre>
*/
String UPDATE_ORDER_DETAIL_PATH = "https://api.weixin.qq.com/wxa/sec/order/update_order_detail_path";
/**
* 查询订单详情路径.
*
* <pre>
* 文档地址https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order_center/order_center.html
* </pre>
*/
String GET_ORDER_DETAIL_PATH = "https://api.weixin.qq.com/wxa/sec/order/get_order_detail_path";
}
public interface Vod {
String LIST_MEDIA_URL = "https://api.weixin.qq.com/wxa/sec/vod/listmedia";
String GET_MEDIA_URL = "https://api.weixin.qq.com/wxa/sec/vod/getmedia";

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-mp</artifactId>

View File

@@ -17,7 +17,7 @@ import java.util.Map;
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class WxMpSubscribeMessage {
public class WxMpSubscribeMessage implements Serializable {
/**
* 接收者openid.

View File

@@ -2,6 +2,9 @@ package me.chanjar.weixin.mp.bean.subscribe;
import org.testng.annotations.*;
import java.io.Serializable;
import java.util.Arrays;
import static org.testng.AssertJUnit.*;
/**
@@ -43,4 +46,9 @@ public class WxMpSubscribeMessageTest {
assertEquals(message.toJson(), actual);
}
@Test
void testWxMpSubscribeMessageIsSerializable() {
assertTrue(Arrays.stream(WxMpSubscribeMessage.class.getInterfaces()).anyMatch(anInterface -> anInterface.isInstance(Serializable.class)));
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-open</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -119,8 +119,8 @@ public class ApplySubjectConfirmCreateRequest implements Serializable {
* IDENTIFICATION_TYPE_TAIWAN_RESIDENT台湾居民证
* 示例值IDENTIFICATION_TYPE_IDCARD
*/
@SerializedName("id_doc_type")
private IdTypeEnum idDocType;
@SerializedName("identification_type")
private IdTypeEnum identificationType;
/**
* 法定代表人说明函
@@ -414,13 +414,13 @@ public class ApplySubjectConfirmCreateRequest implements Serializable {
* 若未传入将默认填写false。
* 示例值true
*/
@SerializedName("finance_institution")
@SerializedName("is_finance_institution")
private Boolean financeInstitution;
/**
* 营业执照
*/
@SerializedName("business_license_info")
@SerializedName("business_licence_info")
private ApplySubConfirmBusinessLicenseInfo businessLicenseInfo;
/**
* 登记证书
@@ -736,8 +736,8 @@ public class ApplySubjectConfirmCreateRequest implements Serializable {
* 参看微信支付提供的特殊行业id对照表
* 示例值100
*/
@SerializedName("finance_type")
private Integer financeType;
@SerializedName("category_id")
private Integer categoryId;
/**
* 行业经营许可证资质照片
@@ -791,29 +791,10 @@ public class ApplySubjectConfirmCreateRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 法人开户承诺函
* 待确认商户号列表
*/
@SerializedName("legal_person_commitment")
private String legalPersonCommitment;
/**
* 法人开户意愿视频
*/
@SerializedName("legal_person_video")
private String legalPersonVideo;
/**
* 补充材料
*/
@SerializedName("business_addition_pics")
private List<String> businessAdditionPics;
/**
* 补充说明
*/
@SerializedName("business_addition_msg")
private String businessAdditionMsg;
@SerializedName("confirm_mchid_list")
private List<String> confirmMchidList;
}
}

View File

@@ -0,0 +1,48 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <pre>
* 商家转账到零钱撤销转账接口
* 文档地址https://pay.weixin.qq.com/doc/v3/merchant/4012716458
* </pre>
*
* @author Nor
* @date 2025/1/17
*/
@Data
@NoArgsConstructor
public class TransferBillsCancelResult implements Serializable {
private static final long serialVersionUID = -4935840810530008418L;
/**
* 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识
*/
@SerializedName("transfer_bill_no")
private String transferBillNo;
/**
* 【单据状态】 商家转账订单状态
* 可选取值
* CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
* CANCELLED: 转账撤销完成
*/
private String state;
/**
* 【最后一次单据状态变更时间】 按照使用rfc3339所定义的格式格式为yyyy-MM-DDThh:mm:ss+TIMEZONE
*/
@SerializedName("update_time")
private String updateTime;
}

View File

@@ -0,0 +1,103 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <pre>
* 商家转账到零钱查询转账单接口
* 文档地址https://pay.weixin.qq.com/doc/v3/merchant/4012716457 https://pay.weixin.qq.com/doc/v3/merchant/4012716437
* </pre>
*
* @author Nor
* @date 2025/1/17
*/
@Data
@NoArgsConstructor
public class TransferBillsGetResult implements Serializable {
private static final long serialVersionUID = -6376955113492371763L;
/**
* 【商户号】 微信支付分配的商户号
*/
@SerializedName("mch_id")
private String mchId;
/**
* 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 【商家转账订单号】 商家转账订单的主键,唯一定义此资源的标识
*/
@SerializedName("transfer_bill_no")
private String transferBillNo;
/**
* 【商户AppID】 申请商户号的AppID或商户号绑定的AppID企业号corpid即为此AppID
*/
private String appid;
/**
* 【单据状态】
* 可选取值
* ACCEPTED: 转账已受理
* PROCESSING: 转账处理中,转账结果尚未明确,如一直处于此状态,建议检查账户余额是否足够
* WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
* TRANSFERING: 转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款
* SUCCESS: 转账成功
* FAIL: 转账失败
* CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
* CANCELLED: 转账撤销完成
*
* @see WxPayConstants.TransformBillState
*/
private String state;
/**
* 【转账金额】 转账金额单位为“分”。
*/
@SerializedName("transfer_amount")
private String transferAmount;
/**
* 【转账备注】 单条转账备注微信用户会收到该备注UTF8编码最多允许32个字符
*/
@SerializedName("transfer_remark")
private String transferRemark;
/**
* 【失败原因】 订单已失败或者已退资金时,返回失败原因
*/
@SerializedName("fail_reason")
private String failReason;
/**
* 【收款用户OpenID】 商户AppID下某用户的OpenID
*/
private String openid;
/**
* 【收款用户姓名】 收款方真实姓名。支持标准RSA算法和国密算法公钥由微信侧提供转账金额 >= 2,000元时该笔明细必须填写若商户传入收款用户姓名微信支付会校验用户OpenID与姓名是否一致并提供电子回单
*/
@SerializedName("user_name")
private String userName;
/**
* 【单据创建时间】 单据受理成功时返回按照使用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;
}

View File

@@ -0,0 +1,79 @@
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;
/**
* <pre>
* 商家转账到零钱接口将转账结果通知用户
* 文档地址https://pay.weixin.qq.com/doc/v3/merchant/4012716434
* </pre>
*/
@Data
public class TransferBillsNotifyResult implements Serializable, WxPayBaseNotifyV3Result<TransferBillsNotifyResult.DecryptNotifyResult> {
/**
* 源数据
*/
private OriginNotifyResponse rawData;
/**
* 解密后的数据
*/
private TransferBillsNotifyResult.DecryptNotifyResult result;
@Data
@NoArgsConstructor
public static class DecryptNotifyResult implements Serializable {
/**
* 商户号
*/
@SerializedName(value = "mch_id")
private String mchId;
/**
* 商家批次单号
*/
@SerializedName(value = "out_bill_no")
private String outBillNo;
/**
* 微信批次单号
*/
@SerializedName(value = "transfer_bill_no")
private String transferBillNo;
/**
* 批次状态
*/
@SerializedName(value = "state")
private String state;
/**
* 转账金额
*/
@SerializedName(value = "transfer_amount")
private Integer transferAmount;
/**
* 批次状态
*/
@SerializedName(value = "openid")
private String openid;
/**
* 单据创建时间
*/
@SerializedName(value = "create_time")
private String createTime;
/**
* 最后一次状态变更时间
*/
@SerializedName(value = "update_time")
private String updateTime;
/**
* 错误原因
*/
@SerializedName(value = "fail_reason")
private String failReason;
}
}

View File

@@ -0,0 +1,108 @@
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;
/**
* 发起商家转账API参数
*
* @author allovine
* created on 2025/1/15
**/
@Data
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
public class TransferBillsRequest implements Serializable {
private static final long serialVersionUID = -2175582517588397437L;
/**
* 直连商户的appid
*/
@SerializedName("appid")
private String appid;
/**
* 商户系统内部的商家单号
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 转账场景ID
* 商户平台-产品中心-商家转账 申请
* 佣金报酬 ID:1005
*/
@SerializedName("transfer_scene_id")
private String transferSceneId;
/**
* 用户在直连商户应用下的用户标示
*/
@SerializedName("openid")
private String openid;
/**
* 收款用户姓名
*/
@SpecEncrypt
@SerializedName("user_name")
private String userName;
/**
* 转账金额
*/
@SerializedName("transfer_amount")
private Integer transferAmount;
/**
* 转账备注
*/
@SerializedName("transfer_remark")
private String transferRemark;
/**
* 异步接收微信支付结果通知的回调地址通知url必须为公网可访问的url必须为https不能携带参数
*/
@SerializedName("notify_url")
private String notifyUrl;
/**
* 用户收款感知
*/
@SerializedName("user_recv_perception")
private String userRecvPerception;
/**
* 转账场景报备信息
*/
@SerializedName("transfer_scene_report_infos")
private List<TransferSceneReportInfo> transferSceneReportInfos;
@Data
@Builder(builderMethodName = "newBuilder")
@AllArgsConstructor
@NoArgsConstructor
public static class TransferSceneReportInfo {
/**
* 信息类型
*/
@SerializedName("info_type")
private String infoType;
/**
* 信息内容
*/
@SerializedName("info_content")
private String infoContent;
}
}

View File

@@ -0,0 +1,58 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 商家转账结果
*
* @author allovine
* created on 2025/1/15
**/
@Data
@NoArgsConstructor
public class TransferBillsResult implements Serializable {
private static final long serialVersionUID = -2175582517588397437L;
/**
* 商户单号
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 微信转账单号
*/
@SerializedName("transfer_bill_no")
private String transferBillNo;
/**
* 单据创建时间
*/
@SerializedName("create_time")
private String createTime;
/**
* 单据状态
*
* @see WxPayConstants.TransformBillState
*/
@SerializedName("state")
private String state;
/**
* 失败原因
*/
@SerializedName("fail_reason")
private String failReason;
/**
* 跳转领取页面的package信息
*/
@SerializedName("package_info")
private String packageInfo;
}

View File

@@ -14,6 +14,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import lombok.Data;
@@ -371,7 +372,7 @@ public class WxPayConfig {
if (configContent != null) {
inputStream = new ByteArrayInputStream(configContent);
} else if (StringUtils.isNotEmpty(configString)) {
configContent = configString.getBytes(StandardCharsets.UTF_8);
configContent = Base64.getDecoder().decode(configString);
inputStream = new ByteArrayInputStream(configContent);
} else {
if (StringUtils.isBlank(configPath)) {

View File

@@ -6,6 +6,7 @@ import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
import com.google.common.collect.Lists;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.time.FastDateFormat;
import java.text.Format;
@@ -353,4 +354,86 @@ public class WxPayConstants {
public static final String PERSONAL_SUB_OPENID = "PERSONAL_SUB_OPENID";
}
/**
* 微信商户转账订单状态
*/
@UtilityClass
public static class TransformBillState {
/**
* 转账已受理
*/
public static final String ACCEPTED = "ACCEPTED";
/**
* 转账处理中,转账结果尚未明确,如一直处于此状态,建议检查账户余额是否足够
*/
public static final String PROCESSING = "PROCESSING";
/**
* 待收款用户确认,可拉起微信收款确认页面进行收款确认
*/
public static final String WAIT_USER_CONFIRM = "WAIT_USER_CONFIRM";
/**
* 转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款
*/
public static final String TRANSFERING = "TRANSFERING";
/**
* 转账成功
*/
public static final String SUCCESS = "SUCCESS";
/**
* 转账失败
*/
public static final String FAIL = "FAIL";
/**
* 商户撤销请求受理成功,该笔转账正在撤销中
*/
public static final String CANCELING = "CANCELING";
/**
* 转账撤销完成
*/
public static final String CANCELLED = "CANCELLED";
}
/**
* 【转账场景ID】 该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。
*/
@UtilityClass
public static class TransformSceneId {
/**
* 现金营销
*/
public static final String CASH_MARKETING = "1001";
}
/**
* 用户收款感知
*
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988#3.3-%E5%8F%91%E8%B5%B7%E8%BD%AC%E8%B4%A6">官方文档</a>
*/
@UtilityClass
public static class UserRecvPerception {
/**
* 转账场景 现金营销
* 场景介绍 向参与营销活动的用户发放现金奖励
*/
public static class CASH_MARKETING {
/**
* 默认展示
*/
public static final String ACTIVITY = "活动奖励";
/**
* 需在发起转账时,“用户收款感知”字段主动传入“现金奖励”才可展示
*/
public static final String CASH = "现金奖励";
}
}
}

View File

@@ -111,4 +111,82 @@ public interface TransferService {
*/
TransferBatchDetailResult transferBatchesOutBatchNoDetail(String outBatchNo, String outDetailNo) throws WxPayException;
/**
* <pre>
*
* 2025.1.15 开始新接口 发起商家转账API
*
* 请求方式POSTHTTPS
* 请求地址:<a href="https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills">请求地址</a>
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716434">发起商家转账API</a>
* </pre>
*
* @param request 转账请求参数
* @return TransferBillsResult 转账结果
* @throws WxPayException .
*/
TransferBillsResult transferBills(TransferBillsRequest request) throws WxPayException;
/**
* <pre>
*
* 2025.1.15 开始新接口 撤销转账API
*
* 请求方式POSTHTTPS
* 请求地址:<a href="https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel">请求地址</a>
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716458">商户撤销转账API</a>
* </pre>
*
* @param outBillNo 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
* @return TransformBillsGetResult 转账单
* @throws WxPayException .
*/
TransferBillsCancelResult transformBillsCancel(String outBillNo) throws WxPayException;
/**
* <pre>
*
* 2025.1.15 开始新接口 发起商家转账API
*
* 请求方式GETHTTPS
* 请求地址:<a href="https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}">请求地址</a>
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716437">商户单号查询转账单API</a>
* </pre>
*
* @param outBillNo 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
* @return TransformBillsGetResult 转账单
* @throws WxPayException .
*/
TransferBillsGetResult getBillsByOutBillNo(String outBillNo) throws WxPayException;
/**
* <pre>
*
* 2025.1.15 开始新接口 微信单号查询转账单API
*
* 请求方式GETHTTPS
* 请求地址:<a href="https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}">请求地址</a>
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716437">商户单号查询转账单API</a>
* </pre>
*
* @param transferBillNo 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识
* @return TransformBillsGetResult 转账单
* @throws WxPayException .
*/
TransferBillsGetResult getBillsByTransferBillNo(String transferBillNo) throws WxPayException;
/**
* 2025.1.15 开始新接口 解析商家转账结果
* 详见<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012712115"></a>
*
* @param notifyData 通知数据
* @param header 通知头部数据,不传则表示不校验头
* @return the wx transfer notify result
* @throws WxPayException the wx pay exception
*/
TransferBillsNotifyResult parseTransferBillsNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
}

View File

@@ -6,6 +6,7 @@ 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.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
@@ -38,16 +39,18 @@ public interface WxPayService {
* Map里 加入新的 {@link WxPayConfig},适用于动态添加新的微信商户配置.
*
* @param mchId 商户id
* @param appId 微信应用id
* @param wxPayConfig 新的微信配置
*/
void addConfig(String mchId, WxPayConfig wxPayConfig);
void addConfig(String mchId, String appId, WxPayConfig wxPayConfig);
/**
* 从 Map中 移除 {@link String mchId} 所对应的 {@link WxPayConfig},适用于动态移除微信商户配置.
* 从 Map中 移除 {@link String mchId} 和 {@link String appId} 所对应的 {@link WxPayConfig},适用于动态移除微信商户配置.
*
* @param mchId 对应商户的标识
* @param appId 微信应用id
*/
void removeConfig(String mchId);
void removeConfig(String mchId, String appId);
/**
* 注入多个 {@link WxPayConfig} 的实现. 并为每个 {@link WxPayConfig} 赋予不同的 {@link String mchId} 值
@@ -69,17 +72,19 @@ public interface WxPayService {
* 进行相应的商户切换.
*
* @param mchId 商户标识
* @param appId 微信应用id
* @return 切换是否成功 boolean
*/
boolean switchover(String mchId);
boolean switchover(String mchId, String appId);
/**
* 进行相应的商户切换.
*
* @param mchId 商户标识
* @param appId 微信应用id
* @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常
*/
WxPayService switchoverTo(String mchId);
WxPayService switchoverTo(String mchId, String appId);
/**
* 发送post请求得到响应字节数组.
@@ -616,10 +621,10 @@ public interface WxPayService {
/**
* 调用统一下单接口,并组装生成支付所需参数对象.
*
* @param <T> 请使用{@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
* @param <T> 请使用{@link WxPayUnifiedOrderV3Result}里的内部类或字段
* @param tradeType the trade type
* @param request 统一下单请求参数
* @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
* @return 返回 {@link WxPayUnifiedOrderV3Result}里的内部类或字段
* @throws WxPayException the wx pay exception
*/
<T> T createOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException;
@@ -627,10 +632,10 @@ public interface WxPayService {
/**
* 服务商模式调用统一下单接口,并组装生成支付所需参数对象.
*
* @param <T> 请使用{@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
* @param <T> 请使用{@link WxPayUnifiedOrderV3Result}里的内部类或字段
* @param tradeType the trade type
* @param request 统一下单请求参数
* @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
* @return 返回 {@link WxPayUnifiedOrderV3Result}里的内部类或字段
* @throws WxPayException the wx pay exception
*/
<T> T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException;
@@ -991,6 +996,17 @@ public interface WxPayService {
*/
WxPayTransferBatchesNotifyV3Result parseTransferBatchesNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
/**
* 解析商家转账批次回调通知
* https://pay.weixin.qq.com/doc/v3/merchant/4012712115
*
* @param notifyData
* @param header
* @return
* @throws WxPayException
*/
TransferBillsNotifyResult parseTransferBillsNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
/**
* 解析服务商模式退款结果通知
* 详见https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_11.shtml
@@ -1603,7 +1619,8 @@ public interface WxPayService {
/**
* 获取服务商直股份签约计划服务类
* @return the partner pay score sign plan service
*
* @return the partner pay score sign plan service
*/
PartnerPayScoreSignPlanService getPartnerPayScoreSignPlanService();
}

View File

@@ -11,9 +11,9 @@ 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.transfer.TransferBillsNotifyResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.config.WxPayConfigHolder;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.constant.WxPayConstants.SignType;
import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType;
import com.github.binarywang.wxpay.exception.WxPayException;
@@ -45,6 +45,7 @@ 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;
@@ -121,7 +122,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
private final PartnerPayScoreService partnerPayScoreService = new PartnerPayScoreServiceImpl(this);
@Getter
private final PartnerPayScoreSignPlanService partnerPayScoreSignPlanService=new PartnerPayScoreSignPlanServiceImpl(this);
private final PartnerPayScoreSignPlanService partnerPayScoreSignPlanService = new PartnerPayScoreSignPlanServiceImpl(this);
@Getter
private final MerchantTransferService merchantTransferService = new MerchantTransferServiceImpl(this);
@@ -129,7 +130,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BrandMerchantTransferService brandMerchantTransferService = new BrandMerchantTransferServiceImpl(this);
protected Map<String, WxPayConfig> configMap = new HashMap<>();
protected Map<String, WxPayConfig> configMap = new ConcurrentHashMap<>();
@Override
public WxPayConfig getConfig() {
@@ -142,38 +143,37 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Override
public void setConfig(WxPayConfig config) {
final String defaultMchId = config.getMchId();
this.setMultiConfig(ImmutableMap.of(defaultMchId, config), defaultMchId);
final String defaultKey = this.getConfigKey(config.getMchId(), config.getAppId());
this.setMultiConfig(ImmutableMap.of(defaultKey, config), defaultKey);
}
@Override
public void addConfig(String mchId, WxPayConfig wxPayConfig) {
public void addConfig(String mchId, String appId, WxPayConfig wxPayConfig) {
synchronized (this) {
if (this.configMap == null) {
this.setConfig(wxPayConfig);
} else {
WxPayConfigHolder.set(mchId);
this.configMap.put(mchId, wxPayConfig);
String configKey = this.getConfigKey(mchId, appId);
WxPayConfigHolder.set(configKey);
this.configMap.put(configKey, wxPayConfig);
}
}
}
@Override
public void removeConfig(String mchId) {
public void removeConfig(String mchId, String appId) {
synchronized (this) {
if (this.configMap.size() == 1) {
this.configMap.remove(mchId);
log.warn("已删除最后一个商户号配置:{}须立即使用setConfig或setMultiConfig添加配置", mchId);
String configKey = this.getConfigKey(mchId, appId);
this.configMap.remove(configKey);
if (this.configMap.isEmpty()) {
log.warn("已删除最后一个商户号配置mchId[{}],appid[{}]须立即使用setConfig或setMultiConfig添加配置", mchId, appId);
return;
}
if (WxPayConfigHolder.get().equals(mchId)) {
this.configMap.remove(mchId);
final String defaultMpId = this.configMap.keySet().iterator().next();
WxPayConfigHolder.set(defaultMpId);
log.warn("已删除默认商户号配置,商户号【{}】被设为默认配置", defaultMpId);
return;
if (WxPayConfigHolder.get().equals(configKey)) {
final String nextConfigKey = this.configMap.keySet().iterator().next();
WxPayConfigHolder.set(nextConfigKey);
log.warn("已删除默认商户号配置,商户号【{}】被设为默认配置", nextConfigKey);
}
this.configMap.remove(mchId);
}
}
@@ -183,28 +183,34 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
}
@Override
public void setMultiConfig(Map<String, WxPayConfig> wxPayConfigs, String defaultMchId) {
public void setMultiConfig(Map<String, WxPayConfig> wxPayConfigs, String defaultConfigKey) {
this.configMap = Maps.newHashMap(wxPayConfigs);
WxPayConfigHolder.set(defaultMchId);
WxPayConfigHolder.set(defaultConfigKey);
}
@Override
public boolean switchover(String mchId) {
if (this.configMap.containsKey(mchId)) {
WxPayConfigHolder.set(mchId);
public boolean switchover(String mchId, String appId) {
String configKey = this.getConfigKey(mchId, appId);
if (this.configMap.containsKey(configKey)) {
WxPayConfigHolder.set(configKey);
return true;
}
log.error("无法找到对应【{}】的商户号配置信息,请核实!", mchId);
log.error("无法找到对应mchId=【{}】,appId=【{}】的商户号配置信息,请核实!", mchId, appId);
return false;
}
@Override
public WxPayService switchoverTo(String mchId) {
if (this.configMap.containsKey(mchId)) {
WxPayConfigHolder.set(mchId);
public WxPayService switchoverTo(String mchId, String appId) {
String configKey = this.getConfigKey(mchId, appId);
if (this.configMap.containsKey(configKey)) {
WxPayConfigHolder.set(configKey);
return this;
}
throw new WxRuntimeException(String.format("无法找到对应【%s】的商户号配置信息请核实", mchId));
throw new WxRuntimeException(String.format("无法找到对应mchId=【%s】,appId=【%s】的商户号配置信息请核实", mchId, appId));
}
private String getConfigKey(String mchId, String appId) {
return mchId + "_" + appId;
}
@Override
@@ -301,7 +307,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Override
public WxPayRefundQueryV3Result refundPartnerQueryV3(WxPayRefundQueryV3Request request) throws WxPayException {
String url = String.format("%s/v3/refund/domestic/refunds/%s?sub_mchid=%s", this.getPayBaseUrl(), request.getOutRefundNo(),request.getSubMchid());
String url = String.format("%s/v3/refund/domestic/refunds/%s?sub_mchid=%s", this.getPayBaseUrl(), request.getOutRefundNo(), request.getSubMchid());
String response = this.getV3(url);
return GSON.fromJson(response, WxPayRefundQueryV3Result.class);
}
@@ -323,7 +329,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
} else if (configMap.get(result.getMchId()).getSignType() != null) {
// 如果配置中signType有值则使用它进行验签
signType = configMap.get(result.getMchId()).getSignType();
this.switchover(result.getMchId());
this.switchover(result.getMchId(), result.getAppid());
}
}
@@ -346,7 +352,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
*/
private boolean verifyNotifySign(SignatureHeader header, String data) throws WxSignTestException {
String wxPaySign = header.getSignature();
if(wxPaySign.startsWith("WECHATPAY/SIGNTEST/")){
if (wxPaySign.startsWith("WECHATPAY/SIGNTEST/")) {
throw new WxSignTestException("微信支付签名探测流量");
}
String beforeSign = String.format("%s\n%s\n%s\n",
@@ -420,7 +426,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
WxPayRefundNotifyResult result;
if (XmlConfig.fastMode) {
result = BaseWxPayResult.fromXML(xmlData, WxPayRefundNotifyResult.class);
this.switchover(result.getMchId());
this.switchover(result.getMchId(), result.getAppid());
result.decryptReqInfo(this.getConfig().getMchKey());
} else {
result = WxPayRefundNotifyResult.fromXML(xmlData, this.getConfig().getMchKey());
@@ -442,6 +448,11 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayTransferBatchesNotifyV3Result.class, WxPayTransferBatchesNotifyV3Result.DecryptNotifyResult.class);
}
@Override
public TransferBillsNotifyResult parseTransferBillsNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
return this.baseParseOrderNotifyV3Result(notifyData, header, TransferBillsNotifyResult.class, TransferBillsNotifyResult.DecryptNotifyResult.class);
}
@Override
public WxPayPartnerRefundNotifyV3Result parsePartnerRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayPartnerRefundNotifyV3Result.class, WxPayPartnerRefundNotifyV3Result.DecryptNotifyResult.class);
@@ -452,7 +463,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
try {
log.debug("扫码支付回调通知请求参数:{}", xmlData);
WxScanPayNotifyResult result = BaseWxPayResult.fromXML(xmlData, WxScanPayNotifyResult.class);
this.switchover(result.getMchId());
this.switchover(result.getMchId(), result.getAppid());
log.debug("扫码支付回调通知解析后的对象:{}", result);
result.checkResult(this, this.getConfig().getSignType(), false);
return result;
@@ -1300,7 +1311,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Override
public WxPayFaceAuthInfoResult getWxPayFaceAuthInfo(WxPayFaceAuthInfoRequest request) throws WxPayException {
if (StringUtils.isEmpty(request.getSignType())) {
request.setSignType(WxPayConstants.SignType.MD5);
request.setSignType(SignType.MD5);
}
request.checkAndSign(this.getConfig());

View File

@@ -48,8 +48,7 @@ public class TransferServiceImpl implements TransferService {
if (request.getNeedQueryDetail()) {
url = String.format("%s/v3/transfer/batches/batch-id/%s?need_query_detail=true&offset=%s&limit=%s&detail_status=%s",
this.payService.getPayBaseUrl(), request.getBatchId(), request.getOffset(), request.getLimit(), request.getDetailStatus());
}
else {
} else {
url = String.format("%s/v3/transfer/batches/batch-id/%s?need_query_detail=false",
this.payService.getPayBaseUrl(), request.getBatchId());
}
@@ -70,8 +69,7 @@ public class TransferServiceImpl implements TransferService {
if (request.getNeedQueryDetail()) {
url = String.format("%s/v3/transfer/batches/out-batch-no/%s?need_query_detail=true&offset=%s&limit=%s&detail_status=%s",
this.payService.getPayBaseUrl(), request.getOutBatchNo(), request.getOffset(), request.getLimit(), request.getDetailStatus());
}
else {
} else {
url = String.format("%s/v3/transfer/batches/out-batch-no/%s?need_query_detail=false",
this.payService.getPayBaseUrl(), request.getOutBatchNo());
}
@@ -85,4 +83,45 @@ public class TransferServiceImpl implements TransferService {
String result = this.payService.getV3(url);
return GSON.fromJson(result, TransferBatchDetailResult.class);
}
@Override
public TransferBillsResult transferBills(TransferBillsRequest request) throws WxPayException {
String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills", this.payService.getPayBaseUrl());
if (request.getUserName() != null && request.getUserName().length() > 0) {
X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate();
RsaCryptoUtil.encryptFields(request, validCertificate);
}
String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(result, TransferBillsResult.class);
}
@Override
public TransferBillsCancelResult transformBillsCancel(String outBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s/cancel",
this.payService.getPayBaseUrl(), outBillNo);
String result = this.payService.postV3(url, "");
return GSON.fromJson(result, TransferBillsCancelResult.class);
}
@Override
public TransferBillsGetResult getBillsByOutBillNo(String outBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s",
this.payService.getPayBaseUrl(), outBillNo);
String result = this.payService.getV3(url);
return GSON.fromJson(result, TransferBillsGetResult.class);
}
@Override
public TransferBillsGetResult getBillsByTransferBillNo(String transferBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/%s",
this.payService.getPayBaseUrl(), transferBillNo);
String result = this.payService.getV3(url);
return GSON.fromJson(result, TransferBillsGetResult.class);
}
@Override
public TransferBillsNotifyResult parseTransferBillsNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
return this.payService.baseParseOrderNotifyV3Result(notifyData, header, TransferBillsNotifyResult.class, TransferBillsNotifyResult.DecryptNotifyResult.class);
}
}

View File

@@ -1,6 +1,7 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.WxPayApiData;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.v3.WxPayV3DownloadHttpGet;
import com.google.gson.JsonElement;
@@ -171,7 +172,7 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
HttpPost httpPost = this.createHttpPost(url, requestStr);
httpPost.addHeader(ACCEPT, APPLICATION_JSON);
httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON);
String serialNumber = getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
String serialNumber = getWechatpaySerial(getConfig());
httpPost.addHeader("Wechatpay-Serial", serialNumber);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
//v3已经改为通过状态码判断200 204 成功
@@ -251,7 +252,7 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader(ACCEPT, APPLICATION_JSON);
httpGet.addHeader(CONTENT_TYPE, APPLICATION_JSON);
String serialNumber = getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
String serialNumber = getWechatpaySerial(getConfig());
httpGet.addHeader("Wechatpay-Serial", serialNumber);
return this.requestV3(url, httpGet);
}
@@ -380,4 +381,16 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
return wxPayException;
}
/**
* 兼容微信支付公钥模式
* @param wxPayConfig
* @return
*/
private String getWechatpaySerial(WxPayConfig wxPayConfig) {
String serialNumber = wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
if (StringUtils.isNotBlank(wxPayConfig.getPublicKeyId())) {
serialNumber = wxPayConfig.getPublicKeyId();
}
return serialNumber;
}
}

View File

@@ -2,6 +2,7 @@ package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest;
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.testbase.ApiTestModule;
@@ -73,4 +74,32 @@ public class TransferServiceImplTest {
public void testTransferBatchesOutBatchNoDetail() throws WxPayException {
log.info("商家明细单号查询明细单:{}", this.payService.getTransferService().transferBatchesOutBatchNoDetail("1655447999520", "1655447989156"));
}
@Test
public void testTransferBills() throws WxPayException {
TransferBillsRequest transferBillsRequest = TransferBillsRequest.newBuilder()
.appid("wxf636efh5xxxxx")
.outBillNo("1655447989156")
.transferSceneId("1005")
.transferAmount(100)
.transferRemark("测试转账")
.openid("oX_7Jzr9gSZz4X_Xc9-_7HGf8XzI")
.userName("测试用户").build();
log.info("发起商家转账:{}", this.payService.getTransferService().transferBills(transferBillsRequest));
}
@Test
public void testTransformBillsCancel() throws WxPayException {
log.info("撤销商家转账:{}", this.payService.getTransferService().transformBillsCancel("123456"));
}
@Test
public void testGetBillsByOutBillNo() throws WxPayException {
log.info("商户单号查询转账单:{}", this.payService.getTransferService().getBillsByOutBillNo("123456"));
}
@Test
public void testGetBillsByTransferBillNo() throws WxPayException {
log.info("微信单号查询转账单:{}", this.payService.getTransferService().getBillsByTransferBillNo("123456"));
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.0</version>
<version>4.7.2.B</version>
</parent>
<artifactId>weixin-java-qidian</artifactId>