Compare commits

...

5 Commits

Author SHA1 Message Date
wuKong
b89ff6a5af 🎨 【微信支付】补充完善投诉通知结果类里缺少的字段
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2026-02-24 07:55:00 +00:00
大粽子
42405575f1 📝 更新赞助商logo 2026-02-24 15:52:15 +08:00
cbxbj
b259206bcb 🎨 #3876 【企业微信】审批详情接口返回里的 ContentValue 数据模型添加了部分官方新增字段
Co-authored-by: cbxbj <cbxbj@qq.com>
2026-02-09 14:19:02 +08:00
Copilot
3965823f0d 🎨 #3872 【微信支付】补充微工卡批量转账 API 缺失的必要字段 2026-01-31 22:54:53 +08:00
Copilot
780c24bda0 🆕 #3871 【微信支付】增加视频上传接口 2026-01-31 00:53:44 +08:00
10 changed files with 407 additions and 6 deletions

View File

@@ -51,8 +51,8 @@
赞助商招募中
</td>
<td align="center">
<a href="https://www.crmeb.com/xiazai" target="_blank">
<img height="120" src="https://crmebjavamer.oss-cn-beijing.aliyuncs.com/crmebimage/gitee/WechatIMG202.jpg" alt="ad">
<a href="https://github.crmeb.net/u/wxjava" target="_blank">
<img height="120" src="https://crmebjavamer.oss-cn-beijing.aliyuncs.com/crmebimage/image/2026/01/30/0a71b2b3535d42b187fff977c33faa30mh9gipgeja.png" alt="ad">
</a>
</td>
</tr>

View File

@@ -58,6 +58,9 @@ public class ContentValue implements Serializable {
private Formula formula;
@SerializedName("bank_account")
private BankAccount bankAccount;
/**
* The type Date.
*/
@@ -68,6 +71,23 @@ public class ContentValue implements Serializable {
@SerializedName("s_timestamp")
private String timestamp;
@SerializedName("timezone_info")
private TimezoneInfo timezoneInfo;
/**
* The type TimezoneInfo.
*/
@Data
public static class TimezoneInfo implements Serializable {
private static final long serialVersionUID = 164839205748392017L;
@SerializedName("zone_offset")
private String zoneOffset;
@SerializedName("zone_desc")
private String zoneDesc;
}
}
/**
@@ -228,6 +248,8 @@ public class ContentValue implements Serializable {
private Long end;
@SerializedName("new_duration")
private Long duration;
@SerializedName("timezone_info")
private Date.TimezoneInfo timezoneInfo;
}
/**
@@ -341,4 +363,55 @@ public class ContentValue implements Serializable {
private String value;
}
/**
* The type BankAccount
*/
@Data
public static class BankAccount implements Serializable {
private static final long serialVersionUID = 938475610283746192L;
@SerializedName("account_type")
private Long accountType;
@SerializedName("account_name")
private String accountName;
@SerializedName("account_number")
private String accountNumber;
private String remark;
private Bank bank;
/**
* The type Bank
*/
@Data
public static class Bank implements Serializable {
private static final long serialVersionUID = 527384916203847561L;
@SerializedName("bank_alias")
private String bankAlias;
@SerializedName("bank_alias_code")
private String bankAliasCode;
private String province;
@SerializedName("province_code")
private Long provinceCode;
private String city;
@SerializedName("city_code")
private Long cityCode;
@SerializedName("bank_branch_name")
private String bankBranchName;
@SerializedName("bank_branch_id")
private String bankBranchId;
}
}
}

View File

@@ -142,6 +142,61 @@ public class PayrollTransferBatchesRequest implements Serializable {
@SerializedName(value = "total_num")
private Integer totalNum;
/**
* <pre>
* 字段名:用工类型
* 变量名employment_type
* 是否必填:是
* 类型string[1, 32]
* 描述:
* 微工卡服务仅支持用于与商户有用工关系的用户,需明确用工类型;参考值:
* LONG_TERM_EMPLOYMENT长期用工
* SHORT_TERM_EMPLOYMENT短期用工
* COOPERATION_EMPLOYMENT合作关系
* 示例值LONG_TERM_EMPLOYMENT
* </pre>
*/
@SerializedName(value = "employment_type")
private String employmentType;
/**
* <pre>
* 字段名:用工场景
* 变量名employment_scene
* 是否必填:否
* 类型string[1, 32]
* 描述:
* 用工场景,参考值:
* LOGISTICS物流
* MANUFACTURING制造业
* HOTEL酒店
* CATERING餐饮业
* EVENT活动促销
* RETAIL零售
* OTHERS其他
* 示例值LOGISTICS
* </pre>
*/
@SerializedName(value = "employment_scene")
private String employmentScene;
/**
* <pre>
* 字段名:特约商户授权类型
* 变量名authorization_type
* 是否必填:是
* 类型string[1, 32]
* 描述:
* 特约商户授权类型:
* INFORMATION_AUTHORIZATION_TYPE特约商户信息授权类型
* FUND_AUTHORIZATION_TYPE特约商户资金授权类型
* INFORMATION_AND_FUND_AUTHORIZATION_TYPE特约商户信息和资金授权类型
* 示例值INFORMATION_AUTHORIZATION_TYPE
* </pre>
*/
@SerializedName(value = "authorization_type")
private String authorizationType;
/**
* <pre>
* 字段名:转账明细列表
@@ -235,5 +290,20 @@ public class PayrollTransferBatchesRequest implements Serializable {
@SpecEncrypt
@SerializedName(value = "user_name")
private String userName;
/**
* <pre>
* 字段名:收款用户身份证
* 变量名user_id_card
* 是否必填:否
* 类型string[1, 1024]
* 描述:
* 收款用户身份证号。该字段需进行加密处理,加密方法详见敏感信息加密说明
* 示例值8609cb22e1774a50a930e414cc71eca06121bcd266335cda230d24a7886a8d9f
* </pre>
*/
@SpecEncrypt
@SerializedName(value = "user_id_card")
private String userIdCard;
}
}

View File

@@ -0,0 +1,29 @@
package com.github.binarywang.wxpay.bean.media;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
/**
* 视频文件上传返回结果对象
*
* @author copilot
*/
@NoArgsConstructor
@Data
public class VideoUploadResult {
public static VideoUploadResult fromJson(String json) {
return WxGsonBuilder.create().fromJson(json, VideoUploadResult.class);
}
/**
* 媒体文件标识 Id
* <p>
* 微信返回的媒体文件标识Id。
* 示例值6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0
*/
@SerializedName("media_id")
private String mediaId;
}

View File

@@ -1,5 +1,6 @@
package com.github.binarywang.wxpay.bean.notify;
import com.github.binarywang.wxpay.v3.SpecEncrypt;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -69,6 +70,113 @@ public class ComplaintNotifyResult implements Serializable {
@SerializedName(value = "action_type")
private String actionType;
/**
* <pre>
* 字段名:商户订单号
* 是否必填:是
* 描述:
* 投诉单关联的商户订单号
* </pre>
*/
@SerializedName("out_trade_no")
private String outTradeNo;
/**
* <pre>
* 字段名:投诉时间
* 是否必填:是
* 描述投诉时间遵循rfc3339标准格式格式为yyyy-MM-DDTHH:mm:ss.sss+TIMEZONEyyyy-MM-DD表示年月日
* T出现在字符串中表示time元素的开头HH:mm:ss.sss表示时分秒毫秒TIMEZONE表示时区+08:00表示东八区时间领先UTC 8小时即北京时间
* 例如2015-05-20T13:29:35.120+08:00表示北京时间2015年05月20日13点29分35秒
* 示例值2015-05-20T13:29:35.120+08:00
* </pre>
*/
@SerializedName("complaint_time")
private String complaintTime;
/**
* <pre>
* 字段名:订单金额
* 是否必填:是
* 描述:
* 订单金额,单位(分)
* </pre>
*/
@SerializedName("amount")
private Integer amount;
/**
* <pre>
* 字段名:投诉人联系方式
* 是否必填:否
* 投诉人联系方式。该字段已做加密处理,具体解密方法详见敏感信息加密说明。
* </pre>
*/
@SerializedName("payer_phone")
@SpecEncrypt
private String payerPhone;
/**
* <pre>
* 字段名:投诉详情
* 是否必填:是
* 投诉的具体描述
* </pre>
*/
@SerializedName("complaint_detail")
private String complaintDetail;
/**
* <pre>
* 字段名:投诉单状态
* 是否必填:是
* 标识当前投诉单所处的处理阶段,具体状态如下所示:
* PENDING待处理
* PROCESSING处理中
* PROCESSED已处理完成
* </pre>
*/
@SerializedName("complaint_state")
private String complaintState;
/**
* <pre>
* 字段名:微信订单号
* 是否必填:是
* 描述:
* 投诉单关联的微信订单号
* </pre>
*/
@SerializedName("transaction_id")
private String transactionId;
/**
* <pre>
* 字段名:商户处理状态
* 是否必填:是
* 描述:
* 触发本次投诉通知回调的具体动作类型,枚举如下:
* 常规通知:
* CREATE_COMPLAINT用户提交投诉
* CONTINUE_COMPLAINT用户继续投诉
* USER_RESPONSE用户新留言
* RESPONSE_BY_PLATFORM平台新留言
* SELLER_REFUND商户发起全额退款
* MERCHANT_RESPONSE商户新回复
* MERCHANT_CONFIRM_COMPLETE商户反馈处理完成
* USER_APPLY_PLATFORM_SERVICE用户申请平台协助
* USER_CANCEL_PLATFORM_SERVICE用户取消平台协助
* PLATFORM_SERVICE_FINISHED客服结束平台协助
*
* 申请退款单的附加通知:
* 以下通知会更新投诉单状态,建议收到后查询投诉单详情。
* MERCHANT_APPROVE_REFUND商户同意退款
* MERCHANT_REJECT_REFUND商户驳回退款
* REFUND_SUCCESS退款到账
* </pre>
*/
@SerializedName("complaint_handle_state")
private String complaintHandleState;
}
}

View File

@@ -1,6 +1,7 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.media.ImageUploadResult;
import com.github.binarywang.wxpay.bean.media.VideoUploadResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import java.io.File;
@@ -42,5 +43,34 @@ public interface MerchantMediaService {
*/
ImageUploadResult imageUploadV3(InputStream inputStream, String fileName) throws WxPayException, IOException;
/**
* <pre>
* 通用接口-视频上传API
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/chapter3_2.shtml
* 接口链接https://api.mch.weixin.qq.com/v3/merchant/media/video_upload
* </pre>
*
* @param videoFile 需要上传的视频文件
* @return VideoUploadResult 微信返回的媒体文件标识Id。示例值6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0
* @throws WxPayException the wx pay exception
* @throws IOException the io exception
*/
VideoUploadResult videoUploadV3(File videoFile) throws WxPayException, IOException;
/**
* <pre>
* 通用接口-视频上传API
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/chapter3_2.shtml
* 接口链接https://api.mch.weixin.qq.com/v3/merchant/media/video_upload
* 注意此方法会将整个视频流读入内存计算SHA256后再上传大文件可能导致OOM建议大文件使用File方式上传
* </pre>
*
* @param inputStream 需要上传的视频文件流
* @param fileName 需要上传的视频文件名
* @return VideoUploadResult 微信返回的媒体文件标识Id。示例值6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0
* @throws WxPayException the wx pay exception
* @throws IOException the io exception
*/
VideoUploadResult videoUploadV3(InputStream inputStream, String fileName) throws WxPayException, IOException;
}

View File

@@ -1,6 +1,7 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.media.ImageUploadResult;
import com.github.binarywang.wxpay.bean.media.VideoUploadResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.MerchantMediaService;
import com.github.binarywang.wxpay.service.WxPayService;
@@ -40,7 +41,7 @@ public class MerchantMediaServiceImpl implements MerchantMediaService {
@Override
public ImageUploadResult imageUploadV3(InputStream inputStream, String fileName) throws WxPayException, IOException {
String url = String.format("%s/v3/merchant/media/upload", this.payService.getPayBaseUrl());
try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[2048];
int len;
while ((len = inputStream.read(buffer)) > -1) {
@@ -57,4 +58,40 @@ public class MerchantMediaServiceImpl implements MerchantMediaService {
}
}
@Override
public VideoUploadResult videoUploadV3(File videoFile) throws WxPayException, IOException {
String url = String.format("%s/v3/merchant/media/video_upload", this.payService.getPayBaseUrl());
try (FileInputStream s1 = new FileInputStream(videoFile)) {
String sha256 = DigestUtils.sha256Hex(s1);
try (InputStream s2 = new FileInputStream(videoFile)) {
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost.Builder(URI.create(url))
.withVideo(videoFile.getName(), sha256, s2)
.build();
String result = this.payService.postV3(url, request);
return VideoUploadResult.fromJson(result);
}
}
}
@Override
public VideoUploadResult videoUploadV3(InputStream inputStream, String fileName) throws WxPayException, IOException {
String url = String.format("%s/v3/merchant/media/video_upload", this.payService.getPayBaseUrl());
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[2048];
int len;
while ((len = inputStream.read(buffer)) > -1) {
bos.write(buffer, 0, len);
}
bos.flush();
byte[] data = bos.toByteArray();
String sha256 = DigestUtils.sha256Hex(data);
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost.Builder(URI.create(url))
.withVideo(fileName, sha256, new ByteArrayInputStream(data))
.build();
String result = this.payService.postV3(url, request);
return VideoUploadResult.fromJson(result);
}
}
}

View File

@@ -35,7 +35,7 @@ public class WechatPayUploadHttpPost extends HttpPost {
this.uri = uri;
}
public Builder withImage(String fileName, String fileSha256, InputStream inputStream) {
private Builder withMedia(String fileName, String fileSha256, InputStream inputStream) {
this.fileName = fileName;
this.fileSha256 = fileSha256;
this.fileInputStream = inputStream;
@@ -50,13 +50,21 @@ public class WechatPayUploadHttpPost extends HttpPost {
return this;
}
public Builder withImage(String fileName, String fileSha256, InputStream inputStream) {
return withMedia(fileName, fileSha256, inputStream);
}
public Builder withVideo(String fileName, String fileSha256, InputStream inputStream) {
return withMedia(fileName, fileSha256, inputStream);
}
public WechatPayUploadHttpPost build() {
if (fileName == null || fileSha256 == null || fileInputStream == null) {
throw new IllegalArgumentException("缺少待上传图片文件信息");
throw new IllegalArgumentException("缺少待上传文件信息");
}
if (uri == null) {
throw new IllegalArgumentException("缺少上传图片接口URL");
throw new IllegalArgumentException("缺少上传文件接口URL");
}
String meta = String.format("{\"filename\":\"%s\",\"sha256\":\"%s\"}", fileName, fileSha256);

View File

@@ -1,6 +1,7 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.media.ImageUploadResult;
import com.github.binarywang.wxpay.bean.media.VideoUploadResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.MerchantMediaService;
import com.github.binarywang.wxpay.service.WxPayService;
@@ -51,4 +52,45 @@ public class MerchantMediaServiceImplTest {
log.info("mediaId2[{}]",mediaId2);
}
@Test
public void testVideoUploadV3() throws WxPayException, IOException {
MerchantMediaService merchantMediaService = new MerchantMediaServiceImpl(wxPayService);
String filePath = "你的视频文件的路径地址";
// String filePath = "WxJava/test-video.mp4";
File file = new File(filePath);
VideoUploadResult videoUploadResult = merchantMediaService.videoUploadV3(file);
String mediaId = videoUploadResult.getMediaId();
log.info("视频上传成功mediaId[{}]", mediaId);
VideoUploadResult videoUploadResult2 = merchantMediaService.videoUploadV3(file);
String mediaId2 = videoUploadResult2.getMediaId();
log.info("视频上传成功2mediaId2[{}]", mediaId2);
}
@Test
public void testVideoUploadV3WithInputStream() throws WxPayException, IOException {
MerchantMediaService merchantMediaService = new MerchantMediaServiceImpl(wxPayService);
String filePath = "你的视频文件的路径地址";
// String filePath = "WxJava/test-video.mp4";
File file = new File(filePath);
try (java.io.FileInputStream inputStream = new java.io.FileInputStream(file)) {
VideoUploadResult videoUploadResult = merchantMediaService.videoUploadV3(inputStream, file.getName());
String mediaId = videoUploadResult.getMediaId();
log.info("通过InputStream上传视频成功mediaId[{}]", mediaId);
}
}
}

View File

@@ -139,6 +139,9 @@ public class PayrollServiceImplTest {
.batchRemark("2019年1月深圳分部报销单")
.totalAmount(200000L)
.totalNum(1)
.employmentType("LONG_TERM_EMPLOYMENT")
.employmentScene("LOGISTICS")
.authorizationType("INFORMATION_AUTHORIZATION_TYPE")
.transferDetailList(Collections.singletonList(
PayrollTransferBatchesRequest.TransferDetail.builder()
.outDetailNo("x23zy545Bd5436" + System.currentTimeMillis())
@@ -146,6 +149,7 @@ public class PayrollServiceImplTest {
.transferRemark("2020年4月报销")
.openid("o-MYE42l80oelYMDE34nYD456Xoy")
.userName("张三")
.userIdCard("8609cb22e1774a50a930e414cc71eca06121bcd266335cda230d24a7886a8d9f")
.build()
))
.build();