From b94106fd57a779f2a6a0b9f81f68a68d3410aca4 Mon Sep 17 00:00:00 2001
From: kongkong
+ * 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作
+ *
+ * @author WxJava Team
+ * @see 运营工具-商家转账API
+ */
+public interface BusinessOperationTransferService {
+
+ /**
+ *
+ * 注意:由于预约扣费功能需要用户授权和实际的签约关系,
+ * 这些测试主要用于验证接口调用的正确性,而不是功能的完整性。
+ * 实际测试需要在具有有效签约关系的环境中进行。
+ *
+ * 用于获取所有小程序类目的 ID 和名称信息,包括一级类目和二级类目。
+ *
+ * 包含一级类目和二级类目的 ID 和名称。
+ *
+ * 问题背景:当微信返回的 XML 包含某些未在 WxPayOrderNotifyResult 中定义的字段时,
+ * 这些字段会被微信服务器用于签名计算。如果 toMap() 方法丢失了这些字段,
+ * 则签名验证会失败,抛出 "参数格式校验错误!" 异常。
+ *
+ * 解决方案:修改 WxPayOrderNotifyResult.toMap() 方法,使用父类的 toMap() 方法
+ * 直接从原始 XML 解析所有字段,而不是使用 SignUtils.xmlBean2Map(this)。
+ *
+ * 通过 code 换取用户登录态信息,用于多端登录场景(如手表端)。
+ *
+ * 从业机构调用该接口向微信医保后台下单
+ * 文档地址: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 {
+
+ /**
+ *
+ * 从业机构调用医保自费混合收款下单接口后返回的结果
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012503131
+ * @author xgl
+ * @date 2025/12/19 14:37
+ */
+@Data
+public class MedInsOrdersResult {
+ /**
+ *
+ * 从业机构调用该接口向微信医保后台通知医保订单的退款成功结果
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012166534
+ * @author xgl
+ * @date 2025/12/20
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class MedInsRefundNotifyRequest {
+
+ /**
+ *
+ * 描述医保自费混合支付中现金增加的类型
+ *
+ * @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;
+
+/**
+ *
+ * 文档地址:用工关系简介
+ *
+ * 企业可以调用该接口解除和用户的B2C用工关系
+ *
+ * 企业可以调用该接口向用户推送用工相关消息
+ *
+ * 文档地址:推送用工消息
+ *
+ * 文档地址:解绑用工关系
+ *
+ * 重要提示:审核额度限制
+ *
+ * 最佳实践:
+ *
+ * 文档地址:
+ * 查询额度
+ *
+ * 返回字段说明:
+ *
+ * 重要说明:
+ *
+ * 使用示例:
+ *
+ * 重要提示:审核额度限制
+ *
+ * 使用示例:
+ *
+ * 用于查询第三方平台服务商的当月提交审核限额和加急次数
+ *
+ * 字段说明:
+ *
+ * 重要提示:
+ *
+ * 这是一个完整的示例,展示如何在提交审核前检查额度,避免额度不足导致的失败
+ *
+ * 当需要为多个小程序提交审核时,应该先统一检查额度是否充足
+ *
+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842}
+ *
+ * 参考:{@code https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN}
+ *
* 请阅读:
* 公众平台:全局返回码说明
* 企业微信:全局错误码
+ *
+ * 对应操作:{@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
- *
+ * 发起运营工具商家转账
+ *
+ * 请求方式: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
+ * 押金消费请求
+ * 详见: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
+ * 查询押金订单请求
+ * 详见: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
+ * 押金退款请求
+ * 详见: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
+ * 押金撤销请求
+ * 详见: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
+ * 押金下单请求
+ * 详见: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
+ * 押金消费结果
+ * 详见: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 008/189] =?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
+ * 字段名:微信支付订单号
+ * 变量名: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;
+
+/**
+ * 微信支付预约扣费服务测试类
+ *
* {@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
+ * 获取所有类目名称信息,用于给用户展示选择
+ * 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;
+
+/**
+ * 获取类目名称信息的返回结果.
+ *
+ * 预约转账批次单号查询接口响应结果
+ * 通过预约批次单号查询批量预约商家转账批次单基本信息。
+ * 文档地址: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
+ * 批量预约商家转账请求参数
+ * 商户可以通过批量预约接口一次发起批量转账请求,最多可以同时向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
+ * 批量预约商家转账响应结果
+ * 文档地址: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
+ * 商户查询用户授权信息接口响应结果
+ * 商户通过此接口可查询用户是否对商户的商家转账场景进行了授权。
+ * 文档地址: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
+ * 微信支付-服务商申请退款.
+ * 应用场景
+ * 当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。
+ *
+ * 注意:
+ * 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 028/189] =?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
@@ -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 045/189] =?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
+ * 查询投诉单列表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
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @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
+ * 字段名:商户订单号
+ * 是否必填:是
+ * 描述:商户系统内部订单号
+ *
+ */
+ @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
+ * 字段名:总数
+ * 是否必填:是
+ * 描述:总投诉单条数,用于分页展示
+ *
+ */
+ @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
+ * 字段名:总数
+ * 是否必填:是
+ * 描述:总协商历史条数,用于分页展示
+ *
+ */
+ @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
+ * 字段名:投诉单号
+ * 是否必填:是
+ * 描述:投诉单对应的投诉单号
+ *
+ */
+ @SerializedName("complaint_id")
+ private String complaintId;
+
+ /**
+ *
+ * 字段名:回复内容
+ * 是否必填:是
+ * 描述:具体的回复内容,长度不超过200字符
+ *
+ */
+ @SerializedName("response_content")
+ private String responseContent;
+
+ /**
+ *
+ * 字段名:回复图片
+ * 是否必填:否
+ * 描述:回复的图片凭证,最多可传5张图片,由图片上传接口返回
+ *
+ */
+ @SerializedName("response_images")
+ private List
+ * 字段名:跳转链接
+ * 是否必填:否
+ * 描述:点击跳转链接
+ *
+ */
+ @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 030/189] =?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
+ * 多端登录验证接口的响应
+ * 文档地址: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 040/189] =?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 041/189] =?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 @@
+ * 字段名:混合支付类型
+ * 变量名: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;
+
+/**
+ * 医保自费混合收款下单响应
+ *
+ * 字段名:混合交易订单号
+ * 变量名: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;
+
+/**
+ * 医保退款通知请求
+ *
+ * 字段名:医保自费混合订单号
+ * 必填:是
+ * 类型: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;
+
+/**
+ * 现金增加类型枚举
+ *
+ * 医保混合收款成功通知结果
+ * 文档地址: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
+ * 微信支付实名验证请求对象.
+ * 详见文档: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
+ * 微信支付实名验证返回结果.
+ * 详见文档: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 055/189] =?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 056/189] =?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 057/189] =?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;
+
+/**
+ * 小程序用工关系相关操作接口
+ *
+ * 文档: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
+ * 字段名:用户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;
+
+/**
+ * 小程序解绑用工关系请求实体
+ *
+ * 字段名:用户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 058/189] =?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
+ * 字段名:收款授权模式
+ * 变量名: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 059/189] =?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;
+ /**
+ *
*/
@SerializedName("total_fee")
- public Long totalFee;
+ public Integer totalFee;
/**
*
+ * 字段名:预支付交易会话标识
+ * 变量名: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
+ * 根据已有的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 060/189] =?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 061/189] =?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
+ *
* @param deviceTicketRequest
* @return
* @throws WxErrorException
@@ -37,4 +39,53 @@ public interface WxMaDeviceSubscribeService {
*/
void sendDeviceSubscribeMsg(WxMaDeviceSubscribeMessageRequest deviceSubscribeMessageRequest) throws WxErrorException;
+ /**
+ *
+ *
+ * {@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
+ *
+ *
+ *
+ * {@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 @@
/**
* 微信小程序代码包提交审核(仅供第三方开发者代小程序调用)
+ *
+ *
+ * {@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;
/**
- * 微信开放平台小程序当前分阶段发布详情
+ * 微信开放平台小程序提交审核额度查询结果
+ *
+ *
+ *
+ *
+ *
+ * @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();
+ }
+ */
+ }
+
+ /**
+ * 演示提交审核前检查额度的最佳实践
+ *
+ * 创建设备组
+ * 详情请见: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
+ * 设备组删除设备
+ * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/removeIotGroupDevice.html
+ *
+ *
+ * @param request 请求参数
+ * @return 成功删除的设备信息
+ * @throws WxErrorException
+ */
+ List>() {
+ }.getType());
+ }
+
+ @Override
+ public List
>() {
+ }.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
* 字段名:医保统筹基金支付金额
* 变量名: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 066/189] =?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 067/189] =?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
+ * 获取应用管理员列表
+ * 第三方服务商可以用此接口获取授权企业中某个第三方应用或者代开发应用的管理员列表(不包括外部管理员),
+ * 以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
+ * 详情请见: 文档
+ *
+ *
+ * @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
+ * 单据创建后,用户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
* 无效媒体文件类型
- * 对应操作:
+ * uploadTempMedia
+ *
* 无效媒体文件 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
/**
* 企业可通过此接口,转接在职成员的客户给其他成员。
- *
+ *
* 权限说明:
- * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
+
+ *
* 群主离职了的客户群,才可继承
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
* 继承给的新群主,必须有激活企业微信
* 同一个人的群,限制每天最多分配300个给新群主
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限}
* 对于第三方/自建应用,群主必须在应用的可见范围。
- *
+
+ *
* 请求方式: 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
/**
*
+ *
+ *
+ *
+ *
+ *
* 请求方式: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 {
*
- * 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 {
/**
*
* 文档地址
- *
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
* 权限说明:
- *
* handover_userid必须是已离职用户。
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
* 权限说明:
- *
* 注意::
- *
* 注意:
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
@@ -716,11 +724,14 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
* 企业可通过此接口添加企业群发消息的任务并通知客服人员发送给相关客户或客户群。(注:企业微信终端需升级到2.7.5版本及以上)
* 注意:调用该接口并不会直接发送消息给客户/客户群,需要相关的客服人员操作以后才会实际发送(客服人员的企业微信需要升级到2.7.5及以上版本)
* 同一个企业每个自然月内仅可针对一个客户/客户群发送4条消息,超过限制的用户将会被忽略。
- *
*
* @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获取家长或者学生信息
- *
* 企业和第三方应用可通过此接口获取企业与成员的群发记录。
- * 获取企业群发成员执行结果
+ * 文档地址: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
+ *
* @param request 查询参数
* @return 客户数据统计 -企业汇总数据
* @throws WxErrorException the wx error exception
@@ -238,7 +238,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List
+ *
* @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
*
* 请求方式: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 {
/**
* 获取用户二次验证信息
- *
*
* @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查询预定详情.
*
* 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 {
*
* 企业可通过此接口根据预定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/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 {
/**
* 付费状态
- *
+ *
*
*
+ *
*
*
* 请使用.
- * {@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
+ *
* 取值范围:-604800 ~ 86399
*/
@SerializedName("remind_time_diffs")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java
index 16cf32fa5c..4f74bab79c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java
@@ -39,7 +39,7 @@ public static WxCpOaMeetingRoomBookResult fromJson(String json) {
private String schedule_id;
/**
* 通过会议预定会议室 和 通过日程预定会议室 接口返回
- *
+ *
* 会议室冲突日期列表,为当天0点的时间戳;使用重复会议预定会议室,部分日期与会议室预定情况冲突时返回
*/
@SerializedName("conflict_date")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
index 58daeb007c..eada2ad100 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
@@ -8,7 +8,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025/1/16 09:40
+ * @since 2025/1/16 09:40
*/
@Data
public class TemplateTips {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
index 939e6819a0..0af8be8be5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
@@ -5,7 +5,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025/1/16 09:42
+ * @since 2025/1/16 09:42
*/
@Data
public class TemplateTipsContent {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
index ac4681038c..78e88562e8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
@@ -5,7 +5,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025/1/16 09:45
+ * @since 2025/1/16 09:45
*/
@Data
public class TemplateTipsSubText {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
index 9c99b2688e..f33c31d054 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
@@ -6,7 +6,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025/1/16 09:46
+ * @since 2025/1/16 09:46
*/
@Data
public class TemplateTipsSubTextContent {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
index 4cd198409a..fd4db2c18f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
@@ -5,7 +5,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025/1/16 09:49
+ * @since 2025/1/16 09:49
*/
@Data
public class TemplateTipsSubTextContentLink {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
index 12969cdcdb..0a2e792edb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
@@ -3,9 +3,9 @@
import lombok.Data;
/**
- * @author mrsiu@msn.com
- * @date 2025/1/16 09:47
- * @version 1.0
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @since 2025/1/16 09:47
*/
@Data
public class TemplateTipsSubTextContentPlainText {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
index 100c5bb137..c9dbe68abf 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
@@ -6,9 +6,9 @@
import java.util.List;
/**
- * @author mrsiu@msn.com
- * @date 2025/1/16 09:43
- * @version 1.0
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @since 2025/1/16 09:43
*/
@Data
public class TemplateTipsText {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
index 07acb189a8..df758ac3a2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
@@ -31,8 +31,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Update corp access token.
*
- * @param corpId
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @param corpAccessToken the corp access token
* @param expiresInSeconds the expires in seconds
*/
@@ -41,8 +41,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* 授权企业的access token相关
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the access token
*/
String getCorpAccessToken(String corpId, Integer agentId);
@@ -50,8 +50,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Gets access token entity.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the access token entity
*/
WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId);
@@ -59,8 +59,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Is access token expired boolean.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the boolean
*/
boolean isCorpAccessTokenExpired(String corpId, Integer agentId);
@@ -68,8 +68,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Expire access token.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
*/
void expireCorpAccessToken(String corpId, Integer agentId);
@@ -118,7 +118,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Gets access token lock.
*
- * @param corpId the corp id
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the access token lock
*/
Lock getCorpAccessTokenLock(String corpId, Integer agentId);
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
index 2cd37821b5..0a2ed0e76a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
@@ -133,7 +133,7 @@ public interface WxCpTpConfigStorage {
String getEncodingAESKey();
/**
- * 企微服务商企业ID & 企业secret
+ * {@code 企微服务商企业ID & 企业secret}
*
* @return the corp id
*/
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
index 08eed33c16..6d1260fe68 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
@@ -187,7 +187,9 @@ public String getEncodingAESKey() {
/**
- * 企微服务商企业ID & 企业secret, 来自于企微配置
+ * {@code 企微服务商企业ID & 企业secret, 来自于企微配置}
+ *
+ * @return 企业ID
*/
@Override
public String getCorpId() {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
index fc124251f5..7fbbc9ccce 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
@@ -29,7 +29,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
private final transient Map
- * 获取审批申请详情
+ * 获取审批申请详情
*
* @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 080/189] =?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 081/189] =?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 切换配置,在异步或多线程场景需要特别注意线程上下文切换。
+ *
- * 文档地址:推送用工消息
+ * 文档地址:推送用工消息
*
- * 文档地址:解绑用工关系
+ * 文档地址:解绑用工关系
*
+ * 微信返回的媒体文件标识Id。
+ * 示例值:6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0
+ */
+ @SerializedName("media_id")
+ private String mediaId;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java
index 0e35dbb68b..f7f0aaaf3e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java
@@ -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;
+ /**
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/hr/employee/get_field_info?access_token=ACCESS_TOKEN
+ * 权限说明:
+ * 需要配置人事助手的secret,调用接口前需给对应成员赋予人事小助手应用的权限。
+ *
+ * @param fields 指定字段key列表,不填则返回全部字段
+ * @return 字段信息响应 wx cp hr employee field info resp
+ * @throws WxErrorException the wx error exception
+ */
+ WxCpHrEmployeeFieldInfoResp getFieldInfo(List
+ * 请求方式:POST(HTTPS)
+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/hr/employee/get_employee_field_info?access_token=ACCESS_TOKEN
+ * 权限说明:
+ * 需要配置人事助手的secret,调用接口前需给对应成员赋予人事小助手应用的权限。
+ *
+ * @param userids 员工userid列表,不超过20个
+ * @param fields 指定字段key列表,不填则返回全部字段
+ * @return 员工档案数据响应 wx cp hr employee field data resp
+ * @throws WxErrorException the wx error exception
+ */
+ WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(List
+ * 请求方式:POST(HTTPS)
+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/hr/employee/update_employee_field_info?access_token=ACCESS_TOKEN
+ * 权限说明:
+ * 需要配置人事助手的secret,调用接口前需给对应成员赋予人事小助手应用的权限。
+ *
+ * @param userid 员工userid
+ * @param fieldList 字段数据列表
+ * @throws WxErrorException the wx error exception
+ */
+ void updateEmployeeFieldInfo(String userid, List
+ * 在该原因下,若商户除了relate_limitations所罗列的被管控能力,还有其他被管控的能力时会返回(如有多项以英文逗号分隔)
+ *
+ * 在该原因下,若存在解脱路径时会返回
+ *
+ * 若解脱路径recover_way为“填写尽调信息”、“补充受益所有人信息”,需通过提交尽调来解脱,此处会返回“尽调单号”;若解脱路径recover_way
+ * 为“提交相关信息申诉”,需通过提交资料来解脱,此处会返回“商户管理记录单号”;若解脱路径recover_way为“联系有权机关咨询”,此处会返回有权机关信息
+ *
+ * 在该原因下,若存在解脱帮助说明时会返回
+ *
+ * 管控处置方式类型,默认是立即管控
+ *
+ * 若商户被管控时会返回,延迟管控但是未到管控时间时不会返回
+ *
+ * 该类是订阅通知的通用结构,每个字段代表的含义和订阅类型有关。请依据文档自行判断使用。
+ *
+ * 产品介绍
+ *
+ * 接口文档
+ * 接口文档:
+ *
+ * 查询服务卡片状态
+ *
+ * @author GitHub Copilot
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaGetUserNotifyRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户身份标识符.
+ * 接口文档:
+ *
+ * 查询服务卡片状态
+ *
+ * @author GitHub Copilot
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxMaGetUserNotifyResult extends WxMaBaseResponse {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 卡片状态信息.
+ */
+ @SerializedName("notify_info")
+ private NotifyInfo notifyInfo;
+
+ /**
+ * 卡片状态详情.
+ */
+ @Data
+ public static class NotifyInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 卡片ID.
+ */
+ @SerializedName("notify_type")
+ private Integer notifyType;
+
+ /**
+ * 上次有效推送的卡片状态与状态相关字段,没推送过为空字符串.
+ */
+ @SerializedName("content_json")
+ private String contentJson;
+
+ /**
+ * code 状态:0 正常;1 有风险;2 异常;10 用户拒收本次code.
+ */
+ @SerializedName("code_state")
+ private Integer codeState;
+
+ /**
+ * code 过期时间,秒级时间戳.
+ */
+ @SerializedName("code_expire_time")
+ private Long codeExpireTime;
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaServiceNotifyExtRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaServiceNotifyExtRequest.java
new file mode 100644
index 0000000000..56315ce95e
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaServiceNotifyExtRequest.java
@@ -0,0 +1,82 @@
+package cn.binarywang.wx.miniapp.bean;
+
+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 GitHub Copilot
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaServiceNotifyExtRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户身份标识符.
+ * 接口文档:
+ *
+ * 激活与更新服务卡片
+ *
+ * @author GitHub Copilot
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaServiceNotifyRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户身份标识符.
+ * 同一企业(corpId 相同)下可配置多个条目以使用不同的权限 Secret,例如: 但同一 corpId 下不允许出现重复的 agentId(包括多个 null)。 企业微信中不同的 corpSecret 对应不同的权限范围,常见的有: 如需同时使用多种权限范围(例如:既要操作通讯录,又要调用自建应用接口),
+ * 可在 {@code wx.cp.corps} 下配置多个条目,每个条目使用对应权限的 {@code corpSecret},
+ * 其中通讯录同步的条目无需填写 {@code agentId}。 企业微信针对不同的功能模块提供了不同的 Secret,每种 Secret 只对对应模块的接口有调用权限: 使用自建应用 Secret 时,需要填写对应应用的 AgentId。 使用通讯录同步 Secret 时,无需填写此字段。 同一企业(corpId 相同)下可配置多个条目以使用不同的权限 Secret,例如: 但同一 corpId 下不允许出现重复的 agentId(包括多个 null)。 企业微信中不同的 corpSecret 对应不同的权限范围,常见的有: 如需同时使用多种权限范围(例如:既要操作通讯录,又要调用自建应用接口),
+ * 可在 {@code wx.cp.corps} 下配置多个条目,每个条目使用对应权限的 {@code corpSecret},
+ * 其中通讯录同步的条目无需填写 {@code agentId}。 企业微信针对不同的功能模块提供了不同的 Secret,每种 Secret 只对对应模块的接口有调用权限: 使用自建应用 Secret 时,需要填写对应应用的 AgentId。 使用通讯录同步 Secret 时,无需填写此字段。 官方文档: https://developer.work.weixin.qq.com/document/path/100719
+ *
+ */
+ 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 095/189] =?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 096/189] =?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 097/189] =?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
- * 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/laboruse/intro.html
+ * 服务端api文档:https://developers.weixin.qq.com/miniprogram/dev/server/API/laboruse/
+ * 整体流程文档: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/laboruse/intro.html
*
* @return 用工关系服务对象WxMaEmployeeRelationService
*/
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
index 8f240e9151..08d29000ee 100644
--- 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
@@ -15,6 +15,7 @@
*
* @author Binary Wang
* created on 2025-12-19
+ * update on 2026-01-22 15:06:33
*/
@RequiredArgsConstructor
public class WxMaEmployeeRelationServiceImpl implements WxMaEmployeeRelationService {
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
index 2d50479817..d93d9beb77 100644
--- 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
@@ -12,11 +12,12 @@
/**
* 小程序推送用工消息请求实体
*
- * 字段名:用户openid
+ * 字段名:模板id
* 是否必填:是
- * 描述:需要接收消息的用户openid
+ * 描述:需要在微信后台申请用工关系权限,通过后创建的模板审核通过后可以复制模板ID
*
*/
- @SerializedName("openid")
- private String openid;
+ @SerializedName("template_id")
+ private String templateId;
/**
*
- * 字段名:企业id
+ * 字段名:页面
* 是否必填:是
- * 描述:企业id,小程序管理员在微信开放平台配置
+ * 描述:用工消息通知跳转的page小程序链接(注意 小程序页面链接要是申请模板的小程序)
*
*/
- @SerializedName("corp_id")
- private String corpId;
+ @SerializedName("page")
+ private String page;
+
+ /**
+ *
+ * 字段名:被推送用户的openId
+ * 是否必填:是
+ * 描述:被推送用户的openId
+ *
+ */
+ @SerializedName("touser")
+ private String touser;
/**
*
* 字段名:消息内容
* 是否必填:是
- * 描述:推送的消息内容,文本格式,最长不超过200个字符
+ * 描述:需要根据小程序后台审核通过的模板id的字段类型序列化json传递
+ *
+ *
+ *
+ * 参考组装代码
+ *
+ *
*/
- @SerializedName("msg")
- private String msg;
+
+ @SerializedName("data")
+ private String data;
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
index e56d84670c..e357f246a5 100644
--- 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
@@ -8,15 +8,17 @@
import lombok.NoArgsConstructor;
import java.io.Serializable;
+import java.util.List;
/**
* 小程序解绑用工关系请求实体
*
+ * // 使用 HashMap 构建数据结构
+ * Map
*
- * 字段名:用户openid
+ * 字段名:用户openid列表
* 是否必填:是
- * 描述:需要解绑的用户openid
+ * 描述:需要解绑的用户openid列表
*
*/
- @SerializedName("openid")
- private String openid;
-
- /**
- *
- * 字段名:企业id
- * 是否必填:是
- * 描述:企业id,小程序管理员在微信开放平台配置
- *
- */
- @SerializedName("corp_id")
- private String corpId;
+ @SerializedName("openid_list")
+ private List
+ * 文档地址: https://developers.weixin.qq.com/miniprogram/dev/server/API/laboruse/
+ *
+ */
public interface Employee {
/** 解绑用工关系 */
- String UNBIND_EMPLOYEE_URL = "https://api.weixin.qq.com/wxa/unbinduserb2cauthinfo";
+ String UNBIND_EMPLOYEE_URL = "https://api.weixin.qq.com/wxa/business/unbinduserb2cauthinfo";
/** 推送用工消息 */
- String SEND_EMPLOYEE_MSG_URL = "https://api.weixin.qq.com/wxa/sendemployeerelationmsg";
+ String SEND_EMPLOYEE_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/employeerelationmsg/send";
}
}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImplTest.java
new file mode 100644
index 0000000000..53afad70f3
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaEmployeeRelationServiceImplTest.java
@@ -0,0 +1,73 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.employee.WxMaSendEmployeeMsgRequest;
+import cn.binarywang.wx.miniapp.bean.employee.WxMaUnbindEmployeeRequest;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.test.ApiTestModule;
+import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.*;
+
+@Slf4j
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMaEmployeeRelationServiceImplTest {
+
+ @Inject
+ protected WxMaService wxService;
+
+ @Test
+ public void testSendEmployeeMsg() throws WxErrorException {
+ WxMaSendEmployeeMsgRequest wxMaSendEmployeeMsgRequest = new WxMaSendEmployeeMsgRequest();
+ wxMaSendEmployeeMsgRequest.setPage("/pages/index/index");
+ wxMaSendEmployeeMsgRequest.setTouser("o0uBr12b1zdgCk1qDoBivmSYb9GA");
+ wxMaSendEmployeeMsgRequest.setTemplateId("nmO-O4V33TOREVLAlumwPCsHssqkt7mea_cyWNE-IFmZqT9jh_LsERhzDOsOqa-3");
+
+ // 使用 HashMap 构建数据结构
+ Map
-
-
-
-
+
+ 赞助商招募中
From 780c24bda0ca46084e504c49a4b22439937a6fe8 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 31 Jan 2026 00:53:44 +0800
Subject: [PATCH 101/189] =?UTF-8?q?:new:=20#3871=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=E8=A7=86?=
=?UTF-8?q?=E9=A2=91=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wxpay/bean/media/VideoUploadResult.java | 29 +++++++++++++
.../wxpay/service/MerchantMediaService.java | 30 +++++++++++++
.../impl/MerchantMediaServiceImpl.java | 39 ++++++++++++++++-
.../wxpay/v3/WechatPayUploadHttpPost.java | 14 +++++--
.../impl/MerchantMediaServiceImplTest.java | 42 +++++++++++++++++++
5 files changed, 150 insertions(+), 4 deletions(-)
create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/media/VideoUploadResult.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/media/VideoUploadResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/media/VideoUploadResult.java
new file mode 100644
index 0000000000..615cbbff5f
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/media/VideoUploadResult.java
@@ -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
+ *
+ * 通用接口-视频上传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
+ *
+ *
+ * @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;
+
+ /**
+ *
+ * 通用接口-视频上传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方式上传
+ *
+ *
+ * @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;
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java
index 7952513f56..ee77f5e974 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java
@@ -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 ImageUploadResult imageUploadV3(File imageFile) throws WxPayException,IOE
@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 ImageUploadResult imageUploadV3(InputStream inputStream, String fileName)
}
}
+ @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);
+ }
+ }
+
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WechatPayUploadHttpPost.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WechatPayUploadHttpPost.java
index 5f5e52d2ff..3387f37e3d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WechatPayUploadHttpPost.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WechatPayUploadHttpPost.java
@@ -35,7 +35,7 @@ public Builder(URI uri) {
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 Builder withImage(String fileName, String fileSha256, InputStream inputSt
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);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImplTest.java
index c8dd069b44..845992e43c 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImplTest.java
@@ -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 void testImageUploadV3() throws WxPayException, IOException {
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("视频上传成功2,mediaId2:[{}]", 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);
+ }
+
+ }
}
From 3965823f0d5ac56159b746494d4abf7f77067f69 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 31 Jan 2026 22:54:53 +0800
Subject: [PATCH 102/189] =?UTF-8?q?:art:=20#3872=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E8=A1=A5=E5=85=85=E5=BE=AE?=
=?UTF-8?q?=E5=B7=A5=E5=8D=A1=E6=89=B9=E9=87=8F=E8=BD=AC=E8=B4=A6=20API=20?=
=?UTF-8?q?=E7=BC=BA=E5=A4=B1=E7=9A=84=E5=BF=85=E8=A6=81=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../PayrollTransferBatchesRequest.java | 70 +++++++++++++++++++
.../service/impl/PayrollServiceImplTest.java | 4 ++
2 files changed, 74 insertions(+)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesRequest.java
index 50954e70e5..7b7eff0233 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PayrollTransferBatchesRequest.java
@@ -142,6 +142,61 @@ public class PayrollTransferBatchesRequest implements Serializable {
@SerializedName(value = "total_num")
private Integer totalNum;
+ /**
+ *
+ * 字段名:用工类型
+ * 变量名:employment_type
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 微工卡服务仅支持用于与商户有用工关系的用户,需明确用工类型;参考值:
+ * LONG_TERM_EMPLOYMENT:长期用工,
+ * SHORT_TERM_EMPLOYMENT:短期用工,
+ * COOPERATION_EMPLOYMENT:合作关系
+ * 示例值:LONG_TERM_EMPLOYMENT
+ *
+ */
+ @SerializedName(value = "employment_type")
+ private String employmentType;
+
+ /**
+ *
+ * 字段名:用工场景
+ * 变量名:employment_scene
+ * 是否必填:否
+ * 类型:string[1, 32]
+ * 描述:
+ * 用工场景,参考值:
+ * LOGISTICS:物流;
+ * MANUFACTURING:制造业;
+ * HOTEL:酒店;
+ * CATERING:餐饮业;
+ * EVENT:活动促销;
+ * RETAIL:零售;
+ * OTHERS:其他
+ * 示例值:LOGISTICS
+ *
+ */
+ @SerializedName(value = "employment_scene")
+ private String employmentScene;
+
+ /**
+ *
+ * 字段名:特约商户授权类型
+ * 变量名:authorization_type
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 特约商户授权类型:
+ * INFORMATION_AUTHORIZATION_TYPE:特约商户信息授权类型,
+ * FUND_AUTHORIZATION_TYPE:特约商户资金授权类型,
+ * INFORMATION_AND_FUND_AUTHORIZATION_TYPE:特约商户信息和资金授权类型
+ * 示例值:INFORMATION_AUTHORIZATION_TYPE
+ *
+ */
+ @SerializedName(value = "authorization_type")
+ private String authorizationType;
+
/**
*
* 字段名:转账明细列表
@@ -235,5 +290,20 @@ public static class TransferDetail implements Serializable {
@SpecEncrypt
@SerializedName(value = "user_name")
private String userName;
+
+ /**
+ *
+ * 字段名:收款用户身份证
+ * 变量名:user_id_card
+ * 是否必填:否
+ * 类型:string[1, 1024]
+ * 描述:
+ * 收款用户身份证号。该字段需进行加密处理,加密方法详见敏感信息加密说明
+ * 示例值:8609cb22e1774a50a930e414cc71eca06121bcd266335cda230d24a7886a8d9f
+ *
+ */
+ @SpecEncrypt
+ @SerializedName(value = "user_id_card")
+ private String userIdCard;
}
}
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 20bb33d7fd..a5421f5dc9 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
@@ -139,6 +139,9 @@ public void payrollCardTransferBatches() throws WxPayException {
.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 void payrollCardTransferBatches() throws WxPayException {
.transferRemark("2020年4月报销")
.openid("o-MYE42l80oelYMDE34nYD456Xoy")
.userName("张三")
+ .userIdCard("8609cb22e1774a50a930e414cc71eca06121bcd266335cda230d24a7886a8d9f")
.build()
))
.build();
From b259206bcbb6a4293e4df16a47c9cde5b388efea Mon Sep 17 00:00:00 2001
From: cbxbj <56364140+cbxbj@users.noreply.github.com>
Date: Mon, 9 Feb 2026 14:19:02 +0800
Subject: [PATCH 103/189] =?UTF-8?q?:art:=20#3876=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=E8=BF=94=E5=9B=9E=E9=87=8C=E7=9A=84?=
=?UTF-8?q?=20ContentValue=20=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E4=BA=86=E9=83=A8=E5=88=86=E5=AE=98=E6=96=B9=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: cbxbj
-
-
From b89ff6a5af2d482109f1136f12d385bfb63937ae Mon Sep 17 00:00:00 2001
From: wuKong
+
+
+ * 字段名:商户订单号
+ * 是否必填:是
+ * 描述:
+ * 投诉单关联的商户订单号
+ *
+ */
+ @SerializedName("out_trade_no")
+ private String outTradeNo;
+
+ /**
+ *
+ * 字段名:投诉时间
+ * 是否必填:是
+ * 描述:投诉时间,遵循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年05月20日13点29分35秒
+ * 示例值:2015-05-20T13:29:35.120+08:00
+ *
+ */
+ @SerializedName("complaint_time")
+ private String complaintTime;
+
+ /**
+ *
+ * 字段名:订单金额
+ * 是否必填:是
+ * 描述:
+ * 订单金额,单位(分)
+ *
+ */
+ @SerializedName("amount")
+ private Integer amount;
+
+ /**
+ *
+ * 字段名:投诉人联系方式
+ * 是否必填:否
+ * 投诉人联系方式。该字段已做加密处理,具体解密方法详见敏感信息加密说明。
+ *
+ */
+ @SerializedName("payer_phone")
+ @SpecEncrypt
+ private String payerPhone;
+
+ /**
+ *
+ * 字段名:投诉详情
+ * 是否必填:是
+ * 投诉的具体描述
+ *
+ */
+ @SerializedName("complaint_detail")
+ private String complaintDetail;
+
+ /**
+ *
+ * 字段名:投诉单状态
+ * 是否必填:是
+ * 标识当前投诉单所处的处理阶段,具体状态如下所示:
+ * PENDING:待处理
+ * PROCESSING:处理中
+ * PROCESSED:已处理完成
+ *
+ */
+ @SerializedName("complaint_state")
+ private String complaintState;
+
+ /**
+ *
+ * 字段名:微信订单号
+ * 是否必填:是
+ * 描述:
+ * 投诉单关联的微信订单号
+ *
+ */
+ @SerializedName("transaction_id")
+ private String transactionId;
+
+ /**
+ *
+ * 字段名:商户处理状态
+ * 是否必填:是
+ * 描述:
+ * 触发本次投诉通知回调的具体动作类型,枚举如下:
+ * 常规通知:
+ * 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:退款到账
+ *
+ */
+ @SerializedName("complaint_handle_state")
+ private String complaintHandleState;
}
}
From 5ff4114b6c045b50925642b738f84761bb182500 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 28 Feb 2026 16:53:17 +0800
Subject: [PATCH 106/189] =?UTF-8?q?:art:=20#3886=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E5=AE=A2=E6=9C=8D=E6=B6=88?=
=?UTF-8?q?=E6=81=AF=E6=96=B0=E5=A2=9E=E8=A7=86=E9=A2=91=E5=8F=B7=E6=B6=88?=
=?UTF-8?q?=E6=81=AF=E7=B1=BB=E5=9E=8B=EF=BC=88channels=EF=BC=89=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/bean/kf/WxCpKfMsgListResp.java | 1 +
.../cp/bean/kf/msg/WxCpKfChannelsMsg.java | 52 +++++++++++++++++++
2 files changed, 53 insertions(+)
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/msg/WxCpKfChannelsMsg.java
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfMsgListResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfMsgListResp.java
index f8f3275c46..a165c1c4cd 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfMsgListResp.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfMsgListResp.java
@@ -67,6 +67,7 @@ public static class WxCpKfMsgItem {
private WxCpKfChannelsShopProductMsg channelsShopProduct;
@SerializedName("channels_shop_order")
private WxCpKfChannelsShopOrderMsg channelsShopOrder;
+ private WxCpKfChannelsMsg channels;
}
/**
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/msg/WxCpKfChannelsMsg.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/msg/WxCpKfChannelsMsg.java
new file mode 100644
index 0000000000..db23c222f5
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/msg/WxCpKfChannelsMsg.java
@@ -0,0 +1,52 @@
+package me.chanjar.weixin.cp.bean.kf.msg;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 视频号消息
+ *
+ * @author liuzhao created on 2024/2/25
+ */
+@NoArgsConstructor
+@Data
+public class WxCpKfChannelsMsg {
+
+ /**
+ * 视频号名称
+ */
+ @SerializedName("nickname")
+ private String nickname;
+
+ /**
+ * 视频/直播标题
+ */
+ @SerializedName("title")
+ private String title;
+
+ /**
+ * 视频/直播描述
+ */
+ @SerializedName("desc")
+ private String desc;
+
+ /**
+ * 封面图片url
+ */
+ @SerializedName("cover_url")
+ private String coverUrl;
+
+ /**
+ * 视频/直播链接
+ */
+ @SerializedName("url")
+ private String url;
+
+ /**
+ * 视频号账号名称
+ */
+ @SerializedName("find_username")
+ private String findUsername;
+
+}
From 3233384b17433de9c265cda32b1f6dcdbb449bf2 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 28 Feb 2026 16:55:43 +0800
Subject: [PATCH 107/189] =?UTF-8?q?:art:=20#3880=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=8DMemChange?=
=?UTF-8?q?List=E7=BE=A4=E6=88=90=E5=91=98=E5=8F=98=E6=9B=B4ID=E8=A7=A3?=
=?UTF-8?q?=E6=9E=90=E4=B8=BA=E7=A9=BA=E5=AD=97=E7=AC=A6=E4=B8=B2=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
---
.../util/xml/XStreamCDataListConverter.java | 54 +++++++++++++++++++
.../cp/bean/message/WxCpXmlMessage.java | 3 +-
.../cp/bean/message/WxCpXmlMessageTest.java | 48 +++++++++++++++++
3 files changed, 104 insertions(+), 1 deletion(-)
create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java
new file mode 100644
index 0000000000..0b55a9c037
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java
@@ -0,0 +1,54 @@
+package me.chanjar.weixin.common.util.xml;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * 兼容两种格式的字符串列表转换器:
+ *
+ *
+ * 解析结果统一为逗号分隔的字符串。
+ */
+public class XStreamCDataListConverter implements Converter {
+
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ if (source != null) {
+ writer.setValue("");
+ }
+ }
+
+ @Override
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ if (reader.hasMoreChildren()) {
+ // 新格式:含有
* 查询订单.
@@ -1164,6 +1171,16 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
*/
WxPayPartnerRefundNotifyV3Result parsePartnerRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
+ /**
+ * 解析合作伙伴订阅通知
+ *
+ * @param notifyData 通知数据
+ * @param header 通知头部数据
+ * @return 合作伙伴订阅通知
+ * @throws WxPayException the wx pay exception
+ */
+ PartnerSubscribeNotifyResult parsePartnerSubscribeNotify(String notifyData, SignatureHeader header) throws WxPayException;
+
/**
* 解析扫码支付回调通知
* 详见https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
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 36987f637d..6868cb644f 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
@@ -143,6 +143,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final MiPayService miPayService = new MiPayServiceImpl(this);
+ @Getter
+ private final MerchantLimitationService merchantLimitationService = new MerchantLimitationServiceImpl(this);
+
protected Map
+ * 激活与更新服务卡片
+ *
+ * 详情请见: 激活与更新服务卡片
+ * 接口url格式: POST https://api.weixin.qq.com/wxa/setusernotify?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param request 请求参数
+ * @throws WxErrorException .
+ */
+ void setUserNotify(WxMaServiceNotifyRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 更新服务卡片扩展信息
+ *
+ * 详情请见: 更新服务卡片扩展信息
+ * 接口url格式: POST https://api.weixin.qq.com/wxa/setusernotifyext?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param request 请求参数
+ * @throws WxErrorException .
+ */
+ void setUserNotifyExt(WxMaServiceNotifyExtRequest request) throws WxErrorException;
+
+ /**
+ *
+ * 查询服务卡片状态
+ *
+ * 详情请见: 查询服务卡片状态
+ * 接口url格式: POST https://api.weixin.qq.com/wxa/getusernotify?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param request 请求参数
+ * @return 服务卡片状态
+ * @throws WxErrorException .
+ */
+ WxMaGetUserNotifyResult getUserNotify(WxMaGetUserNotifyRequest request) throws WxErrorException;
+
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
index a7db154a68..edf4d5ba10 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
@@ -2,6 +2,10 @@
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
+import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyRequest;
+import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyResult;
+import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyExtRequest;
+import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.subscribemsg.CategoryData;
@@ -89,4 +93,32 @@ public void sendSubscribeMsg(WxMaSubscribeMessage subscribeMessage) throws WxErr
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
}
+
+ @Override
+ public void setUserNotify(WxMaServiceNotifyRequest request) throws WxErrorException {
+ String responseContent = this.service.post(SERVICE_NOTIFY_SET_URL, request.toJson());
+ JsonObject jsonObject = GsonParser.parse(responseContent);
+ if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+ }
+ }
+
+ @Override
+ public void setUserNotifyExt(WxMaServiceNotifyExtRequest request) throws WxErrorException {
+ String responseContent = this.service.post(SERVICE_NOTIFY_SET_EXT_URL, request.toJson());
+ JsonObject jsonObject = GsonParser.parse(responseContent);
+ if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+ throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+ }
+ }
+
+ @Override
+ public WxMaGetUserNotifyResult getUserNotify(WxMaGetUserNotifyRequest request) throws WxErrorException {
+ String responseContent = this.service.post(SERVICE_NOTIFY_GET_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(responseContent, WxMaGetUserNotifyResult.class);
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGetUserNotifyRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGetUserNotifyRequest.java
new file mode 100644
index 0000000000..abc7518e02
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGetUserNotifyRequest.java
@@ -0,0 +1,66 @@
+package cn.binarywang.wx.miniapp.bean;
+
+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;
+
+/**
+ * 查询服务卡片状态请求.
+ *
+ *
+ * 参数:openid
+ * 是否必填:是
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 动态更新令牌.
+ *
+ * 参数:notify_code
+ * 是否必填:是
+ *
+ */
+ @SerializedName("notify_code")
+ private String notifyCode;
+
+ /**
+ * 卡片ID.
+ *
+ * 参数:notify_type
+ * 是否必填:是
+ *
+ */
+ @SerializedName("notify_type")
+ private Integer notifyType;
+
+ /**
+ * 转为 JSON 字符串.
+ *
+ * @return JSON 字符串
+ */
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGetUserNotifyResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGetUserNotifyResult.java
new file mode 100644
index 0000000000..0090eb19b4
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGetUserNotifyResult.java
@@ -0,0 +1,60 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 查询服务卡片状态响应.
+ *
+ *
+ * 参数:openid
+ * 是否必填:是
+ * 描述:用户身份标识符。
+ * 当使用微信支付订单号作为 code 时,需要与实际支付用户一致;
+ * 当通过前端获取 code 时,需要与点击 button 的用户一致。
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 卡片ID.
+ *
+ * 参数:notify_type
+ * 是否必填:是
+ * 描述:卡片ID。
+ *
+ */
+ @SerializedName("notify_type")
+ private Integer notifyType;
+
+ /**
+ * 动态更新令牌.
+ *
+ * 参数:notify_code
+ * 是否必填:是
+ * 描述:动态更新令牌。
+ *
+ */
+ @SerializedName("notify_code")
+ private String notifyCode;
+
+ /**
+ * 扩展信息.
+ *
+ * 参数:ext_json
+ * 是否必填:是
+ * 描述:扩展信息,不同卡片的定义不同。
+ *
+ */
+ @SerializedName("ext_json")
+ private String extJson;
+
+ /**
+ * 转为 JSON 字符串.
+ *
+ * @return JSON 字符串
+ */
+ public String toJson() {
+ return WxMaGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaServiceNotifyRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaServiceNotifyRequest.java
new file mode 100644
index 0000000000..e15e0782f9
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaServiceNotifyRequest.java
@@ -0,0 +1,93 @@
+package cn.binarywang.wx.miniapp.bean;
+
+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;
+
+/**
+ * 激活与更新服务卡片请求.
+ *
+ *
+ * 参数:openid
+ * 是否必填:是
+ * 描述:用户身份标识符。
+ * 当使用微信支付订单号作为 code 时,需要与实际支付用户一致;
+ * 当通过前端获取 code 时,需要与点击 button 的用户一致。
+ *
+ */
+ @SerializedName("openid")
+ private String openid;
+
+ /**
+ * 卡片ID.
+ *
+ * 参数:notify_type
+ * 是否必填:是
+ * 描述:卡片ID。
+ *
+ */
+ @SerializedName("notify_type")
+ private Integer notifyType;
+
+ /**
+ * 动态更新令牌.
+ *
+ * 参数:notify_code
+ * 是否必填:是
+ * 描述:动态更新令牌。
+ *
+ */
+ @SerializedName("notify_code")
+ private String notifyCode;
+
+ /**
+ * 卡片状态与状态相关字段.
+ *
+ * 参数:content_json
+ * 是否必填:是
+ * 描述:卡片状态与状态相关字段,不同卡片的定义不同。
+ *
+ */
+ @SerializedName("content_json")
+ private String contentJson;
+
+ /**
+ * 微信支付订单号验证字段(可选).
+ *
+ * 参数:check_json
+ * 是否必填:否
+ * 描述:微信支付订单号验证字段。当将微信支付订单号作为 notify_code 时,在激活时需要传入。
+ *
+ */
+ @SerializedName("check_json")
+ private String checkJson;
+
+ /**
+ * 转为 JSON 字符串.
+ *
+ * @return JSON 字符串
+ */
+ 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 86fa58ac6c..815d47c623 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
@@ -358,6 +358,15 @@ public interface Subscribe {
/** 发送订阅消息 */
String SUBSCRIBE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
+
+ /** 激活与更新服务卡片 */
+ String SERVICE_NOTIFY_SET_URL = "https://api.weixin.qq.com/wxa/setusernotify";
+
+ /** 更新服务卡片扩展信息 */
+ String SERVICE_NOTIFY_SET_EXT_URL = "https://api.weixin.qq.com/wxa/setusernotifyext";
+
+ /** 查询服务卡片状态 */
+ String SERVICE_NOTIFY_GET_URL = "https://api.weixin.qq.com/wxa/getusernotify";
}
public interface User {
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImplTest.java
index 10993e5651..c910d121d1 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImplTest.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImplTest.java
@@ -1,6 +1,11 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
+import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyRequest;
+import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyResult;
+import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyExtRequest;
+import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import me.chanjar.weixin.common.bean.subscribemsg.CategoryData;
import me.chanjar.weixin.common.bean.subscribemsg.PubTemplateKeyword;
@@ -10,12 +15,16 @@
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
+import org.testng.Assert;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
* 测试类.
@@ -71,4 +80,60 @@ public void testSendSubscribeMsg() throws WxErrorException {
// TODO 待完善补充
this.wxService.getSubscribeService().sendSubscribeMsg(WxMaSubscribeMessage.builder().build());
}
+
+ @Test
+ public void testSetUserNotify() throws WxErrorException {
+ WxMaService service = mock(WxMaService.class);
+ when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}");
+
+ WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(service);
+ WxMaServiceNotifyRequest request = WxMaServiceNotifyRequest.builder()
+ .openid("test_openid")
+ .notifyType(1)
+ .notifyCode("test_notify_code")
+ .contentJson("{}")
+ .build();
+ subscribeService.setUserNotify(request);
+ }
+
+ @Test
+ public void testSetUserNotifyExt() throws WxErrorException {
+ WxMaService service = mock(WxMaService.class);
+ when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}");
+
+ WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(service);
+ WxMaServiceNotifyExtRequest request = WxMaServiceNotifyExtRequest.builder()
+ .openid("test_openid")
+ .notifyType(1)
+ .notifyCode("test_notify_code")
+ .extJson("{}")
+ .build();
+ subscribeService.setUserNotifyExt(request);
+ }
+
+ @Test
+ public void testGetUserNotify() throws WxErrorException {
+ WxMaService service = mock(WxMaService.class);
+ when(service.post(anyString(), anyString())).thenReturn(
+ "{\"errcode\":0,\"errmsg\":\"ok\","
+ + "\"notify_info\":{"
+ + "\"notify_type\":1,"
+ + "\"content_json\":\"{\\\"status\\\":1}\","
+ + "\"code_state\":0,"
+ + "\"code_expire_time\":1700000000"
+ + "}}");
+
+ WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(service);
+ WxMaGetUserNotifyRequest request = WxMaGetUserNotifyRequest.builder()
+ .openid("test_openid")
+ .notifyCode("test_notify_code")
+ .notifyType(1)
+ .build();
+ WxMaGetUserNotifyResult result = subscribeService.getUserNotify(request);
+ Assert.assertNotNull(result);
+ Assert.assertNotNull(result.getNotifyInfo());
+ Assert.assertEquals(result.getNotifyInfo().getNotifyType().intValue(), 1);
+ Assert.assertEquals(result.getNotifyInfo().getCodeState().intValue(), 0);
+ Assert.assertEquals(result.getNotifyInfo().getCodeExpireTime().longValue(), 1700000000L);
+ }
}
From bcb3110bd7adb76bc4f2c25ba9890b9473ccb5d6 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 11 May 2026 20:29:31 +0800
Subject: [PATCH 161/189] =?UTF-8?q?:new:=20#3969=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=E5=8A=A0=E5=AF=86?=
=?UTF-8?q?=E7=BD=91=E7=BB=9C=E9=80=9A=E9=81=93=E6=9C=8D=E5=8A=A1=E7=AB=AF?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E5=B9=B6=E4=BF=AE=E5=A4=8D=20HMAC?=
=?UTF-8?q?=20=E7=AD=BE=E5=90=8D=E4=B8=8E=E9=94=99=E8=AF=AF=E5=A4=84?=
=?UTF-8?q?=E7=90=86=20Bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/impl/WxMaInternetServiceImpl.java | 5 +-
.../internet/WxMaInternetUserKeyInfo.java | 2 +-
.../wx/miniapp/util/crypt/WxMaCryptUtils.java | 99 +++++++++++++++++
.../util/crypt/WxMaCryptUtilsTest.java | 100 ++++++++++++++++++
4 files changed, 203 insertions(+), 3 deletions(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java
index 7da44ddaba..91d11795f3 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java
@@ -9,6 +9,7 @@
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
+import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -26,7 +27,7 @@ public class WxMaInternetServiceImpl implements WxMaInternetService {
private String sha256(String data, String sessionKey) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
- SecretKeySpec secret_key = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+ SecretKeySpec secret_key = new SecretKeySpec(Base64.decodeBase64(sessionKey), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
@@ -57,7 +58,7 @@ public WxMaInternetResponse getUserEncryptKey(String openid, String sessionKey)
private WxMaInternetResponse getWxMaInternetResponse(String url) throws WxErrorException {
String responseContent = this.wxMaService.post(url, "");
WxMaInternetResponse response = WxMaGsonBuilder.create().fromJson(responseContent, WxMaInternetResponse.class);
- if (response.getErrcode() == -1) {
+ if (response.getErrcode() != null && response.getErrcode() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
return response;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/internet/WxMaInternetUserKeyInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/internet/WxMaInternetUserKeyInfo.java
index 01bcfbce0b..305d8687e0 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/internet/WxMaInternetUserKeyInfo.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/internet/WxMaInternetUserKeyInfo.java
@@ -44,7 +44,7 @@ public class WxMaInternetUserKeyInfo implements Serializable {
private Long expireIn;
/**
- * 加密iv
+ * 加密iv(Hex 编码,通常为 32 位十六进制字符,解码后为 16 字节,用于 AES-128-CBC)
*/
private String iv;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
index 2343634bfc..252297fdcd 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
@@ -84,4 +84,103 @@ public static String decryptAnotherWay(String sessionKey, String encryptedData,
}
}
+ /**
+ * 使用用户加密 key 对数据进行 AES-128-CBC 解密(用于小程序加密网络通道).
+ *
+ *
+ * 参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/user-encryptkey.html
+ * encryptKey 来自 getUserEncryptKey 接口返回的 encrypt_key 字段(Base64 编码,解码后须为 16 字节)
+ * hexIv 来自 getUserEncryptKey 接口返回的 iv 字段(Hex 编码,须为 32 位十六进制字符,解码后为 16 字节)
+ *
+ *
+ * @param encryptKey 用户加密 key(Base64 编码,解码后须为 16 字节)
+ * @param hexIv 加密 iv(Hex 编码,须为 32 位十六进制字符)
+ * @param encryptedData 加密数据(Base64 编码)
+ * @return 解密后的字符串
+ * @throws IllegalArgumentException 如果 encryptKey 解码后不为 16 字节,或 hexIv 格式非法/解码后不为 16 字节
+ */
+ public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) {
+ byte[] keyBytes = Base64.decodeBase64(encryptKey);
+ if (keyBytes.length != 16) {
+ throw new IllegalArgumentException(
+ "encryptKey 解码后必须为 16 字节(AES-128),实际为 " + keyBytes.length + " 字节");
+ }
+ byte[] ivBytes = hexToBytes(hexIv);
+ if (ivBytes.length != 16) {
+ throw new IllegalArgumentException(
+ "hexIv 解码后必须为 16 字节(AES-128-CBC),实际为 " + ivBytes.length + " 字节(需 32 位 Hex 字符串)");
+ }
+ byte[] dataBytes = Base64.decodeBase64(encryptedData);
+ try {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(keyBytes, "AES"),
+ new IvParameterSpec(ivBytes));
+ return new String(cipher.doFinal(dataBytes), UTF_8);
+ } catch (Exception e) {
+ throw new WxRuntimeException("AES解密失败!", e);
+ }
+ }
+
+ /**
+ * 使用用户加密 key 对数据进行 AES-128-CBC 加密(用于小程序加密网络通道).
+ *
+ *
+ * 参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/user-encryptkey.html
+ * encryptKey 来自 getUserEncryptKey 接口返回的 encrypt_key 字段(Base64 编码,解码后须为 16 字节)
+ * hexIv 来自 getUserEncryptKey 接口返回的 iv 字段(Hex 编码,须为 32 位十六进制字符,解码后为 16 字节)
+ *
+ *
+ * @param encryptKey 用户加密 key(Base64 编码,解码后须为 16 字节)
+ * @param hexIv 加密 iv(Hex 编码,须为 32 位十六进制字符)
+ * @param data 待加密的明文字符串
+ * @return 加密后的数据(Base64 编码)
+ * @throws IllegalArgumentException 如果 encryptKey 解码后不为 16 字节,或 hexIv 格式非法/解码后不为 16 字节
+ */
+ public static String encryptWithEncryptKey(String encryptKey, String hexIv, String data) {
+ byte[] keyBytes = Base64.decodeBase64(encryptKey);
+ if (keyBytes.length != 16) {
+ throw new IllegalArgumentException(
+ "encryptKey 解码后必须为 16 字节(AES-128),实际为 " + keyBytes.length + " 字节");
+ }
+ byte[] ivBytes = hexToBytes(hexIv);
+ if (ivBytes.length != 16) {
+ throw new IllegalArgumentException(
+ "hexIv 解码后必须为 16 字节(AES-128-CBC),实际为 " + ivBytes.length + " 字节(需 32 位 Hex 字符串)");
+ }
+ try {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE,
+ new SecretKeySpec(keyBytes, "AES"),
+ new IvParameterSpec(ivBytes));
+ return Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8)));
+ } catch (Exception e) {
+ throw new WxRuntimeException("AES加密失败!", e);
+ }
+ }
+
+ /**
+ * 将 Hex 字符串转换为字节数组.
+ *
+ * @param hex Hex 字符串(长度必须为偶数,只包含 0-9 和 a-f/A-F 字符)
+ * @return 字节数组
+ * @throws IllegalArgumentException 如果输入不是合法的 Hex 字符串
+ */
+ private static byte[] hexToBytes(String hex) {
+ if (hex == null || hex.length() % 2 != 0) {
+ throw new IllegalArgumentException("无效的十六进制字符串格式:长度必须为偶数");
+ }
+ int len = hex.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ int high = Character.digit(hex.charAt(i), 16);
+ int low = Character.digit(hex.charAt(i + 1), 16);
+ if (high == -1 || low == -1) {
+ throw new IllegalArgumentException("无效的十六进制字符串格式:包含非法字符 '" + hex.charAt(high == -1 ? i : i + 1) + "'");
+ }
+ data[i / 2] = (byte) ((high << 4) + low);
+ }
+ return data;
+ }
+
}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java
index 76b4e96743..742fa7d440 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java
@@ -4,6 +4,7 @@
import org.testng.annotations.*;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
*
@@ -14,6 +15,11 @@
* @author Binary Wang
*/
public class WxMaCryptUtilsTest {
+ // 模拟来自 getUserEncryptKey 接口返回的 encrypt_key(Base64,解码后 16 字节)
+ // 和 iv(Hex,32 位十六进制字符,解码后 16 字节,AES-128-CBC 要求)
+ private static final String ENCRYPT_KEY = "VI6BpyrK9XH4i4AIGe86tg==";
+ private static final String HEX_IV = "6003f73ec441c3866003f73ec441c386";
+
@Test
public void testDecrypt() {
String sessionKey = "7MG7jbTToVVRWRXVA885rg==";
@@ -32,4 +38,98 @@ public void testDecryptAnotherWay() {
assertThat(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr))
.isEqualTo(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr));
}
+
+ /**
+ * 测试使用用户加密 key(来自小程序加密网络通道)进行加密和解密的对称性.
+ * encrypt_key 为 Base64 编码的 16 字节 AES-128 密钥,iv 为 Hex 编码的 16 字节初始向量。
+ */
+ @Test
+ public void testEncryptAndDecryptWithEncryptKey() {
+ String plainText = "{\"userId\":\"12345\",\"amount\":100}";
+
+ String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
+ assertThat(encrypted).isNotNull().isNotEmpty();
+
+ String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted);
+ assertThat(decrypted).isEqualTo(plainText);
+ }
+
+ /**
+ * 测试加密网络通道的加解密对称性(不同明文).
+ */
+ @Test
+ public void testEncryptDecryptSymmetryWithEncryptKey() {
+ String plainText = "hello miniprogram";
+
+ String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
+ String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted);
+ assertThat(decrypted).isEqualTo(plainText);
+ }
+
+ /**
+ * 测试 hexIv 为奇数长度时,应抛出 IllegalArgumentException.
+ */
+ @Test
+ public void testEncryptWithEncryptKeyInvalidHexIvOddLength() {
+ assertThatThrownBy(() -> WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, "abc", "data"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("长度必须为偶数");
+ }
+
+ /**
+ * 测试 hexIv 包含非十六进制字符时,应抛出 IllegalArgumentException.
+ */
+ @Test
+ public void testEncryptWithEncryptKeyInvalidHexIvNonHexChar() {
+ // 32 位但含非法字符 'z'
+ assertThatThrownBy(() -> WxMaCryptUtils.encryptWithEncryptKey(
+ ENCRYPT_KEY, "6003f73ec441c3866003f73ec441z386", "data"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("非法字符");
+ }
+
+ /**
+ * 测试 hexIv 解码后不足 16 字节(如仅 16 位 hex = 8 字节)时,应抛出 IllegalArgumentException.
+ */
+ @Test
+ public void testEncryptWithEncryptKeyShortHexIv() {
+ // 16 位 hex = 8 字节,不满足 AES-CBC 要求的 16 字节
+ assertThatThrownBy(() -> WxMaCryptUtils.encryptWithEncryptKey(
+ ENCRYPT_KEY, "6003f73ec441c386", "data"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("hexIv 解码后必须为 16 字节");
+ }
+
+ /**
+ * 测试 encryptKey 解码后不足 16 字节时,应抛出 IllegalArgumentException.
+ */
+ @Test
+ public void testEncryptWithEncryptKeyShortKey() {
+ // Base64 编码的 8 字节 key(不符合 AES-128 要求)
+ String shortKey = java.util.Base64.getEncoder().encodeToString(new byte[8]);
+ assertThatThrownBy(() -> WxMaCryptUtils.encryptWithEncryptKey(shortKey, HEX_IV, "data"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("encryptKey 解码后必须为 16 字节");
+ }
+
+ /**
+ * 测试 decryptWithEncryptKey 使用非法 hexIv 时,应抛出 IllegalArgumentException.
+ */
+ @Test
+ public void testDecryptWithEncryptKeyInvalidHexIv() {
+ assertThatThrownBy(() -> WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, "abc", "data"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("长度必须为偶数");
+ }
+
+ /**
+ * 测试 decryptWithEncryptKey encryptKey 长度不合法时,应抛出 IllegalArgumentException.
+ */
+ @Test
+ public void testDecryptWithEncryptKeyShortKey() {
+ String shortKey = java.util.Base64.getEncoder().encodeToString(new byte[8]);
+ assertThatThrownBy(() -> WxMaCryptUtils.decryptWithEncryptKey(shortKey, HEX_IV, "data"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("encryptKey 解码后必须为 16 字节");
+ }
}
From 24703be5831a4404acb7f2d97ab3cea64f6accb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B0=B4=E4=BE=9D=E5=AF=92?=
*/
@SerializedName("use_insured")
- private final Integer useInsured = WxMaConstants.OrderAddInsured.INSURED_PROGRAM;
+ @Builder.Default
+ private Integer useInsured = WxMaConstants.OrderAddInsured.INSURED_PROGRAM;
/**
* 保价金额
@@ -41,6 +42,6 @@ public class WxMaExpressOrderInsured implements Serializable {
*/
@SerializedName("insured_value")
@Builder.Default
- private final Integer insuredValue = WxMaConstants.OrderAddInsured.DEFAULT_INSURED_VALUE;
+ private Integer insuredValue = WxMaConstants.OrderAddInsured.DEFAULT_INSURED_VALUE;
}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderInsuredTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderInsuredTest.java
new file mode 100644
index 0000000000..f308398147
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderInsuredTest.java
@@ -0,0 +1,56 @@
+package cn.binarywang.wx.miniapp.bean.express.request;
+
+import cn.binarywang.wx.miniapp.constant.WxMaConstants;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class WxMaExpressOrderInsuredTest {
+
+ @Test
+ public void testDefaultValueWithNoArgsConstructor() {
+ WxMaExpressOrderInsured insured = new WxMaExpressOrderInsured();
+
+ assertEquals(insured.getUseInsured(), Integer.valueOf(WxMaConstants.OrderAddInsured.INSURED_PROGRAM));
+ assertEquals(insured.getInsuredValue(), Integer.valueOf(WxMaConstants.OrderAddInsured.DEFAULT_INSURED_VALUE));
+ }
+
+ @Test
+ public void testCanModifyInsuredConfigBySetter() {
+ WxMaExpressOrderInsured insured = new WxMaExpressOrderInsured();
+ insured.setUseInsured(WxMaConstants.OrderAddInsured.USE_INSURED);
+ insured.setInsuredValue(10000);
+
+ assertEquals(insured.getUseInsured(), Integer.valueOf(WxMaConstants.OrderAddInsured.USE_INSURED));
+ assertEquals(insured.getInsuredValue(), Integer.valueOf(10000));
+ }
+
+ @Test
+ public void testBuilderSupportsCustomInsuredConfig() {
+ WxMaExpressOrderInsured insured = WxMaExpressOrderInsured.builder()
+ .useInsured(WxMaConstants.OrderAddInsured.USE_INSURED)
+ .insuredValue(5000)
+ .build();
+
+ assertEquals(insured.getUseInsured(), Integer.valueOf(WxMaConstants.OrderAddInsured.USE_INSURED));
+ assertEquals(insured.getInsuredValue(), Integer.valueOf(5000));
+
+ String json = WxMaGsonBuilder.create().toJson(insured);
+ assertTrue(json.contains("\"use_insured\":1"));
+ assertTrue(json.contains("\"insured_value\":5000"));
+ }
+
+ @Test
+ public void testBuilderDefaultsWhenNoFieldSet() {
+ WxMaExpressOrderInsured insured = WxMaExpressOrderInsured.builder().build();
+
+ assertEquals(insured.getUseInsured(), Integer.valueOf(WxMaConstants.OrderAddInsured.INSURED_PROGRAM));
+ assertEquals(insured.getInsuredValue(), Integer.valueOf(WxMaConstants.OrderAddInsured.DEFAULT_INSURED_VALUE));
+
+ String json = WxMaGsonBuilder.create().toJson(insured);
+ assertTrue(json.contains("\"use_insured\":0"));
+ assertTrue(json.contains("\"insured_value\":0"));
+ }
+}
From 2dc2da061ed76820864541f47bc977be0cedd9e4 Mon Sep 17 00:00:00 2001
From: Yixuan Xu <109468061+mzl2233@users.noreply.github.com>
Date: Wed, 20 May 2026 10:23:31 +0800
Subject: [PATCH 167/189] =?UTF-8?q?:art:=20#4001=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E4=B8=BA=E6=99=AE=E9=80=9A?=
=?UTF-8?q?=E5=95=86=E6=88=B7=E7=89=88=E9=80=80=E6=AC=BE=E8=AF=B7=E6=B1=82?=
=?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=B1=BB=E8=A1=A5=E9=BD=90=E4=B8=8E=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E5=95=86=E7=89=88=E4=B8=80=E8=87=B4=E7=9A=84=E2=80=9C?=
=?UTF-8?q?=E9=80=80=E6=AC=BE=E8=B5=84=E9=87=91=E6=9D=A5=E6=BA=90/?=
=?UTF-8?q?=E5=87=BA=E8=B5=84=E8=B4=A6=E6=88=B7=E6=98=8E=E7=BB=86=E2=80=9D?=
=?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E4=BB=A5=E6=94=AF=E6=8C=81=E5=9C=A8?=
=?UTF-8?q?=20V3=20=E9=80=80=E6=AC=BE=E7=94=B3=E8=AF=B7=E6=97=B6=E6=8C=87?=
=?UTF-8?q?=E5=AE=9A=E9=80=80=E6=AC=BE=E6=9D=A5=E6=BA=90=E8=B5=84=E9=87=91?=
=?UTF-8?q?=E8=B4=A6=E6=88=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../bean/request/WxPayRefundV3Request.java | 65 +++++++++++++++++++
.../request/WxPayRefundV3RequestTest.java | 56 ++++++++++++++++
2 files changed, 121 insertions(+)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3RequestTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3Request.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3Request.java
index e9f1f3b140..e1bba3d266 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3Request.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3Request.java
@@ -84,6 +84,24 @@ public class WxPayRefundV3Request implements Serializable {
*/
@SerializedName(value = "notify_url")
private String notifyUrl;
+ /**
+ *
* Created by BinaryWang on 2017/6/18.
@@ -38,6 +40,15 @@ public void testHashCode() {
payConfig.hashCode();
}
+ @Test
+ public void testApiHostUrlPath() {
+ payConfig.setApiHostUrl("http://10.0.0.1:3128/");
+ payConfig.setApiHostUrlPath("api-weixin/");
+ assertEquals(payConfig.getApiHostUrl(), "http://10.0.0.1:3128");
+ assertEquals(payConfig.getApiHostUrlPath(), "/api-weixin");
+ assertEquals(payConfig.getApiHostWithPathPrefix(), "http://10.0.0.1:3128/api-weixin");
+ }
+
@Test
public void testInitSSLContext_base64() throws Exception {
payConfig.setMchId("123");
From b79206dc8c43493c65a302f171d17a81366953b8 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 11 May 2026 20:33:43 +0800
Subject: [PATCH 163/189] =?UTF-8?q?:memo:=20=E6=98=8E=E7=A1=AE=E8=AF=B4?=
=?UTF-8?q?=E6=98=8E=20wx-java-cp-multi-spring-boot-starter=20=E4=B8=AD=20?=
=?UTF-8?q?corp-secret=20=E7=9A=84=E9=85=8D=E7=BD=AE=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx-java-cp-multi-solon-plugin/README.md | 85 ++++++++++--------
.../services/AbstractWxCpConfiguration.java | 12 ++-
.../properties/WxCpSingleProperties.java | 26 +++++-
.../README.md | 89 +++++++++++--------
.../services/AbstractWxCpConfiguration.java | 12 ++-
.../cp/properties/WxCpSingleProperties.java | 26 +++++-
6 files changed, 168 insertions(+), 82 deletions(-)
diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/README.md b/solon-plugins/wx-java-cp-multi-solon-plugin/README.md
index 97bcf0723f..8eb467f98f 100644
--- a/solon-plugins/wx-java-cp-multi-solon-plugin/README.md
+++ b/solon-plugins/wx-java-cp-multi-solon-plugin/README.md
@@ -6,6 +6,25 @@
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
+## 关于 corp-secret 的说明
+
+企业微信中不同功能模块对应不同的 `corp-secret`,每种 Secret 只对对应模块的接口具有调用权限:
+
+| Secret 类型 | 获取位置 | 可调用的接口 | 是否需要 agent-id |
+|---|---|---|---|
+| 自建应用 Secret | 应用管理 → 自建应用 → 选择应用 → 查看 Secret | 该应用有权限的接口 | **必填** |
+| 通讯录同步 Secret | 管理工具 → 通讯录同步 → 查看 Secret | 部门/成员增删改查等通讯录接口 | **不填** |
+| 客户联系 Secret | 客户联系 → API → Secret | 客户联系相关接口 | 不填 |
+
+> **常见问题**:
+> - 使用自建应用 Secret + agent-id 可以获取部门列表,但**无法更新部门**(因为写接口需要通讯录同步权限)
+> - 使用通讯录同步 Secret 可以同步部门,但**调用某些需要 agent-id 的应用接口会报错**
+
+如需同时使用多种权限范围,可在 `wx.cp.corps` 下配置多个条目,每个条目使用对应权限的 Secret,通过不同的 `tenantId` 区分后使用。
+
+> **注意**:
+> 当前插件实现会校验同一 `corp-id` 下的 `agent-id` **必须唯一**,并且 **只能有一个条目不填写 `agent-id`**。
+> 如果在同一 `corp-id` 下同时配置多个未填写 `agent-id` 的条目,会因 token/ticket 缓存 key 冲突而在启动时直接抛异常。
## 快速开始
1. 引入依赖
@@ -18,25 +37,21 @@
```
2. 添加配置(app.properties)
```properties
- # 应用 1 配置
- wx.cp.corps.tenantId1.corp-id = @corp-id
- wx.cp.corps.tenantId1.corp-secret = @corp-secret
+ # 自建应用 1 配置(使用自建应用 Secret,需填写 agent-id)
+ wx.cp.corps.app1.corp-id = @corp-id
+ wx.cp.corps.app1.corp-secret = @自建应用的Secret(在"应用管理-自建应用"中查看)
+ wx.cp.corps.app1.agent-id = @自建应用的AgentId
## 选填
- wx.cp.corps.tenantId1.agent-id = @agent-id
- wx.cp.corps.tenantId1.token = @token
- wx.cp.corps.tenantId1.aes-key = @aes-key
- wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
- wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
-
- # 应用 2 配置
- wx.cp.corps.tenantId2.corp-id = @corp-id
- wx.cp.corps.tenantId2.corp-secret = @corp-secret
- ## 选填
- wx.cp.corps.tenantId2.agent-id = @agent-id
- wx.cp.corps.tenantId2.token = @token
- wx.cp.corps.tenantId2.aes-key = @aes-key
- wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
- wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
+ wx.cp.corps.app1.token = @token
+ wx.cp.corps.app1.aes-key = @aes-key
+ wx.cp.corps.app1.msg-audit-priKey = @msg-audit-priKey
+ wx.cp.corps.app1.msg-audit-lib-path = @msg-audit-lib-path
+
+ # 通讯录同步配置(使用通讯录同步 Secret,不需要填写 agent-id)
+ # 此配置用于部门、成员的增删改查等通讯录管理操作
+ wx.cp.corps.contact.corp-id = @corp-id
+ wx.cp.corps.contact.corp-secret = @通讯录同步的Secret(在"管理工具-通讯录同步"中查看)
+ ## agent-id 不填,通讯录同步不需要 agentId
# 公共配置
## ConfigStorage 配置(选填)
@@ -59,8 +74,10 @@
```java
import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServices;
+import me.chanjar.weixin.cp.api.WxCpDepartmentService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpUserService;
+import me.chanjar.weixin.cp.bean.WxCpDepart;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -70,27 +87,21 @@ public class DemoService {
private WxCpMultiServices wxCpMultiServices;
public void test() {
- // 应用 1 的 WxCpService
- WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
- WxCpUserService userService1 = wxCpService1.getUserService();
- userService1.getUserId("xxx");
- // todo ...
-
- // 应用 2 的 WxCpService
- WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
- WxCpUserService userService2 = wxCpService2.getUserService();
- userService2.getUserId("xxx");
+ // 使用自建应用的 WxCpService(对应 corp-secret 为自建应用 Secret)
+ WxCpService appService = wxCpMultiServices.getWxCpService("app1");
+ WxCpUserService userService = appService.getUserService();
+ userService.getUserId("xxx");
// todo ...
- // 应用 3 的 WxCpService
- WxCpService wxCpService3 = wxCpMultiServices.getWxCpService("tenantId3");
- // 判断是否为空
- if (wxCpService3 == null) {
- // todo wxCpService3 为空,请先配置 tenantId3 企业微信应用参数
- return;
- }
- WxCpUserService userService3 = wxCpService3.getUserService();
- userService3.getUserId("xxx");
+ // 使用通讯录同步的 WxCpService(对应 corp-secret 为通讯录同步 Secret)
+ // 通讯录同步 Secret 具有部门/成员增删改查等权限
+ WxCpService contactService = wxCpMultiServices.getWxCpService("contact");
+ WxCpDepartmentService departmentService = contactService.getDepartmentService();
+ // 更新部门示例(WxCpDepart 包含 id、name、parentId 等字段)
+ WxCpDepart depart = new WxCpDepart();
+ depart.setId(100L);
+ depart.setName("新部门名称");
+ departmentService.update(depart);
// todo ...
}
}
diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java
index ada4ac504c..25b4ab3747 100644
--- a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java
+++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java
@@ -15,6 +15,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -37,6 +38,13 @@ protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiPrope
/**
* 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
*
+ *
+ *
+ *
+ *
+ *
+ *
*/
private String corpSecret;
/**
@@ -28,7 +47,10 @@ public class WxCpSingleProperties implements Serializable {
*/
private String token;
/**
- * 微信企业号应用 ID
+ * 微信企业号应用 ID(AgentId)
+ *
+ *
+ *
+ *
+ *
+ *
+ *
*/
private String corpSecret;
/**
@@ -28,7 +47,10 @@ public class WxCpSingleProperties implements Serializable {
*/
private String token;
/**
- * 微信企业号应用 ID
+ * 微信企业号应用 ID(AgentId)
+ *
+ *
+ * 字段名:退款资金来源
+ * 变量名:funds_account
+ * 是否必填:否
+ * 类型:string[1, 32]
+ * 描述:
+ * 若传递此参数则使用对应的资金账户退款,否则默认使用未结算资金退款(仅对老资金流商户适用)
+ * 示例值:
+ * UNSETTLED : 未结算资金
+ * AVAILABLE : 可用余额
+ * UNAVAILABLE : 不可用余额
+ * OPERATION : 运营户
+ * BASIC : 基本账户(含可用余额和不可用余额)
+ *
+ */
+ @SerializedName(value = "funds_account")
+ private String fundsAccount;
/**
*
* 字段名:订单金额
@@ -152,6 +170,53 @@ public static class Amount implements Serializable {
*/
@SerializedName(value = "currency")
private String currency;
+ /**
+ *
+ * 字段名:退款出资账户及金额
+ * 变量名:from
+ * 是否必填:否
+ * 类型:array
+ * 描述:
+ * 退款出资的账户类型及金额信息
+ *
+ */
+ @SerializedName(value = "from")
+ private List
+ * 字段名:出资账户类型
+ * 变量名:account
+ * 是否必填:是
+ * 类型:string[1, 32]
+ * 描述:
+ * 下面枚举值多选一。
+ * 枚举值:
+ * AVAILABLE : 可用余额
+ * UNAVAILABLE : 不可用余额
+ * 示例值:AVAILABLE
+ *
+ */
+ @SerializedName(value = "account")
+ private String account;
+ /**
+ *
+ * 字段名:出资金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 对应账户出资金额
+ * 示例值:444
+ *
+ */
+ @SerializedName(value = "amount")
+ private Integer amount;
}
@Data
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3RequestTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3RequestTest.java
new file mode 100644
index 0000000000..1d7a79f3d4
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3RequestTest.java
@@ -0,0 +1,56 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import org.testng.annotations.Test;
+
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link WxPayRefundV3Request} 单元测试
+ *
+ */
+public class WxPayRefundV3RequestTest {
+
+ @Test
+ public void testFundsAccountSerialization() {
+ WxPayRefundV3Request request = new WxPayRefundV3Request();
+ request.setOutRefundNo("1217752501201407033233368018");
+ request.setFundsAccount("AVAILABLE");
+
+ Gson gson = new Gson();
+ String json = gson.toJson(request);
+ JsonObject jsonObject = gson.fromJson(json, JsonObject.class);
+
+ assertThat(jsonObject.has("funds_account")).isTrue();
+ assertThat(jsonObject.get("funds_account").getAsString()).isEqualTo("AVAILABLE");
+ }
+
+ @Test
+ public void testAmountFromSerialization() {
+ WxPayRefundV3Request.From from = new WxPayRefundV3Request.From();
+ from.setAccount("AVAILABLE");
+ from.setAmount(444);
+
+ WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
+ amount.setRefund(888);
+ amount.setTotal(888);
+ amount.setCurrency("CNY");
+ amount.setFrom(Collections.singletonList(from));
+
+ WxPayRefundV3Request request = new WxPayRefundV3Request();
+ request.setAmount(amount);
+
+ Gson gson = new Gson();
+ String json = gson.toJson(request);
+ JsonObject jsonObject = gson.fromJson(json, JsonObject.class);
+ JsonArray fromJson = jsonObject.getAsJsonObject("amount").getAsJsonArray("from");
+
+ assertThat(fromJson).hasSize(1);
+ assertThat(fromJson.get(0).getAsJsonObject().get("account").getAsString()).isEqualTo("AVAILABLE");
+ assertThat(fromJson.get(0).getAsJsonObject().get("amount").getAsInt()).isEqualTo(444);
+ }
+}
From 93017a5ae0cc629a42c59be43c0d11429855e4c0 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 20 May 2026 21:38:13 +0800
Subject: [PATCH 168/189] =?UTF-8?q?:art:=20#4011=20=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E5=A2=9E=E5=8A=A0=E6=99=BA?=
=?UTF-8?q?=E8=83=BD=E6=9C=BA=E5=99=A8=E4=BA=BA=20API=20=E6=A8=A1=E5=BC=8F?=
=?UTF-8?q?=20JSON=20=E5=9B=9E=E8=B0=83=E6=B6=88=E6=81=AF=E7=B1=BB?=
=?UTF-8?q?=E5=92=8C=E7=9B=B8=E5=BA=94=E7=9A=84=E8=A7=A3=E6=9E=90=E8=B0=83?=
=?UTF-8?q?=E7=94=A8=E6=96=B9=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
weixin-java-cp/INTELLIGENT_ROBOT.md | 12 +-
.../cp/api/WxCpIntelligentRobotService.java | 10 +-
.../impl/WxCpIntelligentRobotServiceImpl.java | 7 +-
.../WxCpIntelligentRobotMessage.java | 196 ++++++++++++++++++
.../WxCpIntelligentRobotServiceImplTest.java | 13 +-
.../WxCpIntelligentRobotMessageTest.java | 76 +++++++
6 files changed, 310 insertions(+), 4 deletions(-)
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotMessage.java
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotMessageTest.java
diff --git a/weixin-java-cp/INTELLIGENT_ROBOT.md b/weixin-java-cp/INTELLIGENT_ROBOT.md
index dcd90e1a1a..18dd0c677f 100644
--- a/weixin-java-cp/INTELLIGENT_ROBOT.md
+++ b/weixin-java-cp/INTELLIGENT_ROBOT.md
@@ -109,6 +109,16 @@ String fromUser = message.getFromUserName(); // 发送用户
// ...
```
+对于智能机器人 API 模式的 JSON 回调消息,可使用 `WxCpIntelligentRobotMessage` 解析:
+
+```java
+WxCpIntelligentRobotMessage callbackMessage =
+ robotService.parseCallbackMessage(jsonBody);
+String botId = callbackMessage.getAiBotId();
+String userId = callbackMessage.getFrom().getUserid();
+String msgType = callbackMessage.getMsgType();
+```
+
### 删除智能机器人
```java
@@ -146,4 +156,4 @@ robotService.deleteRobot(robotId);
1. 需要确保企业微信应用具有智能机器人相关权限
2. 智能机器人功能可能需要特定的企业微信版本支持
3. 会话ID可以用于保持对话的连续性,提升用户体验
-4. 机器人状态: 0表示停用,1表示启用
\ No newline at end of file
+4. 机器人状态: 0表示停用,1表示启用
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
index bc5f3f1915..58f4373ceb 100644
--- 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
@@ -74,4 +74,12 @@ public interface WxCpIntelligentRobotService {
*/
WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotSendMessageRequest request) throws WxErrorException;
-}
\ No newline at end of file
+ /**
+ * 解析智能机器人 API 模式回调消息.
+ *
+ * @param callbackMessageJson 回调消息JSON
+ * @return 解析后的回调消息对象
+ */
+ WxCpIntelligentRobotMessage parseCallbackMessage(String callbackMessageJson);
+
+}
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
index 8a12fa4ff4..aba1ee85c4 100644
--- 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
@@ -67,4 +67,9 @@ public WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotS
return WxCpIntelligentRobotSendMessageResponse.fromJson(responseText);
}
-}
\ No newline at end of file
+ @Override
+ public WxCpIntelligentRobotMessage parseCallbackMessage(String callbackMessageJson) {
+ return WxCpIntelligentRobotMessage.fromJson(callbackMessageJson);
+ }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotMessage.java
new file mode 100644
index 0000000000..d485b59d69
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotMessage.java
@@ -0,0 +1,196 @@
+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;
+import java.util.List;
+
+/**
+ * 企业微信智能机器人回调消息.
+ *
+ *
- * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_7.shtml
- *
- */
-@Data
-@NoArgsConstructor
-public class CombineTransactionsNotifyResult implements Serializable {
-
- private static final long serialVersionUID = -4710926828683593250L;
- /**
- * 源数据
- */
- private NotifyResponse rawData;
-
- /**
- * 解密后的数据
- */
- private CombineTransactionsResult result;
-
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java
deleted file mode 100644
index 3f285285ae..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java
+++ /dev/null
@@ -1,459 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 合单支付API
- *
- * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
- *
- */
-@Data
-@NoArgsConstructor
-public class CombineTransactionsRequest implements Serializable {
- private static final long serialVersionUID = -1242741645939606441L;
- /**
- *
- * 字段名:合单商户appid
- * 变量名:combine_appid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 合单发起方的appid。
- * 示例值:wxd678efh567hg6787
- *
- */
- @SerializedName(value = "combine_appid")
- private String combineAppid;
-
- /**
- *
- * 字段名:合单商户号
- * 变量名:combine_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 合单发起方商户号。
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "combine_mchid")
- private String combineMchid;
-
- /**
- *
- * 字段名:合单商户订单号
- * 变量名:combine_out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
- * 示例值:P20150806125346
- *
- */
- @SerializedName(value = "combine_out_trade_no")
- private String combineOutTradeNo;
-
- /**
- *
- * 字段名:+场景信息
- * 变量名:scene_info
- * 是否必填:否
- * 类型:object
- * 描述:支付场景信息描述
- *
- */
- @SerializedName(value = "scene_info")
- private SceneInfo sceneInfo;
-
- /**
- *
- * 字段名:+子单信息
- * 变量名:sub_orders
- * 是否必填:是
- * 类型:array
- * 描述:
- * 最多支持子单条数:50
- *
- *
- */
- @SerializedName(value = "sub_orders")
- private List
- * 字段名:+支付者
- * 变量名:combine_payer_info
- * 是否必填:否(JSAPI必填)
- * 类型:object
- * 描述:支付者信息
- *
- */
- @SerializedName(value = "combine_payer_info")
- private CombinePayerInfo combinePayerInfo;
-
- /**
- *
- * 字段名:交易起始时间
- * 变量名:time_start
- * 是否必填:否
- * 类型:string(14)
- * 描述:
- * 订单生成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
- * 示例值:2019-12-31T15:59:60+08:00
- *
- */
- @SerializedName(value = "time_start")
- private String timeStart;
-
- /**
- *
- * 字段名:交易结束时间
- * 变量名:time_expire
- * 是否必填:否
- * 类型:string(14)
- * 描述:
- * 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
- * 示例值:2019-12-31T15:59:60+08:00
- *
- */
- @SerializedName(value = "time_expire")
- private String timeExpire;
-
- /**
- *
- * 字段名:通知地址
- * 变量名:notify_url
- * 是否必填:是
- * 类型:string(256)
- * 描述:
- * 接收微信支付异步通知回调地址,通知url必须为直接可访问的URL,不能携带参数。
- * 格式: URL
- * 示例值:https://yourapp.com/notify
- *
- */
- @SerializedName(value = "notify_url")
- private String notifyUrl;
-
-
- @Data
- @NoArgsConstructor
- public static class SceneInfo implements Serializable {
- /**
- *
- * 字段名:商户端设备号
- * 变量名:device_id
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * 终端设备号(门店号或收银设备ID)。
- * 特殊规则:长度最小7个字节
- * 示例值:POS1:1
- *
- */
- @SerializedName(value = "device_id")
- private String deviceId;
-
- /**
- *
- * 字段名:用户终端IP
- * 变量名:payer_client_ip
- * 是否必填:是
- * 类型:string(45)
- * 描述:
- * 用户端实际ip
- * 格式: ip(ipv4+ipv6)
- * 示例值:14.17.22.32
- *
- */
- @SerializedName(value = "payer_client_ip")
- private String payerClientIp;
-
- /**
- *
- * 字段名:H5场景信息
- * 变量名:h5_info
- * 是否必填:否(H5支付必填)
- * 类型:object
- * 描述:
- * H5场景信息
- *
- */
- @SerializedName(value = "h5_info")
- private H5Info h5Info;
- }
-
- @Data
- @NoArgsConstructor
- public static class SubOrders implements Serializable {
- /**
- *
- * 字段名:子单商户号
- * 变量名:mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 子单发起方商户号,必须与发起方appid有绑定关系。
- * 示例值:1900000109
- * 此处一般填写服务商商户号
- *
- */
- @SerializedName(value = "mchid")
- private String mchid;
-
- /**
- *
- * 字段名:附加信息
- * 变量名:attach
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
- * 示例值:深圳分店
- *
- */
- @SerializedName(value = "attach")
- private String attach;
-
- /**
- *
- * 字段名:+订单金额
- * 变量名:amount
- * 是否必填:是
- * 类型:object
- * 描述:
- *
- */
- @SerializedName(value = "amount")
- private Amount amount;
-
- /**
- *
- * 字段名:子单商户订单号
- * 变量名:out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
- * 特殊规则:最小字符长度为6
- * 示例值:20150806125346
- *
- */
- @SerializedName(value = "out_trade_no")
- private String outTradeNo;
-
- /**
- *
- * 字段名:二级商户号
- * 变量名:sub_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 二级商户商户号,由微信支付生成并下发。
- * 注意:仅适用于电商平台 服务商
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "sub_mchid")
- private String subMchid;
-
- /**
- *
- * 字段名:商品描述
- * 变量名:description
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 商品简单描述。需传入应用市场上的APP名字-实际商品名称,例如:天天爱消除-游戏充值。
- * 示例值:腾讯充值中心-QQ会员充值
- *
- */
- @SerializedName(value = "description")
- private String description;
-
- /**
- *
- * 字段名:+结算信息
- * 变量名:settle_info
- * 是否必填:否
- * 类型:Object
- * 描述:结算信息
- *
- */
- @SerializedName(value = "settle_info")
- private SettleInfo settleInfo;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class CombinePayerInfo implements Serializable {
- /**
- *
- * 字段名:用户标识
- * 变量名:openid
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
- * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
- *
- */
- @SerializedName(value = "openid")
- private String openid;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class Amount implements Serializable {
- /**
- *
- * 字段名:标价金额
- * 变量名:total_amount
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 子单金额,单位为分。
- * 示例值:100
- *
- */
- @SerializedName(value = "total_amount")
- private Integer totalAmount;
-
- /**
- *
- * 字段名:标价币种
- * 变量名:currency
- * 是否必填:是
- * 类型:string(8)
- * 描述:
- * 符合ISO 4217标准的三位字母代码,人民币:CNY。
- * 示例值:CNY
- *
- */
- @SerializedName(value = "currency")
- private String currency;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class SettleInfo implements Serializable {
- /**
- *
- * 字段名:是否指定分账
- * 变量名:profit_sharing
- * 是否必填:否
- * 类型:bool
- * 描述:
- * 是否分账,与外层profit_sharing同时存在时,以本字段为准。
- * true:是
- * false:否
- * 示例值:true
- *
- */
- @SerializedName(value = "profit_sharing")
- private Boolean profitSharing;
-
- /**
- *
- * 字段名:补差金额
- * 变量名:subsidy_amount
- * 是否必填:否
- * 类型:int64
- * 描述:
- * SettleInfo.profit_sharing为true时,该金额才生效。
- * 示例值:10
- *
- */
- @SerializedName(value = "subsidy_amount")
- private Integer subsidyAmount;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class H5Info implements Serializable {
-
- /**
- *
- * 字段名:场景类型
- * 变量名:type
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 场景类型,枚举值:
- * iOS:IOS移动应用;
- * Android:安卓移动应用;
- * Wap:WAP网站应用;
- * 示例值:iOS
- *
- */
- @SerializedName(value = "type")
- private String type;
-
- /**
- *
- * 字段名:应用名称
- * 变量名:app_name
- * 是否必填:否
- * 类型:string(64)
- * 描述:
- * 应用名称
- * 示例值:王者荣耀
- *
- */
- @SerializedName(value = "app_name")
- private String appName;
-
- /**
- *
- * 字段名:网站URL
- * 变量名:app_url
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * 网站URL
- * 示例值:https://pay.qq.com
- *
- */
- @SerializedName(value = "app_url")
- private String appUrl;
-
- /**
- *
- * 字段名:iOS平台BundleID
- * 变量名:bundle_id
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * iOS平台BundleID
- * 示例值:com.tencent.wzryiOS
- *
- */
- @SerializedName(value = "bundle_id")
- private String bundleId;
-
- /**
- *
- * 字段名:Android平台PackageName
- * 变量名:package_name
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * Android平台PackageName
- * 示例值:com.tencent.tmgp.sgame
- *
- */
- @SerializedName(value = "package_name")
- private String packageName;
-
- }
-
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java
deleted file mode 100644
index 1b929ed96f..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java
+++ /dev/null
@@ -1,353 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-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/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
- *
- */
-@Data
-@NoArgsConstructor
-public class CombineTransactionsResult implements Serializable {
-
- /**
- *
- * 字段名:合单商户appid
- * 变量名:combine_appid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 合单发起方的appid。(即电商平台appid)
- * 示例值:wxd678efh567hg6787
- *
- */
- @SerializedName(value = "combine_appid")
- private String combineAppid;
-
- /**
- *
- * 字段名:合单商户号
- * 变量名:combine_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 合单发起方商户号。(即电商平台mchid)
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "combine_mchid")
- private String combineMchid;
-
- /**
- *
- * 字段名:合单商户订单号
- * 变量名:combine_out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
- * 示例值:P20150806125346
- *
- */
- @SerializedName(value = "combine_out_trade_no")
- private String combineOutTradeNo;
-
- /**
- *
- * 字段名:+场景信息
- * 变量名:scene_info
- * 是否必填:否
- * 类型:object
- * 描述:支付场景信息描述
- *
- */
- @SerializedName(value = "scene_info")
- private SceneInfo sceneInfo;
-
- /**
- *
- * 字段名:+子单信息
- * 变量名:sub_orders
- * 是否必填:是
- * 类型:array
- * 描述:
- * 最多支持子单条数:50
- *
- *
- */
- @SerializedName(value = "sub_orders")
- private List
- * 字段名:+支付者
- * 变量名:combine_payer_info
- * 是否必填:否
- * 类型:object
- * 描述:示例值:见请求示例
- *
- */
- @SerializedName(value = "combine_payer_info")
- private CombinePayerInfo combinePayerInfo;
-
- @Data
- @NoArgsConstructor
- public static class SubOrders implements Serializable {
- /**
- *
- * 字段名:子单商户号
- * 变量名:mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 子单发起方商户号,必须与发起方Appid有绑定关系。(即电商平台mchid)
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "mchid")
- private String mchid;
-
- /**
- *
- * 字段名:交易类型
- * 变量名:trade_type
- * 是否必填:是
- * 类型:string (16)
- * 描述:
- * 枚举值:
- * NATIVE:扫码支付
- * JSAPI:公众号支付
- * APP:APP支付
- * MWEB:H5支付
- * 示例值: JSAPI
- *
- */
- @SerializedName(value = "trade_type")
- private String tradeType;
-
- /**
- *
- * 字段名:交易状态
- * 变量名:trade_state
- * 是否必填:是
- * 类型:string (32)
- * 描述:
- * 枚举值:
- * SUCCESS:支付成功
- * REFUND:转入退款
- * NOTPAY:未支付
- * CLOSED:已关闭
- * USERPAYING:用户支付中
- * PAYERROR:支付失败(其他原因,如银行返回失败)
- * 示例值: SUCCESS
- *
- */
- @SerializedName(value = "trade_state")
- private String tradeState;
-
- /**
- *
- * 字段名:付款银行
- * 变量名:bank_type
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * 银行类型,采用字符串类型的银行标识。
- * 示例值:CMC
- *
- */
- @SerializedName(value = "bank_type")
- private String bankType;
-
- /**
- *
- * 字段名:附加信息
- * 变量名:attach
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
- * 示例值:深圳分店
- *
- */
- @SerializedName(value = "attach")
- private String attach;
-
- /**
- *
- * 字段名:支付完成时间
- * 变量名:success_time
- * 是否必填:是
- * 类型:string(16)
- * 描述:
- * 订单支付时间,遵循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秒。
- * 示例值:2015-05-20T13:29:35.120+08:00
- *
- */
- @SerializedName(value = "success_time")
- private String successTime;
-
- /**
- *
- * 字段名:微信订单号
- * 变量名:transaction_id
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 微信支付订单号。
- * 示例值: 1009660380201506130728806387
- *
- */
- @SerializedName(value = "transaction_id")
- private String transactionId;
-
- /**
- *
- * 字段名:子单商户订单号
- * 变量名:out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
- * 特殊规则:最小字符长度为6
- * 示例值:20150806125346
- *
- */
- @SerializedName(value = "out_trade_no")
- private String outTradeNo;
-
- /**
- *
- * 字段名:二级商户号
- * 变量名:sub_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 二级商户商户号,由微信支付生成并下发。
- * 注意:仅适用于电商平台 服务商
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "sub_mchid")
- private String subMchid;
-
- /**
- *
- * 字段名:+订单金额
- * 变量名:amount
- * 是否必填:是
- * 类型:object
- * 描述:订单金额信息
- *
- */
- @SerializedName(value = "amount")
- private Amount amount;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class SceneInfo implements Serializable {
- /**
- *
- * 字段名:商户端设备号
- * 变量名:device_id
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * 终端设备号(门店号或收银设备ID)。
- * 特殊规则:长度最小7个字节
- * 示例值:POS1:1
- *
- */
- @SerializedName(value = "device_id")
- private String deviceId;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class CombinePayerInfo implements Serializable {
- /**
- *
- * 字段名:用户标识
- * 变量名:openid
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
- * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
- *
- */
- @SerializedName(value = "openid")
- private String openid;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class Amount implements Serializable {
- /**
- *
- * 字段名:标价金额
- * 变量名:total_amount
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 子单金额,单位为分。
- * 示例值:100
- *
- */
- @SerializedName(value = "total_amount")
- private Integer totalAmount;
-
- /**
- *
- * 字段名:标价币种
- * 变量名:currency
- * 是否必填:是
- * 类型:string(8)
- * 描述:
- * 符合ISO 4217标准的三位字母代码,人民币:CNY。
- * 示例值:CNY
- *
- */
- @SerializedName(value = "currency")
- private String currency;
-
- /**
- *
- * 字段名:现金支付金额
- * 变量名:payer_amount
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 订单现金支付金额。
- * 示例值:10
- *
- */
- @SerializedName(value = "payer_amount")
- private Integer payerAmount;
-
- /**
- *
- * 字段名:现金支付币种
- * 变量名:payer_currency
- * 是否必填:是
- * 类型:string(8)
- * 描述:
- * 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY。
- * 示例值: CNY
- *
- */
- @SerializedName(value = "payer_currency")
- private String payerCurrency;
- }
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsCloseRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsCloseRequest.java
deleted file mode 100644
index c09c1aede6..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsCloseRequest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-
-/**
- * 关闭普通订单请求
- *
- * @author f00lish
- * created on 2020/12/09
- */
-@Data
-@NoArgsConstructor
-public class PartnerTransactionsCloseRequest implements Serializable {
-
- private static final long serialVersionUID = -7602636370950088329L;
-
- /**
- *
- * 字段名:服务商户号
- * 变量名:sp_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 服务商户号,由微信支付生成并下发
- * 示例值:1230000109
- *
- */
- @SerializedName(value = "sp_mchid")
- private String spMchid;
-
- /**
- *
- * 字段名:二级商户号
- * 变量名:sub_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 二级商户的商户号,有微信支付生成并下发。
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "sub_mchid")
- private String subMchid;
-
- /**
- *
- * 字段名:商户订单号
- * 变量名:out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
- * 特殊规则:最小字符长度为6
- * 示例值:1217752501201407033233368018
- *
- */
- private transient String outTradeNo;
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsNotifyResult.java
deleted file mode 100644
index 03d9535fa8..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsNotifyResult.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-
-/**
- * 普通支付 通知结果
- *
- * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_11.shtml
- *
- */
-@Data
-@NoArgsConstructor
-public class PartnerTransactionsNotifyResult implements Serializable {
- private static final long serialVersionUID = -6602962275015706689L;
- /**
- * 源数据
- */
- private NotifyResponse rawData;
-
- /**
- * 解密后的数据
- */
- private PartnerTransactionsResult result;
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsQueryRequest.java
deleted file mode 100644
index 2b90e432bb..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsQueryRequest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-
-@Data
-@NoArgsConstructor
-public class PartnerTransactionsQueryRequest implements Serializable {
-
-
- /**
- *
- * 字段名:服务商户号
- * 变量名:sp_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 服务商户号,由微信支付生成并下发
- * 示例值:1230000109
- *
- */
- @SerializedName(value = "sp_mchid")
- private String spMchid;
-
- /**
- *
- * 字段名:二级商户号
- * 变量名:sub_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 二级商户的商户号,有微信支付生成并下发。
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "sub_mchid")
- private String subMchid;
-
- /**
- *
- * 字段名:微信支付订单号
- * 变量名:transaction_id
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 微信支付系统生成的订单号
- * 示例值:1217752501201407033233368018
- *
- */
- @SerializedName(value = "transaction_id")
- private String transactionId;
- /**
- *
- * 字段名:商户订单号
- * 变量名:out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
- * 特殊规则:最小字符长度为6
- * 示例值:1217752501201407033233368018
- *
- */
- @SerializedName(value = "out_trade_no")
- private String outTradeNo;
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java
deleted file mode 100644
index efe0978247..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java
+++ /dev/null
@@ -1,646 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * 普通支付(电商收付通)API
- *
- * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e_transactions.shtml
- *
- *
- * @author cloudX
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PartnerTransactionsRequest implements Serializable {
- private static final long serialVersionUID = -1550405819444680465L;
-
- /**
- *
- * 字段名:服务商公众号ID
- * 变量名:sp_appid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 服务商申请的公众号或移动应用appid
- * 示例值:wx8888888888888888
- *
- */
- @SerializedName(value = "sp_appid")
- private String spAppid;
- /**
- *
- * 字段名:服务商户号
- * 变量名:sp_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 服务商户号,由微信支付生成并下发
- * 示例值:1230000109
- *
- */
- @SerializedName(value = "sp_mchid")
- private String spMchid;
- /**
- *
- * 字段名:子商户公众号ID
- * 变量名:sub_appid
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * 子商户申请的公众号或移动应用appid。
- * 示例值:wxd678efh567hg6999
- *
- */
- @SerializedName(value = "sub_appid")
- private String subAppid;
- /**
- *
- * 字段名:二级商户号
- * 变量名:sub_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 二级商户的商户号,有微信支付生成并下发。
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "sub_mchid")
- private String subMchid;
- /**
- *
- * 字段名:商品描述
- * 变量名:description
- * 是否必填:是
- * 类型:string(127)
- * 描述:
- * 商品描述
- * 示例值:Image形象店-深圳腾大-QQ公仔
- *
- */
- @SerializedName(value = "description")
- private String description;
- /**
- *
- * 字段名:商户订单号
- * 变量名:out_trade_no
- * 是否必填:是
- * 类型:string(127)
- * 描述:
- * 商户系统内部订单号, 只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】
- * 特殊规则:最小字符长度为6
- * 示例值:1217752501201407033233368018
- *
- */
- @SerializedName(value = "out_trade_no")
- private String outTradeNo;
- /**
- *
- * 字段名:交易结束时间
- * 变量名:time_expire
- * 是否必填:否
- * 类型:string(14)
- * 描述:
- * 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
- * 示例值:2019-12-31T15:59:60+08:00
- *
- */
- @SerializedName(value = "time_expire")
- private String timeExpire;
- /**
- *
- * 字段名:附加数据
- * 变量名:attach
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
- * 示例值:自定义数据
- *
- */
- @SerializedName(value = "attach")
- private String attach;
- /**
- *
- * 字段名:通知地址
- * 变量名:notify_url
- * 是否必填:是
- * 类型:string(127)
- * 描述:
- * 通知URL必须为直接可访问的URL,不允许携带查询串。
- * 示例值:https://www.weixin.qq.com/wxpay/pay.php
- *
- */
- @SerializedName(value = "notify_url")
- private String notifyUrl;
- /**
- *
- * 字段名:订单优惠标记
- * 变量名:goods_tag
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * 订单优惠标记
- * 示例值:WXG
- *
- */
- @SerializedName(value = "goods_tag")
- private String goodsTag;
- /**
- *
- * 字段名:电子发票入口开放标识
- * 变量名:support_fapiao
- * 是否必填:否
- * 类型:boolean
- * 描述:传入true时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效。
- *
- */
- @SerializedName(value = "support_fapiao")
- private Boolean supportFapiao;
- /**
- *
- * 字段名:+结算信息
- * 变量名:settle_info
- * 是否必填:否
- * 类型:Object
- * 描述:结算信息
- *
- */
- @SerializedName(value = "settle_info")
- private SettleInfo settleInfo;
- /**
- *
- * 字段名:订单金额
- * 变量名:amount
- * 是否必填:是
- * 类型:object
- * 描述:
- * 订单金额信息
- *
- */
- @SerializedName(value = "amount")
- private Amount amount;
- /**
- *
- * 字段名:优惠功能
- * 变量名:detail
- * 是否必填:否
- * 类型:object
- * 描述:
- * 优惠功能
- *
- */
- @SerializedName(value = "detail")
- private Discount detail;
- /**
- *
- * 字段名:支付者
- * 变量名:payer
- * 是否必填:是(仅JSAPI支付必传)
- * 类型:object
- * 描述:
- * 支付者信息
- *
- */
- @SerializedName(value = "payer")
- private Payer payer;
- /**
- *
- * 字段名:场景信息
- * 变量名:scene_info
- * 是否必填:是(仅H5支付必传)
- * 类型:object
- * 描述:
- * 支付场景描述
- *
- */
- @SerializedName(value = "scene_info")
- private SceneInfo sceneInfo;
-
- @Data
- @NoArgsConstructor
- public static class Discount implements Serializable {
- private static final long serialVersionUID = 1090134053810201492L;
-
- /**
- *
- * 字段名:订单原价
- * 变量名:cost_price
- * 是否必填:否
- * 类型:int64
- * 描述:
- * 1、商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的交易金额。
- * 2、当订单原价与支付金额不相等,则不享受优惠。
- * 3、该字段主要用于防止同一张小票分多次支付,以享受多次优惠的情况,正常支付订单不必上传此参数。
- * 示例值:608800
- *
- */
- @SerializedName(value = "cost_price")
- private Integer costPrice;
- /**
- *
- * 字段名:商品小票ID
- * 变量名:invoice_id
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * 商品小票ID
- * 示例值:微信123
- *
- */
- @SerializedName(value = "invoice_id")
- private String invoiceId;
- /**
- *
- * 字段名:单品列表
- * 变量名:goods_detail
- * 是否必填:否
- * 类型:array
- * 描述:
- * 单品列表信息
- * 条目个数限制:【1,undefined】
- *
- */
- @SerializedName(value = "goods_detail")
- private List
- * 字段名:总金额
- * 变量名:total
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 订单总金额,单位为分。
- * 示例值:100
- *
- */
- @SerializedName(value = "total")
- private Integer total;
- /**
- *
- * 字段名:币类型
- * 变量名:currency
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * CNY:人民币,境内商户号仅支持人民币。
- * 示例值:CNY
- *
- */
- @SerializedName(value = "currency")
- private String currency;
- }
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public static class Payer implements Serializable {
- private static final long serialVersionUID = -3946401119476159971L;
-
- /**
- *
- * 字段名:用户服务标识
- * 变量名:sp_openid
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 用户在服务商appid下的唯一标识。
- * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
- *
- */
- @SerializedName(value = "sp_openid")
- private String spOpenid;
- /**
- *
- * 字段名:用户子标识
- * 变量名:sub_openid
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * 用户在子商户appid下的唯一标识。
- * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
- *
- */
- @SerializedName(value = "sub_openid")
- private String subOpenid;
- }
-
- @Data
- @NoArgsConstructor
- public static class SettleInfo implements Serializable {
- private static final long serialVersionUID = 4438958789491671746L;
-
- /**
- *
- * 字段名:是否指定分账
- * 变量名:profit_sharing
- * 是否必填:否
- * 类型:bool
- * 描述:
- * 是否分账,与外层profit_sharing同时存在时,以本字段为准。
- * true:是
- * false:否
- * 示例值:true
- *
- */
- @SerializedName(value = "profit_sharing")
- private Boolean profitSharing;
- /**
- *
- * 字段名:补差金额
- * 变量名:subsidy_amount
- * 是否必填:否
- * 类型:int64
- * 描述:
- * SettleInfo.profit_sharing为true时,该金额才生效。
- * 注意:单笔订单最高补差金额为5000元
- * 示例值:10
- *
- */
- @SerializedName(value = "subsidy_amount")
- private BigDecimal subsidyAmount;
- }
-
- @Data
- @NoArgsConstructor
- public static class GoodsDetail implements Serializable {
- private static final long serialVersionUID = -2574001236925022932L;
-
- /**
- *
- * 字段名:商户侧商品编码
- * 变量名:merchant_goods_id
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 由半角的大小写字母、数字、中划线、下划线中的一种或几种组成。
- * 示例值:商品编码
- *
- */
- @SerializedName(value = "merchant_goods_id")
- private String merchantGoodsId;
- /**
- *
- * 字段名:微信侧商品编码
- * 变量名:wechatpay_goods_id
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * 微信支付定义的统一商品编号(没有可不传)
- * 示例值:1001
- *
- */
- @SerializedName(value = "wechatpay_goods_id")
- private String wechatpayGoodsId;
- /**
- *
- * 字段名:商品名称
- * 变量名:goods_name
- * 是否必填:否
- * 类型:string(256)
- * 描述:
- * 商品的实际名称
- * 示例值:iPhoneX 256G
- *
- */
- @SerializedName(value = "goods_name")
- private String goodsName;
- /**
- *
- * 字段名:商品数量
- * 变量名:quantity
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 用户购买的数量
- * 示例值:1
- *
- */
- @SerializedName(value = "quantity")
- private Integer quantity;
- /**
- *
- * 字段名:商品单价
- * 变量名:unit_price
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 商品单价,单位为分
- * 示例值:828800
- *
- */
- @SerializedName(value = "unit_price")
- private Integer unitPrice;
- }
-
- @Data
- @NoArgsConstructor
- public static class SceneInfo implements Serializable {
- private static final long serialVersionUID = 4678263124015070957L;
-
- /**
- *
- * 字段名:商户端设备号
- * 变量名:device_id
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * 终端设备号(门店号或收银设备ID)。
- * 特殊规则:长度最小7个字节
- * 示例值:POS1:1
- *
- */
- @SerializedName(value = "device_id")
- private String deviceId;
- /**
- *
- * 字段名:用户终端IP
- * 变量名:payer_client_ip
- * 是否必填:是
- * 类型:string(45)
- * 描述:
- * 用户端实际ip
- * 格式: ip(ipv4+ipv6)
- * 示例值:14.17.22.32
- *
- */
- @SerializedName(value = "payer_client_ip")
- private String payerClientIp;
- /**
- *
- * 字段名:H5场景信息
- * 变量名:h5_info
- * 是否必填:否(H5支付必填)
- * 类型:object
- * 描述:
- * H5场景信息
- *
- */
- @SerializedName(value = "h5_info")
- private H5Info h5Info;
- /**
- *
- * 字段名:商户门店信息
- * 变量名:store_info
- * 是否必填:否(H5支付必填)
- * 类型:object
- * 描述:
- * 商户门店信息
- *
- */
- @SerializedName(value = "store_info")
- private StoreInfo storeInfo;
- }
-
- @Data
- @NoArgsConstructor
- public static class H5Info implements Serializable {
- private static final long serialVersionUID = -6865738707329486532L;
-
- /**
- *
- * 字段名:场景类型
- * 变量名:type
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 场景类型,枚举值:
- * iOS:IOS移动应用;
- * Android:安卓移动应用;
- * Wap:WAP网站应用;
- * 示例值:iOS
- *
- */
- @SerializedName(value = "type")
- private String type;
- /**
- *
- * 字段名:应用名称
- * 变量名:app_name
- * 是否必填:否
- * 类型:string(64)
- * 描述:
- * 应用名称
- * 示例值:王者荣耀
- *
- */
- @SerializedName(value = "app_name")
- private String appName;
- /**
- *
- * 字段名:网站URL
- * 变量名:app_url
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * 网站URL
- * 示例值:https://pay.qq.com
- *
- */
- @SerializedName(value = "app_url")
- private String appUrl;
- /**
- *
- * 字段名:iOS平台BundleID
- * 变量名:bundle_id
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * iOS平台BundleID
- * 示例值:com.tencent.wzryiOS
- *
- */
- @SerializedName(value = "bundle_id")
- private String bundleId;
- /**
- *
- * 字段名:Android平台PackageName
- * 变量名:package_name
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * Android平台PackageName
- * 示例值:com.tencent.tmgp.sgame
- *
- */
- @SerializedName(value = "package_name")
- private String packageName;
- }
-
- @Data
- @NoArgsConstructor
- public static class StoreInfo implements Serializable {
- private static final long serialVersionUID = -8002411737407580701L;
-
- /**
- *
- * 字段名:门店编号
- * 变量名:id
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * 商户侧门店编号
- * 示例值:0001
- *
- */
- @SerializedName(value = "id")
- private String id;
- /**
- *
- * 字段名:门店名称
- * 变量名:name
- * 是否必填:是
- * 类型:string(256)
- * 描述:
- * 商户侧门店名称
- * 示例值:腾讯大厦分店
- *
- */
- @SerializedName(value = "name")
- private String name;
- /**
- *
- * 字段名:地区编码
- * 变量名:area_code
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 地区编码,详细请见省市区编号对照表(https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter4_1.shtml)。
- * 示例值:440305
- *
- */
- @SerializedName(value = "area_code")
- private String areaCode;
- /**
- *
- * 字段名:详细地址
- * 变量名:address
- * 是否必填:是
- * 类型:string(512)
- * 描述:
- * 详细的商户门店地址
- * 示例值:广东省深圳市南山区科技中一道10000号
- *
- */
- @SerializedName(value = "address")
- private String address;
- }
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsResult.java
deleted file mode 100644
index 2c9086e7f4..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsResult.java
+++ /dev/null
@@ -1,600 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-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/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_5.shtml
- *
- * @author cloudX
- */
-@Data
-@NoArgsConstructor
-public class PartnerTransactionsResult implements Serializable {
- private static final long serialVersionUID = 2371448241965534820L;
-
- /**
- *
- * 字段名:服务商公众号ID
- * 变量名:sp_appid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 服务商申请的公众号或移动应用appid。
- * 示例值:wx8888888888888888
- *
- */
- @SerializedName(value = "sp_appid")
- private String spAppid;
-
- /**
- *
- * 字段名:服务商户号
- * 变量名:sp_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 服务商户号,由微信支付生成并下发
- * 示例值:1230000109
- *
- */
- @SerializedName(value = "sp_mchid")
- private String spMchid;
-
- /**
- *
- * 字段名:二级商户公众号ID
- * 变量名:sub_appid
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * 二级商户申请的公众号或移动应用appid。
- * 示例值:wxd678efh567hg6999
- *
- */
- @SerializedName(value = "sub_appid")
- private String subAppid;
-
- /**
- *
- * 字段名:二级商户号
- * 变量名:sub_mchid
- * 是否必填:是
- * 类型:string(32)
- * 描述:
- * 二级商户的商户号,有微信支付生成并下发。
- * 示例值:1900000109
- *
- */
- @SerializedName(value = "sub_mchid")
- private String subMchid;
-
- /**
- *
- * 字段名:+商户订单号
- * 变量名:out_trade_no
- * 是否必填:是
- * 类型:string(32)
- * 描述:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
- * 特殊规则:最小字符长度为6
- * 示例值:1217752501201407033233368018
- *
- */
- @SerializedName(value = "out_trade_no")
- private String outTradeNo;
-
- /**
- *
- * 字段名:微信支付订单号
- * 变量名:transaction_id
- * 是否必填:否
- * 类型:string(32)
- * 描述:微信支付系统生成的订单号。
- * 示例值:1217752501201407033233368018
- *
- */
- @SerializedName(value = "transaction_id")
- private String transactionId;
-
- /**
- *
- * 字段名:交易类型
- * 变量名:trade_type
- * 是否必填:否
- * 类型:string(16)
- * 描述:交易类型,枚举值:
- * JSAPI:公众号支付
- * NATIVE:扫码支付
- * APP:APP支付
- * MICROPAY:付款码支付
- * MWEB:H5支付
- * FACEPAY:刷脸支付
- *
- * 示例值: MICROPAY
- *
- */
- @SerializedName(value = "trade_type")
- private String tradeType;
-
- /**
- *
- * 字段名:交易状态
- * 变量名:trade_state
- * 是否必填:是
- * 类型:string(32)
- * 描述:交易状态,枚举值:
- * SUCCESS:支付成功
- * REFUND:转入退款
- * NOTPAY:未支付
- * CLOSED:已关闭
- * REVOKED:已撤销(付款码支付)
- * USERPAYING:用户支付中(付款码支付)
- * PAYERROR:支付失败(其他原因,如银行返回失败)
- *
- * 示例值:SUCCESS
- *
- */
- @SerializedName(value = "trade_state")
- private String tradeState;
-
- /**
- *
- * 字段名:交易状态描述
- * 变量名:trade_state_desc
- * 是否必填:是
- * 类型:string(256)
- * 描述:交易状态描述
- * 示例值:支付失败,请重新下单支付
- *
- */
- @SerializedName(value = "trade_state_desc")
- private String tradeStateDesc;
-
- /**
- *
- * 字段名:付款银行
- * 变量名:bank_type
- * 是否必填:否
- * 类型:string(16)
- * 描述:银行类型,采用字符串类型的银行标识。
- * 示例值:CMC
- *
- */
- @SerializedName(value = "bank_type")
- private String bankType;
-
- /**
- *
- * 字段名:附加数据
- * 变量名:attach
- * 是否必填:否
- * 类型:string(128)
- * 描述:附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
- * 示例值:自定义数据
- *
- */
- @SerializedName(value = "attach")
- private String attach;
-
- /**
- *
- * 字段名:支付完成时间
- * 变量名:success_time
- * 是否必填:否
- * 类型:string(64)
- * 描述:支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
- * 示例值:2018-06-08T10:34:56+08:00
- *
- */
- @SerializedName(value = "success_time")
- private String successTime;
-
- /**
- *
- * 字段名:支付者信息
- * 变量名:payer
- * 是否必填:是
- * 类型:object
- * 描述:基础支付支付者信息
- *
- */
- private CombinePayerInfo payer;
-
- /**
- *
- * 字段名:支付者
- * 变量名:combine_payer_info
- * 是否必填:否
- * 类型:object
- * 描述:合单支付支付者信息,示例值:见请求示例
- *
- */
- @SerializedName(value = "combine_payer_info")
- private CombinePayerInfo combinePayerInfo;
-
- /**
- *
- * 字段名:订单金额
- * 变量名:amount
- * 是否必填:是
- * 类型:object
- * 描述:订单金额信息
- *
- */
- @SerializedName(value = "amount")
- private Amount amount;
-
- /**
- *
- * 字段名:场景信息
- * 变量名:scene_info
- * 是否必填:否
- * 类型:object
- * 描述:支付场景信息描述
- *
- */
- @SerializedName(value = "scene_info")
- private SceneInfo sceneInfo;
-
- /**
- *
- * 字段名:优惠功能
- * 变量名:promotion_detail
- * 是否必填:否
- * 类型:array
- * 描述:优惠功能,享受优惠时返回该字段。
- *
- */
- @SerializedName(value = "promotion_detail")
- private List
- * 字段名:商户端设备号
- * 变量名:device_id
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * 终端设备号(门店号或收银设备ID)。
- * 特殊规则:长度最小7个字节
- * 示例值:POS1:1
- *
- */
- @SerializedName(value = "device_id")
- private String deviceId;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class CombinePayerInfo implements Serializable {
- /**
- *
- * 字段名:用户标识
- * 变量名:sp_openid
- * 是否必填:是
- * 类型:string(128)
- * 描述:
- * 用户在服务商appid下的唯一标识。
- * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
- *
- */
- @SerializedName(value = "sp_openid")
- private String spOpenid;
-
-
- /**
- *
- * 字段名:二级商户用户标识
- * 变量名:sub_openid
- * 是否必填:否
- * 类型:string(128)
- * 描述:
- * 用户在二级商户appid下的唯一标识。
- * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
- *
- */
- @SerializedName(value = "sub_openid")
- private String subOpenid;
-
- }
-
- @Data
- @NoArgsConstructor
- public static class Amount implements Serializable {
- /**
- *
- * 字段名:总金额
- * 变量名:total
- * 是否必填:否
- * 类型:int
- * 描述:
- * 订单总金额,单位为分
- * 示例值:100
- *
- */
- @SerializedName(value = "total")
- private Integer total;
-
-
- /**
- *
- * 字段名:用户支付金额
- * 变量名:payer_total
- * 是否必填:否
- * 类型:int
- * 描述:
- * 用户支付金额,单位为分。
- * 示例值:100
- *
- */
- @SerializedName(value = "payer_total")
- private Integer payerTotal;
-
-
- /**
- *
- * 字段名:货币类型
- * 变量名:currency
- * 是否必填:否
- * 类型:string(16)
- * 描述:
- * CNY:人民币,境内商户号仅支持人民币。
- * 示例值:CNY
- *
- */
- @SerializedName(value = "currency")
- private String currency;
-
-
- /**
- *
- * 字段名:用户支付币种
- * 变量名:payer_currency
- * 是否必填:否
- * 类型:string(8)
- * 描述:
- * 用户支付币种
- * 示例值: CNY
- *
- */
- @SerializedName(value = "payer_currency")
- private String payerCurrency;
- }
-
- @Data
- @NoArgsConstructor
- public static class PromotionDetail implements Serializable {
-
- /**
- *
- * 字段名:券ID
- * 变量名:coupon_id
- * 是否必填:是
- * 类型:string(32)
- * 描述: 券ID
- * 示例值:109519
- *
- */
- @SerializedName(value = "coupon_id")
- private String couponId;
-
- /**
- *
- * 字段名:优惠名称
- * 变量名:name
- * 是否必填:否
- * 类型:string(64)
- * 描述: 优惠名称
- * 示例值:单品惠-6
- *
- */
- @SerializedName(value = "name")
- private String name;
- /**
- *
- * 字段名:优惠范围
- * 变量名:scope
- * 是否必填:否
- * 类型:string(32)
- * 描述: 优惠名称
- * 示例值:
- * GLOBAL:全场代金券
- * SINGLE:单品优惠
- * 示例值:GLOBAL
- *
- */
- @SerializedName(value = "scope")
- private String scope;
-
- /**
- *
- * 字段名:优惠类型
- * 变量名:type
- * 是否必填:否
- * 类型:string(32)
- * 描述:
- * CASH:充值
- * NOCASH:预充值
- * 示例值:CASH
- *
- */
- @SerializedName(value = "type")
- private String type;
-
- /**
- *
- * 字段名:优惠券面额
- * 变量名:amount
- * 是否必填:是
- * 类型:int
- * 描述: 优惠券面额
- * 示例值:100
- *
- */
- @SerializedName(value = "amount")
- private Integer amount;
-
- /**
- *
- * 字段名:活动ID
- * 变量名:stock_id
- * 是否必填:否
- * 类型:string(32)
- * 描述:活动ID
- * 示例值:931386
- *
- */
- @SerializedName(value = "stock_id")
- private String stockId;
-
- /**
- *
- * 字段名:微信出资
- * 变量名:wechatpay_contribute
- * 是否必填:否
- * 类型:int
- * 描述:微信出资,单位为分
- * 示例值:0
- *
- */
- @SerializedName(value = "wechatpay_contribute")
- private Integer wechatpayContribute;
-
- /**
- *
- * 字段名:商户出资
- * 变量名:merchant_contribute
- * 是否必填:否
- * 类型:int
- * 描述:商户出资,单位为分
- * 示例值:0
- *
- */
- @SerializedName(value = "merchant_contribute")
- private Integer merchantContribute;
-
- /**
- *
- * 字段名:其他出资
- * 变量名:other_contribute
- * 是否必填:否
- * 类型:int
- * 描述:其他出资,单位为分
- * 示例值:0
- *
- */
- @SerializedName(value = "other_contribute")
- private Integer otherContribute;
-
- /**
- *
- * 字段名:优惠币种
- * 变量名:currency
- * 是否必填:否
- * 类型:String(16)
- * 描述:
- * CNY:人民币,境内商户号仅支持人民币。
- * 示例值:CNY
- *
- */
- @SerializedName(value = "currency")
- private String currency;
-
- /**
- *
- * 字段名:单品列表
- * 变量名:goods_detail
- * 是否必填:否
- * 类型:array
- * 描述:单品列表信息
- *
- */
- @SerializedName(value = "goods_detail")
- private List
- * 字段名:商品编码
- * 变量名:goods_id
- * 是否必填:是
- * 类型:string(32)
- * 描述:商品编码
- * 示例值:M1006
- *
- */
- @SerializedName(value = "goods_id")
- private String goodsId;
-
- /**
- *
- * 字段名:商品数量
- * 变量名:quantity
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 用户购买的数量
- * 示例值:1
- *
- */
- @SerializedName(value = "quantity")
- private Integer quantity;
-
- /**
- *
- * 字段名:商品单价
- * 变量名:unit_price
- * 是否必填:是
- * 类型:int64
- * 描述:
- * 商品单价,单位为分
- * 示例值:100
- *
- */
- @SerializedName(value = "unit_price")
- private Integer unitPrice;
-
- /**
- *
- * 字段名:商品优惠金额
- * 变量名:discount_amount
- * 是否必填:是
- * 类型:int
- * 描述:商品优惠金额
- * 示例值:0
- *
- */
- @SerializedName(value = "discount_amount")
- private Integer discountAmount;
-
- /**
- *
- * 字段名:商品备注
- * 变量名:goods_remark
- * 是否必填:否
- * 类型:string(128)
- * 描述:商品备注信息
- * 示例值:商品备注信息
- *
- */
- @SerializedName(value = "goods_remark")
- private String goodsRemark;
- }
-
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java
deleted file mode 100644
index 498a788c07..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-
-/**
- * 微信通知接口头部信息,需要做签名验证
- * 文档地址: https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-yan-zheng
- *
- * @author cloudX
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class SignatureHeader implements Serializable {
- private static final long serialVersionUID = -6958015499416059949L;
- /**
- * 时间戳
- */
- private String timeStamp;
-
- /**
- * 随机串
- */
- private String nonce;
-
- /**
- * 已签名字符串
- */
- private String signed;
-
- /**
- * 证书序列号
- */
- private String serialNo;
-}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TransactionsResult.java
deleted file mode 100644
index 8e11d859b6..0000000000
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TransactionsResult.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.github.binarywang.wxpay.bean.ecommerce;
-
-import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
-import com.github.binarywang.wxpay.v3.util.SignUtils;
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-import java.io.Serializable;
-import java.security.PrivateKey;
-
-/**
- * 合单支付 JSAPI支付结果响应
- */
-@Data
-@NoArgsConstructor
-public class TransactionsResult implements Serializable {
- private static final long serialVersionUID = 1760592667519950149L;
- /**
- *
- * 字段名:预支付交易会话标识 (APP支付、JSAPI支付 会返回)
- * 变量名:prepay_id
- * 是否必填:是
- * 类型:string(64)
- * 描述:
- * 数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。
- * 示例值:wx201410272009395522657a690389285100
- *
- */
- @SerializedName("prepay_id")
- private String prepayId;
-
- /**
- *
- * 字段名:支付跳转链接 (H5支付 会返回)
- * 变量名:h5_url
- * 是否必填:是
- * 类型:string(512)
- * 描述:
- * 支付跳转链接
- * 示例值:https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx2016121516420242444321ca0631331346&package=1405458241
- *
- */
- @SerializedName("h5_url")
- private String h5Url;
-
- /**
- *
- * 字段名:二维码链接 (NATIVE支付 会返回)
- * 变量名:h5_url
- * 是否必填:是
- * 类型:string(512)
- * 描述:
- * 二维码链接
- * 示例值:weixin://pay.weixin.qq.com/bizpayurl/up?pr=NwY5Mz9&groupid=00
- *
- */
- @SerializedName("code_url")
- private String codeUrl;
-
- @Data
- @Accessors(chain = true)
- public static class JsapiResult implements Serializable {
- private String appId;
- private String timeStamp;
- private String nonceStr;
- /**
- * 由于package为java保留关键字,因此改为packageValue,序列化时会自动转换为package字段名
- */
- @SerializedName("package")
- private String packageValue;
- private String signType;
- private String paySign;
-
- private String getSignStr() {
- return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue);
- }
- }
-
- @Data
- @Accessors(chain = true)
- public static class AppResult implements Serializable {
- private String appid;
- private String partnerid;
- private String prepayid;
- /**
- * 由于package为java保留关键字,因此改为packageValue,序列化时会自动转换为package字段名
- */
- @SerializedName("package")
- private String packageValue;
- private String noncestr;
- private String timestamp;
- private String sign;
-
- private String getSignStr() {
- return String.format("%s\n%s\n%s\n%s\n", appid, timestamp, noncestr, prepayid);
- }
- }
-
- public
+ * 字段名:补差金额
+ * 变量名:subsidy_amount
+ * 是否必填:否
+ * 类型:int64
+ * 描述:
+ * SettleInfo.profit_sharing为true时,该金额才生效。
+ * 注意:单笔订单最高补差金额为5000元
+ * 示例值:10
+ *
+ */
+ @SerializedName(value = "subsidy_amount")
+ private Integer subsidyAmount;
}
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/TradeTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/TradeTypeEnum.java
index 80edf2d99b..460da8f509 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/TradeTypeEnum.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/TradeTypeEnum.java
@@ -29,9 +29,9 @@ public enum TradeTypeEnum {
H5("/v3/pay/transactions/h5", "/v3/combine-transactions/h5", "/v3/pay/partner/transactions/h5");
/**
- * 单独下单url
+ * 直连商户支付url
*/
- private final String partnerUrl;
+ private final String merchantUrl;
/**
* 合并下单url
@@ -39,7 +39,7 @@ public enum TradeTypeEnum {
private final String combineUrl;
/**
- * 服务商下单
+ * 服务商支付url
*/
- private final String basePartnerUrl;
+ private final String partnerUrl;
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessCircleService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessCircleService.java
index 21af39ae16..7fef47ed23 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessCircleService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessCircleService.java
@@ -4,7 +4,7 @@
import com.github.binarywang.wxpay.bean.businesscircle.PaidResult;
import com.github.binarywang.wxpay.bean.businesscircle.PointsNotifyRequest;
import com.github.binarywang.wxpay.bean.businesscircle.RefundResult;
-import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.exception.WxPayException;
/**
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 b630ce1785..5ef94e531d 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
@@ -3,7 +3,15 @@
import com.github.binarywang.wxpay.bean.ecommerce.*;
import com.github.binarywang.wxpay.bean.ecommerce.enums.FundBillTypeEnum;
import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
-import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.bean.notify.CombineNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.WxPayPartnerNotifyV3Result;
+import com.github.binarywang.wxpay.bean.request.*;
+import com.github.binarywang.wxpay.bean.result.CombineQueryResult;
+import com.github.binarywang.wxpay.bean.result.CombineTransactionsResult;
+import com.github.binarywang.wxpay.bean.result.WxPayPartnerOrderQueryV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import java.io.File;
@@ -13,7 +21,7 @@
/**
*
* 电商收付通相关服务类.
- * 接口规则:https://wechatpay-api.gitbook.io/wechatpay-api-v3
+ * 产品介绍
*
*
* @author cloudX
@@ -24,7 +32,7 @@ public interface EcommerceService {
*
* 二级商户进件API
* 接口地址: https://api.mch.weixin.qq.com/v3/ecommerce/applyments/
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_8.shtml
+ * 接口文档
*
*
*
@@ -38,7 +46,7 @@ public interface EcommerceService {
*
* 查询申请状态API
* 请求URL: https://api.mch.weixin.qq.com/v3/ecommerce/applyments/{applyment_id}
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_2.shtml
+ * 接口文档
*
*
* @param applymentId 申请单ID
@@ -51,7 +59,7 @@ public interface EcommerceService {
*
* 查询申请状态API
* 请求URL: https://api.mch.weixin.qq.com/v3/ecommerce/applyments/out-request-no/{out_request_no}
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_2.shtml
+ * 接口文档
*
*
* @param outRequestNo 业务申请编号
@@ -64,21 +72,21 @@ public interface EcommerceService {
*
* 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
* 请求URL:https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+ * 接口文档
*
*
* @param tradeType 支付方式
* @param request 请求对象
- * @return 微信合单支付返回 transactions result
+ * @return 微信合单支付返回 CombineTransactionsResult
* @throws WxPayException the wx pay exception
*/
- TransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException;
+ CombineTransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException;
/**
*
* 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
* 请求URL:https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+ * 接口文档
*
*
* @param
* 合单支付通知回调数据处理
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+ * 接口文档
*
*
* @param notifyData 通知数据
* @param header 通知头部数据,不传则表示不校验头
- * @return 解密后通知数据 combine transactions notify result
+ * @return 解密后通知数据 CombineNotifyResult
* @throws WxPayException the wx pay exception
*/
- CombineTransactionsNotifyResult parseCombineNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+ CombineNotifyResult parseCombineNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
/**
*
* 合单查询订单API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
+ * 接口文档
*
*
- * @param outTradeNo 合单商户订单号
+ * @param combineOutTradeNo 合单商户订单号
* @return 支付订单信息
* @throws WxPayException the wx pay exception
*/
- CombineTransactionsResult queryCombineTransactions(String outTradeNo) throws WxPayException;
+ CombineQueryResult queryCombine(String combineOutTradeNo) throws WxPayException;
+
+ /**
+ *
+ * 合单关闭订单API
+ * 请求URL: https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close
+ * 接口文档
+ *
+ *
+ * @param request 请求对象
+ * @throws WxPayException the wx pay exception
+ */
+ void closeCombine(CombineCloseRequest request) throws WxPayException;
/**
*
* 服务商模式普通支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
* 请求URL:https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/transactions_sl.shtml
+ * 接口文档
*
*
* @param tradeType 支付方式
* @param request 请求对象
- * @return 调起支付需要的参数 transactions result
+ * @return 调起支付需要的参数 WxPayUnifiedOrderV3Result
* @throws WxPayException the wx pay exception
*/
- TransactionsResult partner(TradeTypeEnum tradeType, PartnerTransactionsRequest request) throws WxPayException;
+ WxPayUnifiedOrderV3Result unifiedPartnerOrder(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException;
/**
*
* 服务商模式普通支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
* 请求URL:https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/transactions_sl.shtml
+ * 接口文档
*
*
* @param
* 普通支付通知回调数据处理
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e_transactions.shtml
+ * 接口文档
*
*
* @param notifyData 通知数据
* @param header 通知头部数据,不传则表示不校验头
- * @return 解密后通知数据 partner transactions notify result
+ * @return 解密后通知数据 WxPayPartnerNotifyV3Result
* @throws WxPayException the wx pay exception
*/
- PartnerTransactionsNotifyResult parsePartnerNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+ WxPayPartnerNotifyV3Result parsePartnerNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
/**
*
* 普通查询订单API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_5.shtml
+ * 接口文档
*
*
* @param request 商户订单信息
* @return 支付订单信息
* @throws WxPayException the wx pay exception
*/
- PartnerTransactionsResult queryPartnerTransactions(PartnerTransactionsQueryRequest request) throws WxPayException;
+ WxPayPartnerOrderQueryV3Result queryPartnerOrder(WxPayPartnerOrderQueryV3Request request) throws WxPayException;
/**
*
* 关闭普通订单API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_6.shtml
+ * 接口文档
*
*
- * @param request 关闭普通订单请求
+ * @param request 请求对象
* @throws WxPayException the wx pay exception
- * @return
*/
- String closePartnerTransactions(PartnerTransactionsCloseRequest request) throws WxPayException;
+ void closePartnerOrder(WxPayPartnerOrderCloseV3Request request) throws WxPayException;
/**
*
* 服务商账户实时余额
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+ * 接口文档
*
*
* @param accountType 服务商账户类型
@@ -195,7 +214,7 @@ public interface EcommerceService {
/**
*
* 服务商账户日终余额
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+ * 接口文档
*
*
* @param accountType 服务商账户类型
@@ -208,7 +227,7 @@ public interface EcommerceService {
/**
*
* 二级商户号账户实时余额
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -220,7 +239,7 @@ public interface EcommerceService {
/**
*
* 二级商户号账户实时余额
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_3_11.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -233,7 +252,7 @@ public interface EcommerceService {
/**
*
* 二级商户号账户日终余额
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -246,7 +265,7 @@ public interface EcommerceService {
/**
*
* 请求分账API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_1.shtml
+ * 接口文档
*
*
* @param request 分账请求
@@ -258,7 +277,7 @@ public interface EcommerceService {
/**
*
* 查询分账结果API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_2.shtml
+ * 接口文档
*
*
* @param request 查询分账请求
@@ -270,7 +289,7 @@ public interface EcommerceService {
/**
*
* 查询订单剩余待分金额API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_9.shtml
+ * 接口文档
*
*
* @param request 查询订单剩余待分金额请求
@@ -282,7 +301,7 @@ public interface EcommerceService {
/**
*
* 添加分账接收方API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_7.shtml
+ * 接口文档
*
*
* @param request 添加分账接收方
@@ -294,7 +313,7 @@ public interface EcommerceService {
/**
*
* 删除分账接收方API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_8.shtml
+ * 接口文档
*
*
* @param request 删除分账接收方
@@ -306,7 +325,7 @@ public interface EcommerceService {
/**
*
* 请求分账回退API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_3.shtml
+ * 接口文档
*
*
* @param request 分账回退请求
@@ -318,7 +337,7 @@ public interface EcommerceService {
/**
*
* 查询分账回退API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_3.shtml
+ * 接口文档
*
*
* @param request 查询分账回退请求
@@ -330,7 +349,7 @@ public interface EcommerceService {
/**
*
* 完结分账API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_5.shtml
+ * 接口文档
*
*
* @param request 完结分账请求
@@ -342,7 +361,7 @@ public interface EcommerceService {
/**
*
* 退款申请API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
+ * 接口文档
*
*
* @param request 退款请求
@@ -354,7 +373,7 @@ public interface EcommerceService {
/**
*
* 查询退款API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_2.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -368,7 +387,7 @@ public interface EcommerceService {
/**
*
* 垫付退款回补API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_4.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -382,7 +401,7 @@ public interface EcommerceService {
/**
*
* 查询垫付回补结果API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_5.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -394,7 +413,7 @@ public interface EcommerceService {
/**
*
* 查询退款API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_2.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -407,7 +426,7 @@ public interface EcommerceService {
/**
*
* 退款通知回调数据处理
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_3.shtml
+ * 接口文档
*
*
* @param notifyData 通知数据
@@ -420,7 +439,7 @@ public interface EcommerceService {
/**
*
* 提现状态变更通知回调数据处理
- * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013049135
+ * 接口文档
*
*
* @param notifyData 通知数据
@@ -433,7 +452,7 @@ public interface EcommerceService {
/**
*
* 二级商户账户余额提现API
- * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476652
+ * 接口文档
*
*
* @param request 提现请求
@@ -445,7 +464,7 @@ public interface EcommerceService {
/**
*
* 电商平台提现API
- * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476670
+ * 接口文档
*
*
* @param request 提现请求
@@ -457,7 +476,7 @@ public interface EcommerceService {
/**
*
* 二级商户查询提现状态API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_3.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -470,7 +489,7 @@ public interface EcommerceService {
/**
*
* 电商平台查询提现状态API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_6.shtml
+ * 接口文档
*
*
* @param outRequestNo 商户提现单号
@@ -482,7 +501,7 @@ public interface EcommerceService {
/**
*
* 平台查询预约提现状态(根据微信支付预约提现单号查询)
- * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476674
+ * 接口文档
*
*
* @param withdrawId 微信支付提现单号
@@ -494,7 +513,7 @@ public interface EcommerceService {
/**
*
* 二级商户按日终余额预约提现
- * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013328143
+ * 接口文档
*
*
* @param request 提现请求
@@ -506,7 +525,7 @@ public interface EcommerceService {
/**
*
* 查询二级商户按日终余额预约提现状态
- * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013328163
+ * 接口文档
*
*
* @param subMchid 二级商户号
@@ -519,7 +538,7 @@ public interface EcommerceService {
/**
*
* 修改结算账号API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_4.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号。
@@ -531,7 +550,7 @@ public interface EcommerceService {
/**
*
* 查询结算账户API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_5.shtml
+ * 接口文档
*
*
* @param subMchid 二级商户号。
@@ -543,7 +562,7 @@ public interface EcommerceService {
/**
*
* 请求账单API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+ * 接口文档
*
*
* @param request 请求信息。
@@ -555,7 +574,7 @@ public interface EcommerceService {
/**
*
* 申请资金账单API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/bill/chapter3_2.shtml
+ * 接口文档
*
*
* @param billType 账单类型。
@@ -568,7 +587,7 @@ public interface EcommerceService {
/**
*
* 下载账单API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+ * 接口文档
*
*
* @param url 微信返回的账单地址。
@@ -581,7 +600,7 @@ public interface EcommerceService {
/**
*
* 请求补差API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_1.shtml
+ * 接口文档
*
*
* @param subsidiesCreateRequest 请求补差。
@@ -593,7 +612,7 @@ public interface EcommerceService {
/**
*
* 请求补差回退API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_2.shtml
+ * 接口文档
*
*
* @param subsidiesReturnRequest 请求补差。
@@ -605,7 +624,7 @@ public interface EcommerceService {
/**
*
* 取消补差API
- * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_3.shtml
+ * 接口文档
*
*
* @param subsidiesCancelRequest 请求补差。
@@ -617,7 +636,7 @@ public interface EcommerceService {
/**
*
* 提交注销申请单
- * 文档地址: https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/cancel-applications/create-cancel-application.html
+ * 接口文档
*
*
* @param accountCancelApplicationsRequest 提交注销申请单
@@ -629,7 +648,7 @@ public interface EcommerceService {
/**
*
* 查询注销单状态
- * 文档地址: https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/cancel-applications/get-cancel-application.html
+ * 接口文档
*
*
* @param outApplyNo 注销申请单号
@@ -641,7 +660,7 @@ public interface EcommerceService {
/**
*
* 注销单资料图片上传
- * 文档地址: https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/media/upload-media.html
+ * 接口文档
*
*
* @param imageFile 图片
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MarketingFavorService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MarketingFavorService.java
index ac0ed5212f..47e7035510 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MarketingFavorService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MarketingFavorService.java
@@ -1,6 +1,6 @@
package com.github.binarywang.wxpay.service;
-import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.marketing.*;
import com.github.binarywang.wxpay.exception.WxPayException;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreService.java
index c5c4e06796..0bb9b82af1 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreService.java
@@ -1,6 +1,6 @@
package com.github.binarywang.wxpay.service;
-import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreRequest;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreResult;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreSignPlanService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreSignPlanService.java
index 3e51ebd7f0..f72f004fb8 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreSignPlanService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreSignPlanService.java
@@ -1,6 +1,6 @@
package com.github.binarywang.wxpay.service;
-import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.PartnerUserSignPlanEntity;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreSignPlanRequest;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreSignPlanResult;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java
index 5b4f692033..ee816f1ab3 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java
@@ -1,6 +1,6 @@
package com.github.binarywang.wxpay.service;
-import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult;
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest;
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 a460d5f248..9cf5aba4a4 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
@@ -5,12 +5,13 @@
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.result.enums.GlobalTradeTypeEnum;
+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;
+import com.github.binarywang.wxpay.exception.WxSignTestException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
@@ -1069,6 +1070,16 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
*/
WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String signType) throws WxPayException;
+ /**
+ * 校验通知签名
+ *
+ * @param header 通知头信息
+ * @param data 通知数据
+ * @return true:校验通过 false:校验不通过
+ * @throws WxSignTestException 微信支付签名探测流量异常
+ */
+ boolean verifyNotifySign(SignatureHeader header, String data) throws WxSignTestException;
+
/**
* 解析支付结果v3通知. 直连商户模式
* 详见https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml
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 2574e969d7..943894146c 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,21 +1,17 @@
package com.github.binarywang.wxpay.service.impl;
-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.coupon.*;
+import com.github.binarywang.wxpay.bean.notify.*;
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.GlobalTradeTypeEnum;
+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;
@@ -23,6 +19,7 @@
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;
@@ -32,6 +29,14 @@
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;
@@ -40,15 +45,12 @@
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 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 static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT;
+import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType;
/**
*
@@ -526,7 +528,8 @@ public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String sign
* @param data 通知数据
* @return true:校验通过 false:校验不通过
*/
- private boolean verifyNotifySign(SignatureHeader header, String data) throws WxSignTestException {
+ @Override
+ public boolean verifyNotifySign(SignatureHeader header, String data) throws WxSignTestException {
String wxPaySign = header.getSignature();
if (wxPaySign.startsWith("WECHATPAY/SIGNTEST/")) {
throw new WxSignTestException("微信支付签名探测流量");
@@ -959,7 +962,7 @@ public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType,
request.setSubMchId(this.getConfig().getSubMchId());
}
- String url = this.getPayBaseUrl() + tradeType.getBasePartnerUrl();
+ String url = this.getPayBaseUrl() + tradeType.getPartnerUrl();
String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
}
@@ -976,7 +979,7 @@ public WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUn
request.setNotifyUrl(this.getConfig().getNotifyUrl());
}
- String url = this.getPayBaseUrl() + tradeType.getPartnerUrl();
+ String url = this.getPayBaseUrl() + tradeType.getMerchantUrl();
String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
}
@@ -1360,8 +1363,8 @@ public WxPayMicropayResult micropay(WxPayMicropayRequest request) throws WxPayEx
@Override
public WxPayCodepayResult codepay(WxPayCodepayRequest request) throws WxPayException {
// 判断是否为服务商模式:如果设置了sp_appid或sp_mchid或sub_mchid中的任何一个,则认为是服务商模式
- boolean isPartnerMode = StringUtils.isNotBlank(request.getSpAppid())
- || StringUtils.isNotBlank(request.getSpMchid())
+ boolean isPartnerMode = StringUtils.isNotBlank(request.getSpAppid())
+ || StringUtils.isNotBlank(request.getSpMchid())
|| StringUtils.isNotBlank(request.getSubMchid());
if (isPartnerMode) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessCircleServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessCircleServiceImpl.java
index 49c400538d..8ed8286c9a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessCircleServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessCircleServiceImpl.java
@@ -4,7 +4,7 @@
import com.github.binarywang.wxpay.bean.businesscircle.PaidResult;
import com.github.binarywang.wxpay.bean.businesscircle.PointsNotifyRequest;
import com.github.binarywang.wxpay.bean.businesscircle.RefundResult;
-import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.BusinessCircleService;
import com.github.binarywang.wxpay.service.WxPayService;
@@ -16,7 +16,6 @@
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Objects;
@@ -38,22 +37,9 @@ public void notifyPoints(PointsNotifyRequest request) throws WxPayException {
this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
}
- /**
- * 校验通知签名
- *
- * @param header 通知头信息
- * @param data 通知数据
- * @return true:校验通过 false:校验不通过
- */
- private boolean verifyNotifySign(SignatureHeader header, String data) {
- String beforeSign = String.format("%s\n%s\n%s\n", header.getTimeStamp(), header.getNonce(), data);
- return payService.getConfig().getVerifier().verify(header.getSerialNo(),
- beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
- }
-
@Override
public BusinessCircleNotifyData parseNotifyData(String data, SignatureHeader header) throws WxPayException {
- if (Objects.nonNull(header) && !this.verifyNotifySign(header, data)) {
+ if (Objects.nonNull(header) && !this.payService.verifyNotifySign(header, data)) {
throw new WxPayException("非法请求,头部信息验证失败");
}
return GSON.fromJson(data, BusinessCircleNotifyData.class);
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 171535c992..0f99d428fc 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
@@ -3,7 +3,15 @@
import com.github.binarywang.wxpay.bean.ecommerce.*;
import com.github.binarywang.wxpay.bean.ecommerce.enums.FundBillTypeEnum;
import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
-import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.bean.notify.CombineNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.WxPayPartnerNotifyV3Result;
+import com.github.binarywang.wxpay.bean.request.*;
+import com.github.binarywang.wxpay.bean.result.CombineQueryResult;
+import com.github.binarywang.wxpay.bean.result.CombineTransactionsResult;
+import com.github.binarywang.wxpay.bean.result.WxPayPartnerOrderQueryV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.EcommerceService;
import com.github.binarywang.wxpay.service.WxPayService;
@@ -28,9 +36,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
-import java.text.DateFormat;
import java.util.*;
@RequiredArgsConstructor
@@ -38,10 +44,6 @@ public class EcommerceServiceImpl implements EcommerceService {
private static final Gson GSON = new GsonBuilder().create();
- // https://stackoverflow.com/questions/6873020/gson-date-format
- // gson default date format not match, so custom DateFormat
- // detail DateFormat: FULL,LONG,SHORT,MEDIUM
- private static final Gson GSON_CUSTOM = new GsonBuilder().setDateFormat(DateFormat.FULL, DateFormat.FULL).create();
private final WxPayService payService;
@Override
@@ -68,104 +70,53 @@ public ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo
}
@Override
- public TransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException {
- String url = this.payService.getPayBaseUrl() + tradeType.getCombineUrl();
- String response = this.payService.postV3(url, GSON.toJson(request));
- return GSON.fromJson(response, TransactionsResult.class);
+ public CombineTransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException {
+ return this.payService.combine(tradeType, request);
}
@Override
public
- * 字段名:退款资金来源 - * 变量名:funds_account - * 是否必填:否 - * 类型:string[1, 32] - * 描述: - * 若传递此参数则使用对应的资金账户退款,否则默认使用未结算资金退款(仅对老资金流商户适用) - * 示例值: - * UNSETTLED : 未结算资金 - * AVAILABLE : 可用余额 - * UNAVAILABLE : 不可用余额 - * OPERATION : 运营户 - * BASIC : 基本账户(含可用余额和不可用余额) - *- */ - @SerializedName(value = "funds_account") - private String fundsAccount; } From 4284b0bc9706cced7dac41246edc39c84c7cedd0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:15:03 +0800 Subject: [PATCH 177/189] =?UTF-8?q?:art:=20#4023=20=E3=80=90=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=8F=B7=E3=80=91=E4=BF=AE=E5=A4=8D=20WxAssistantServ?= =?UTF-8?q?iceImpl=20=E6=89=80=E6=9C=89=E6=96=B9=E6=B3=95=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=8F=82=E6=95=B0=E6=9C=AA=E4=BC=A0=E9=80=92=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 --- .../weixin/channel/api/impl/WxAssistantServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java index 55be5abcca..58d6d45269 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java @@ -31,25 +31,25 @@ public class WxAssistantServiceImpl implements WxAssistantService { private final BaseWxChannelServiceImpl, ?> shopService; @Override public WxChannelBaseResponse addWindowProduct(AddWindowProductRequest req) throws WxErrorException { - String resJson = shopService.post(ADD_WINDOW_PRODUCT_URL, "{}"); + String resJson = shopService.post(ADD_WINDOW_PRODUCT_URL, req); return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); } @Override public GetWindowProductResponse getWindowProduct(WindowProductRequest req) throws WxErrorException { - String resJson = shopService.post(GET_WINDOW_PRODUCT_URL, "{}"); + String resJson = shopService.post(GET_WINDOW_PRODUCT_URL, req); return ResponseUtils.decode(resJson, GetWindowProductResponse.class); } @Override public GetWindowProductListResponse getWindowProductList(GetWindowProductListRequest req) throws WxErrorException { - String resJson = shopService.post(LIST_WINDOW_PRODUCT_URL, "{}"); + String resJson = shopService.post(LIST_WINDOW_PRODUCT_URL, req); return ResponseUtils.decode(resJson, GetWindowProductListResponse.class); } @Override public WxChannelBaseResponse offWindowProduct(WindowProductRequest req) throws WxErrorException { - String resJson = shopService.post(OFF_WINDOW_PRODUCT_URL, "{}"); + String resJson = shopService.post(OFF_WINDOW_PRODUCT_URL, req); return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); } } From 86b02922d04c9020a5ca2edc244f24fa9e913bc6 Mon Sep 17 00:00:00 2001 From: Binary Wang
+ * 获取通讯录同步access_token,本方法线程安全 + * 通讯录同步相关接口仅支持通过"通讯录同步secret"调用,需要使用独立的access_token + * 详情请见: https://developer.work.weixin.qq.com/document/path/91579 + *+ * + * @param forceRefresh 强制刷新 + * @return 通讯录同步专用的access token + * @throws WxErrorException the wx error exception + */ + String getContactAccessToken(boolean forceRefresh) throws WxErrorException; + /** *
* 获取会话存档access_token,本方法线程安全
@@ -220,6 +233,32 @@ public interface WxCpService extends WxService {
*/
String postForMsgAudit(String url, String postData) throws WxErrorException;
+ /**
+ *
+ * 使用通讯录同步access token发起get请求
+ * 通讯录同步相关API需要使用通讯录同步专用的secret获取独立的access token
+ *
+ *
+ * @param url 接口地址
+ * @param queryParam 请求参数
+ * @return the string
+ * @throws WxErrorException the wx error exception
+ */
+ String getForContact(String url, String queryParam) throws WxErrorException;
+
+ /**
+ *
+ * 使用通讯录同步access token发起post请求
+ * 通讯录同步相关API需要使用通讯录同步专用的secret获取独立的access token
+ *
+ *
+ * @param url 接口地址
+ * @param postData 请求body字符串
+ * @return the string
+ * @throws WxErrorException the wx error exception
+ */
+ String postForContact(String url, String postData) throws WxErrorException;
+
/**
*
* Service没有实现某个API的时候,可以用这个,
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 7c72cb9a8c..a3ec703ca4 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
@@ -313,6 +313,29 @@ public String postForMsgAudit(String url, String postData) throws WxErrorExcepti
return this.executeNormal(SimplePostRequestExecutor.create(this), urlWithToken, postData);
}
+ @Override
+ public String getForContact(String url, String queryParam) throws WxErrorException {
+ // 获取通讯录同步专用的access token
+ String contactAccessToken = getContactAccessToken(false);
+ // 拼接access_token参数
+ String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + contactAccessToken;
+ if (queryParam != null && !queryParam.isEmpty()) {
+ urlWithToken = urlWithToken + "&" + queryParam;
+ }
+ // 使用executeNormal方法,不自动添加token
+ return this.executeNormal(SimpleGetRequestExecutor.create(this), urlWithToken, null);
+ }
+
+ @Override
+ public String postForContact(String url, String postData) throws WxErrorException {
+ // 获取通讯录同步专用的access token
+ String contactAccessToken = getContactAccessToken(false);
+ // 拼接access_token参数
+ String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + contactAccessToken;
+ // 使用executeNormal方法,不自动添加token
+ return this.executeNormal(SimplePostRequestExecutor.create(this), urlWithToken, postData);
+ }
+
/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
*/
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
index ef78116e12..8285e59df2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
@@ -75,6 +75,51 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
return this.configStorage.getAccessToken();
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+
+ Lock lock = this.configStorage.getContactAccessTokenLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = this.configStorage.getContactSecret();
+ if (contactSecret == null || contactSecret.trim().isEmpty()) {
+ throw new WxErrorException("通讯录同步secret未配置");
+ }
+ String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
+ this.configStorage.getCorpId(), contactSecret);
+
+ try {
+ HttpGet httpGet = new HttpGet(url);
+ if (this.httpProxy != null) {
+ RequestConfig config = RequestConfig.custom()
+ .setProxy(this.httpProxy).build();
+ httpGet.setConfig(config);
+ }
+ String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+
+ WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+ this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+ } catch (IOException e) {
+ throw new WxRuntimeException(e);
+ }
+ } finally {
+ lock.unlock();
+ }
+ return this.configStorage.getContactAccessToken();
+ }
+
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
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 3ca041e7ec..129823df5a 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
@@ -76,6 +76,51 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
return this.configStorage.getAccessToken();
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+
+ Lock lock = this.configStorage.getContactAccessTokenLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = this.configStorage.getContactSecret();
+ if (contactSecret == null || contactSecret.trim().isEmpty()) {
+ throw new WxErrorException("通讯录同步secret未配置");
+ }
+ String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
+ this.configStorage.getCorpId(), contactSecret);
+
+ try {
+ HttpGet httpGet = new HttpGet(url);
+ if (this.httpProxy != null) {
+ RequestConfig config = RequestConfig.custom()
+ .setProxy(this.httpProxy).build();
+ httpGet.setConfig(config);
+ }
+ String resultContent = getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+
+ WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+ this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+ } catch (IOException e) {
+ throw new WxRuntimeException(e);
+ }
+ } finally {
+ lock.unlock();
+ }
+ return this.configStorage.getContactAccessToken();
+ }
+
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
index 7b651cbc08..69cc074be9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
@@ -70,6 +70,49 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
return configStorage.getAccessToken();
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ final WxCpConfigStorage configStorage = getWxCpConfigStorage();
+ if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return configStorage.getContactAccessToken();
+ }
+ Lock lock = configStorage.getContactAccessTokenLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return configStorage.getContactAccessToken();
+ }
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = configStorage.getContactSecret();
+ if (contactSecret == null || contactSecret.trim().isEmpty()) {
+ throw new WxErrorException("通讯录同步secret未配置");
+ }
+ String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
+ this.configStorage.getCorpId(), contactSecret);
+ try {
+ HttpGet httpGet = new HttpGet(url);
+ if (getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build();
+ httpGet.setConfig(config);
+ }
+ String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+
+ WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+ configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+ } catch (IOException e) {
+ throw new WxRuntimeException(e);
+ }
+ } finally {
+ lock.unlock();
+ }
+ return configStorage.getContactAccessToken();
+ }
+
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
final WxCpConfigStorage configStorage = getWxCpConfigStorage();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
index eba9315649..ef6d86c665 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
@@ -65,6 +65,45 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
return this.configStorage.getAccessToken();
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+
+ Lock lock = this.configStorage.getContactAccessTokenLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = this.configStorage.getContactSecret();
+ if (contactSecret == null || contactSecret.trim().isEmpty()) {
+ throw new WxErrorException("通讯录同步secret未配置");
+ }
+ HttpRequest request = HttpRequest.get(String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
+ this.configStorage.getCorpId(), contactSecret));
+ if (this.httpProxy != null) {
+ httpClient.useProxy(this.httpProxy);
+ }
+ request.withConnectionProvider(httpClient);
+ HttpResponse response = request.send();
+
+ String resultContent = response.bodyText();
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+ this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+ } finally {
+ lock.unlock();
+ }
+ return this.configStorage.getContactAccessToken();
+ }
+
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
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 ce77b37805..76847f1f93 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
@@ -56,12 +56,15 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
this.configStorage.getCorpSecret()))
.get()
.build();
- String resultContent = null;
- try {
- Response response = client.newCall(request).execute();
+ String resultContent;
+ try (Response response = client.newCall(request).execute()) {
+ if (response.body() == null) {
+ throw new WxErrorException("请求access token失败:响应内容为空");
+ }
resultContent = response.body().string();
} catch (IOException e) {
log.error(e.getMessage(), e);
+ throw new WxErrorException(e);
}
WxError error = WxError.fromJson(resultContent, WxType.CP);
@@ -75,6 +78,55 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
return this.configStorage.getAccessToken();
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+
+ Lock lock = this.configStorage.getContactAccessTokenLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getContactAccessToken();
+ }
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = this.configStorage.getContactSecret();
+ if (contactSecret == null || contactSecret.trim().isEmpty()) {
+ throw new WxErrorException("通讯录同步secret未配置");
+ }
+ //得到httpClient
+ OkHttpClient client = getRequestHttpClient();
+ //请求的request
+ Request request = new Request.Builder()
+ .url(String.format(this.configStorage.getApiUrl(GET_TOKEN), this.configStorage.getCorpId(),
+ contactSecret))
+ .get()
+ .build();
+ String resultContent;
+ try (Response response = client.newCall(request).execute()) {
+ if (response.body() == null) {
+ throw new WxErrorException("请求通讯录同步access token失败:响应内容为空");
+ }
+ resultContent = response.body().string();
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ throw new WxErrorException(e);
+ }
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+ this.configStorage.updateContactAccessToken(accessToken.getAccessToken(),
+ accessToken.getExpiresIn());
+ } finally {
+ lock.unlock();
+ }
+ return this.configStorage.getContactAccessToken();
+ }
+
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
@@ -101,11 +153,15 @@ public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorExcepti
msgAuditSecret))
.get()
.build();
- String resultContent = null;
+ String resultContent;
try (Response response = client.newCall(request).execute()) {
+ if (response.body() == null) {
+ throw new WxErrorException("请求会话存档access token失败:响应内容为空");
+ }
resultContent = response.body().string();
} catch (IOException e) {
log.error(e.getMessage(), e);
+ throw new WxErrorException(e);
}
WxError error = WxError.fromJson(resultContent, WxType.CP);
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 fe6acf12d3..4159e186f9 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
@@ -258,6 +258,47 @@ public interface WxCpConfigStorage {
*/
String getWebhookKey();
+ /**
+ * 获取通讯录同步的secret
+ *
+ * @return contact secret
+ */
+ String getContactSecret();
+
+ /**
+ * 获取通讯录同步的access token
+ *
+ * @return contact access token
+ */
+ String getContactAccessToken();
+
+ /**
+ * 获取通讯录同步access token的锁
+ *
+ * @return contact access token lock
+ */
+ Lock getContactAccessTokenLock();
+
+ /**
+ * 检查通讯录同步access token是否已过期
+ *
+ * @return true: 已过期, false: 未过期
+ */
+ boolean isContactAccessTokenExpired();
+
+ /**
+ * 强制将通讯录同步access token过期掉
+ */
+ void expireContactAccessToken();
+
+ /**
+ * 更新通讯录同步access token
+ *
+ * @param accessToken 通讯录同步access token
+ * @param expiresInSeconds 过期时间(秒)
+ */
+ void updateContactAccessToken(String accessToken, int expiresInSeconds);
+
/**
* 获取会话存档的secret
*
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 c7b300ba48..8395ca28a5 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
@@ -44,6 +44,16 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
private volatile String token;
private volatile String aesKey;
private volatile long expiresTime;
+ /**
+ * 通讯录同步secret及其access token
+ */
+ private volatile String contactSecret;
+ private volatile String contactAccessToken;
+ private volatile long contactAccessTokenExpiresTime;
+ /**
+ * 通讯录同步access token锁
+ */
+ protected transient Lock contactAccessTokenLock = new ReentrantLock();
/**
* 会话存档私钥以及sdk路径
*/
@@ -465,6 +475,49 @@ public WxCpDefaultConfigImpl setWebhookKey(String webhookKey) {
return this;
}
+ @Override
+ public String getContactSecret() {
+ return this.contactSecret;
+ }
+
+ /**
+ * 设置通讯录同步secret.
+ *
+ * @param contactSecret 通讯录同步secret
+ * @return this
+ */
+ public WxCpDefaultConfigImpl setContactSecret(String contactSecret) {
+ this.contactSecret = contactSecret;
+ return this;
+ }
+
+ @Override
+ public String getContactAccessToken() {
+ return this.contactAccessToken;
+ }
+
+ @Override
+ public Lock getContactAccessTokenLock() {
+ return this.contactAccessTokenLock;
+ }
+
+ @Override
+ public boolean isContactAccessTokenExpired() {
+ return System.currentTimeMillis() > this.contactAccessTokenExpiresTime;
+ }
+
+ @Override
+ public void expireContactAccessToken() {
+ this.contactAccessTokenExpiresTime = 0;
+ }
+
+ @Override
+ public synchronized void updateContactAccessToken(String accessToken, int expiresInSeconds) {
+ this.contactAccessToken = accessToken;
+ // 预留200秒的时间
+ this.contactAccessTokenExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+ }
+
@Override
public String getMsgAuditSecret() {
return this.msgAuditSecret;
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 2ba71fffb6..01c61673a5 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
@@ -492,6 +492,36 @@ public String getMsgAuditSecret() {
return null;
}
+ @Override
+ public String getContactSecret() {
+ return null;
+ }
+
+ @Override
+ public String getContactAccessToken() {
+ return null;
+ }
+
+ @Override
+ public Lock getContactAccessTokenLock() {
+ return this.msgAuditAccessTokenLock;
+ }
+
+ @Override
+ public boolean isContactAccessTokenExpired() {
+ return true;
+ }
+
+ @Override
+ public void expireContactAccessToken() {
+ // 不支持
+ }
+
+ @Override
+ public void updateContactAccessToken(String accessToken, int expiresInSeconds) {
+ // 不支持
+ }
+
@Override
public String getMsgAuditAccessToken() {
return null;
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
index 87d2094e58..bdc85afcfc 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
@@ -106,6 +106,11 @@ public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorExcepti
return "mock_msg_audit_access_token";
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "mock_contact_access_token";
+ }
+
@Override
public void initHttp() {
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetContactAccessTokenTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetContactAccessTokenTest.java
new file mode 100644
index 0000000000..5fb79aaaee
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetContactAccessTokenTest.java
@@ -0,0 +1,264 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.locks.Lock;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * 测试 getContactAccessToken 方法在各个实现类中的正确性
+ *
+ * @author Binary Wang
+ */
+@Test
+public class WxCpServiceGetContactAccessTokenTest {
+
+ private WxCpDefaultConfigImpl config;
+
+ @BeforeMethod
+ public void setUp() {
+ config = new WxCpDefaultConfigImpl();
+ config.setCorpId("testCorpId");
+ config.setCorpSecret("testCorpSecret");
+ config.setContactSecret("testContactSecret");
+ }
+
+ /**
+ * 测试通讯录同步access token的缓存机制
+ * 验证当token未过期时,直接从配置中返回缓存的token
+ */
+ @Test
+ public void testGetContactAccessToken_Cache() throws WxErrorException {
+ // 预先设置一个有效的token
+ config.updateContactAccessToken("cached_token", 7200);
+
+ BaseWxCpServiceImpl service = createTestService(config);
+
+ // 不强制刷新时应该返回缓存的token
+ String token = service.getContactAccessToken(false);
+ assertThat(token).isEqualTo("cached_token");
+ }
+
+ /**
+ * 测试强制刷新通讯录同步access token
+ * 验证forceRefresh=true时会重新获取token
+ */
+ @Test
+ public void testGetContactAccessToken_ForceRefresh() throws WxErrorException {
+ // 预先设置一个有效的token
+ config.updateContactAccessToken("old_token", 7200);
+
+ BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "new_token");
+
+ // 强制刷新应该获取新token
+ String token = service.getContactAccessToken(true);
+ assertThat(token).isEqualTo("new_token");
+ }
+
+ /**
+ * 测试token过期时自动刷新
+ * 验证当token已过期时,会自动重新获取
+ */
+ @Test
+ public void testGetContactAccessToken_Expired() throws WxErrorException {
+ // 设置一个已过期的token(过期时间为负数,确保立即过期)
+ config.updateContactAccessToken("expired_token", -1);
+
+ BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "refreshed_token");
+
+ // 过期的token应该被自动刷新
+ String token = service.getContactAccessToken(false);
+ assertThat(token).isEqualTo("refreshed_token");
+ }
+
+ /**
+ * 测试获取锁机制
+ * 验证配置中的锁可以正常获取和使用
+ */
+ @Test
+ public void testGetContactAccessToken_Lock() {
+ // 验证配置提供的锁不为null
+ assertThat(config.getContactAccessTokenLock()).isNotNull();
+
+ // 验证锁可以正常使用
+ config.getContactAccessTokenLock().lock();
+ try {
+ assertThat(config.getContactAccessToken()).isNull();
+ } finally {
+ config.getContactAccessTokenLock().unlock();
+ }
+ }
+
+ /**
+ * 检查token是否需要刷新的公共逻辑
+ */
+ private boolean shouldRefreshToken(WxCpConfigStorage storage, boolean forceRefresh) {
+ return storage.isContactAccessTokenExpired() || forceRefresh;
+ }
+
+ /**
+ * 验证通讯录同步secret是否已配置的公共逻辑
+ */
+ private void validateContactSecret(String contactSecret) throws WxErrorException {
+ if (contactSecret == null || contactSecret.trim().isEmpty()) {
+ throw new WxErrorException("通讯录同步secret未配置");
+ }
+ }
+
+ /**
+ * 创建一个用于测试的BaseWxCpServiceImpl实现,
+ * 用于测试缓存和过期逻辑
+ */
+ private BaseWxCpServiceImpl createTestService(WxCpConfigStorage config) {
+ return new BaseWxCpServiceImpl() {
+ @Override
+ public Object getRequestHttpClient() {
+ return null;
+ }
+
+ @Override
+ public Object getRequestHttpProxy() {
+ return null;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return null;
+ }
+
+ @Override
+ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "test_access_token";
+ }
+
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ // 检查是否需要刷新
+ if (!shouldRefreshToken(getWxCpConfigStorage(), forceRefresh)) {
+ return getWxCpConfigStorage().getContactAccessToken();
+ }
+
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = getWxCpConfigStorage().getContactSecret();
+ validateContactSecret(contactSecret);
+
+ // 返回缓存的token(用于测试缓存机制)
+ return getWxCpConfigStorage().getContactAccessToken();
+ }
+
+ @Override
+ public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "test_msg_audit_token";
+ }
+
+ @Override
+ public void initHttp() {
+ }
+
+ @Override
+ public WxCpConfigStorage getWxCpConfigStorage() {
+ return config;
+ }
+ };
+ }
+
+ /**
+ * 创建一个用于测试的BaseWxCpServiceImpl实现,
+ * 模拟返回指定的token(用于测试刷新逻辑)
+ */
+ private BaseWxCpServiceImpl createTestServiceWithMockToken(WxCpConfigStorage config, String mockToken) {
+ return new BaseWxCpServiceImpl() {
+ @Override
+ public Object getRequestHttpClient() {
+ return null;
+ }
+
+ @Override
+ public Object getRequestHttpProxy() {
+ return null;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return null;
+ }
+
+ @Override
+ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "test_access_token";
+ }
+
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ // 使用锁机制
+ Lock lock = getWxCpConfigStorage().getContactAccessTokenLock();
+ lock.lock();
+ try {
+ // 检查是否需要刷新
+ if (!shouldRefreshToken(getWxCpConfigStorage(), forceRefresh)) {
+ return getWxCpConfigStorage().getContactAccessToken();
+ }
+
+ // 使用通讯录同步secret获取access_token
+ String contactSecret = getWxCpConfigStorage().getContactSecret();
+ validateContactSecret(contactSecret);
+
+ // 模拟获取新token并更新配置
+ getWxCpConfigStorage().updateContactAccessToken(mockToken, 7200);
+ return mockToken;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "test_msg_audit_token";
+ }
+
+ @Override
+ public void initHttp() {
+ }
+
+ @Override
+ public WxCpConfigStorage getWxCpConfigStorage() {
+ return config;
+ }
+ };
+ }
+
+ /**
+ * 测试当 ContactSecret 未配置时应该抛出异常
+ */
+ @Test
+ public void testGetContactAccessToken_WithoutSecret() {
+ config.setContactSecret(null);
+ BaseWxCpServiceImpl service = createTestService(config);
+
+ // 验证当 secret 为 null 时抛出异常
+ assertThatThrownBy(() -> service.getContactAccessToken(true))
+ .isInstanceOf(WxErrorException.class)
+ .hasMessageContaining("通讯录同步secret未配置");
+ }
+
+ /**
+ * 测试当 ContactSecret 为空字符串时应该抛出异常
+ */
+ @Test
+ public void testGetContactAccessToken_WithEmptySecret() {
+ config.setContactSecret(" ");
+ BaseWxCpServiceImpl service = createTestService(config);
+
+ // 验证当 secret 为空字符串时抛出异常
+ assertThatThrownBy(() -> service.getContactAccessToken(true))
+ .isInstanceOf(WxErrorException.class)
+ .hasMessageContaining("通讯录同步secret未配置");
+ }
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java
index da74c1d13a..edea88e01e 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java
@@ -153,6 +153,11 @@ public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorExcepti
return getWxCpConfigStorage().getMsgAuditAccessToken();
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "mock_contact_access_token";
+ }
+
@Override
public void initHttp() {
}
@@ -213,6 +218,11 @@ public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorExcepti
}
}
+ @Override
+ public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
+ return "mock_contact_access_token";
+ }
+
@Override
public void initHttp() {
}
From 990eb71703adbc3c713297361c9cae93a3fd4d47 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Jun 2026 18:26:17 +0800
Subject: [PATCH 182/189] =?UTF-8?q?:new:=20#3931=E3=80=90=E4=BC=81?=
=?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=91=E7=AC=AC=E4=B8=89=E6=96=B9?=
=?UTF-8?q?=E5=BA=94=E7=94=A8=E5=A2=9E=E5=8A=A0=E6=B6=88=E6=81=AF=E5=8F=91?=
=?UTF-8?q?=E9=80=81=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cp/tp/service/WxCpTpMessageService.java | 86 +++++++++++++
.../weixin/cp/tp/service/WxCpTpService.java | 18 +++
.../service/impl/BaseWxCpTpServiceImpl.java | 11 ++
.../impl/WxCpTpMessageServiceImpl.java | 66 ++++++++++
.../impl/WxCpTpMessageServiceImplTest.java | 118 ++++++++++++++++++
weixin-java-cp/src/test/resources/testng.xml | 1 +
6 files changed, 300 insertions(+)
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMessageService.java
create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImpl.java
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImplTest.java
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMessageService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMessageService.java
new file mode 100644
index 0000000000..edd1a96c85
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMessageService.java
@@ -0,0 +1,86 @@
+package me.chanjar.weixin.cp.tp.service;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.message.*;
+
+/**
+ * 企业微信第三方应用消息推送接口.
+ *
+ * 第三方应用使用授权企业的 access_token 代表授权企业发送应用消息。
+ *
+ * @author GitHub Copilot
+ */
+public interface WxCpTpMessageService {
+
+ /**
+ *
+ * 发送应用消息(代授权企业发送).
+ * 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/90236
+ *
+ *
+ * @param message 要发送的消息对象
+ * @param corpId 授权企业的 corpId
+ * @return 消息发送结果
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpMessageSendResult send(WxCpMessage message, String corpId) throws WxErrorException;
+
+ /**
+ *
+ * 查询应用消息发送统计.
+ * 请求方式:POST(HTTPS)
+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/message/get_statistics?access_token=ACCESS_TOKEN
+ * 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/92369
+ *
+ *
+ * @param timeType 查询哪天的数据,0:当天;1:昨天。默认为0。
+ * @param corpId 授权企业的 corpId
+ * @return 统计结果
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpMessageSendStatistics getStatistics(int timeType, String corpId) throws WxErrorException;
+
+ /**
+ *
+ * 互联企业发送应用消息.
+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/linkedcorp/message/send?access_token=ACCESS_TOKEN
+ * 文章地址:https://work.weixin.qq.com/api/doc/90000/90135/90250
+ *
+ *
+ * @param message 要发送的消息对象
+ * @param corpId 授权企业的 corpId
+ * @return 消息发送结果
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message, String corpId) throws WxErrorException;
+
+ /**
+ *
+ * 发送「学校通知」.
+ * https://developer.work.weixin.qq.com/document/path/92321
+ * 学校可以通过此接口来给家长发送不同类型的学校通知。
+ * 请求方式:POST(HTTPS)
+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/message/send?access_token=ACCESS_TOKEN
+ *
+ *
+ * @param message 要发送的消息对象
+ * @param corpId 授权企业的 corpId
+ * @return 消息发送结果
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpSchoolContactMessageSendResult sendSchoolContactMessage(WxCpSchoolContactMessage message, String corpId) throws WxErrorException;
+
+ /**
+ *
+ * 撤回应用消息.
+ * 请求地址: 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
+ * @param corpId 授权企业的 corpId
+ * @throws WxErrorException 微信错误异常
+ */
+ void recall(String msgId, String corpId) 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 92966c1d03..5189c5d821 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
@@ -530,6 +530,24 @@ public interface WxCpTpService {
*/
WxCpTpLicenseService getWxCpTpLicenseService();
+ /**
+ * get message service
+ *
+ * @return WxCpTpMessageService wx cp tp message service
+ */
+ default WxCpTpMessageService getWxCpTpMessageService() {
+ throw new UnsupportedOperationException("WxCpTpMessageService is not supported");
+ }
+
+ /**
+ * set message service
+ *
+ * @param wxCpTpMessageService the message service
+ */
+ default void setWxCpTpMessageService(WxCpTpMessageService wxCpTpMessageService) {
+ throw new UnsupportedOperationException("WxCpTpMessageService is not supported");
+ }
+
WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
String timestamp, String nonce, String msgSignature);
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 25c1470eb2..d8ed7c0e47 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
@@ -61,6 +61,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
private WxCpTpIdConvertService wxCpTpIdConvertService = new WxCpTpIdConvertServiceImpl(this);
private WxCpTpOAuth2Service wxCpTpOAuth2Service = new WxCpTpOAuth2ServiceImpl(this);
private WxCpTpCustomizedService wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(this);
+ private WxCpTpMessageService wxCpTpMessageService = new WxCpTpMessageServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
*/
@@ -665,6 +666,16 @@ public void setWxCpTpLicenseService(WxCpTpLicenseService wxCpTpLicenseService) {
this.wxCpTpLicenseService = wxCpTpLicenseService;
}
+ @Override
+ public WxCpTpMessageService getWxCpTpMessageService() {
+ return wxCpTpMessageService;
+ }
+
+ @Override
+ public void setWxCpTpMessageService(WxCpTpMessageService wxCpTpMessageService) {
+ this.wxCpTpMessageService = wxCpTpMessageService;
+ }
+
@Override
public void setWxCpTpUserService(WxCpTpUserService wxCpTpUserService) {
this.wxCpTpUserService = wxCpTpUserService;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImpl.java
new file mode 100644
index 0000000000..ef80d01ac1
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImpl.java
@@ -0,0 +1,66 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.message.*;
+import me.chanjar.weixin.cp.tp.service.WxCpTpMessageService;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message.*;
+
+/**
+ * 企业微信第三方应用消息推送接口实现类.
+ *
+ * 代授权企业发送应用消息,所有方法均需传入授权企业的 corpId。
+ *
+ * @author GitHub Copilot
+ */
+@RequiredArgsConstructor
+public class WxCpTpMessageServiceImpl implements WxCpTpMessageService {
+
+ private final WxCpTpService mainService;
+
+ @Override
+ public WxCpMessageSendResult send(WxCpMessage message, String corpId) throws WxErrorException {
+ String url = mainService.getWxCpTpConfigStorage().getApiUrl(MESSAGE_SEND)
+ + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
+ return WxCpMessageSendResult.fromJson(this.mainService.post(url, message.toJson(), true));
+ }
+
+ @Override
+ public WxCpMessageSendStatistics getStatistics(int timeType, String corpId) throws WxErrorException {
+ String url = mainService.getWxCpTpConfigStorage().getApiUrl(GET_STATISTICS)
+ + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
+ return WxCpMessageSendStatistics.fromJson(
+ this.mainService.post(url, WxCpGsonBuilder.create().toJson(ImmutableMap.of("time_type", timeType)), true));
+ }
+
+ @Override
+ public WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message, String corpId)
+ throws WxErrorException {
+ String url = mainService.getWxCpTpConfigStorage().getApiUrl(LINKEDCORP_MESSAGE_SEND)
+ + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
+ return WxCpLinkedCorpMessageSendResult.fromJson(this.mainService.post(url, message.toJson(), true));
+ }
+
+ @Override
+ public WxCpSchoolContactMessageSendResult sendSchoolContactMessage(WxCpSchoolContactMessage message, String corpId)
+ throws WxErrorException {
+ String url = mainService.getWxCpTpConfigStorage().getApiUrl(EXTERNAL_CONTACT_MESSAGE_SEND)
+ + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
+ return WxCpSchoolContactMessageSendResult.fromJson(this.mainService.post(url, message.toJson(), true));
+ }
+
+ @Override
+ public void recall(String msgId, String corpId) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("msgid", msgId);
+ String url = mainService.getWxCpTpConfigStorage().getApiUrl(MESSAGE_RECALL)
+ + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
+ this.mainService.post(url, jsonObject.toString(), true);
+ }
+
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImplTest.java
new file mode 100644
index 0000000000..ff0a143b71
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMessageServiceImplTest.java
@@ -0,0 +1,118 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
+import me.chanjar.weixin.cp.tp.service.WxCpTpMessageService;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message.MESSAGE_RECALL;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message.MESSAGE_SEND;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * 企业微信第三方应用消息推送服务测试.
+ *
+ * @author GitHub Copilot
+ */
+public class WxCpTpMessageServiceImplTest {
+
+ @Mock
+ private WxCpTpServiceApacheHttpClientImpl wxCpTpService;
+
+ @Mock
+ private WxCpTpConfigStorage configStorage;
+
+ private WxCpTpMessageService wxCpTpMessageService;
+
+ private AutoCloseable mockitoAnnotations;
+
+ /**
+ * Sets up.
+ */
+ @BeforeClass
+ public void setUp() {
+ mockitoAnnotations = MockitoAnnotations.openMocks(this);
+ when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
+ WxCpTpDefaultConfigImpl defaultConfig = new WxCpTpDefaultConfigImpl();
+ when(configStorage.getApiUrl(anyString()))
+ .thenAnswer(invocation -> defaultConfig.getApiUrl(invocation.getArgument(0)));
+ wxCpTpMessageService = new WxCpTpMessageServiceImpl(wxCpTpService);
+ }
+
+ /**
+ * Tear down.
+ *
+ * @throws Exception the exception
+ */
+ @AfterClass
+ public void tearDown() throws Exception {
+ mockitoAnnotations.close();
+ }
+
+ /**
+ * 测试 send 方法:验证使用了 corpId 对应的 access_token,并以 withoutSuiteAccessToken=true 发起请求.
+ *
+ * @throws WxErrorException 微信错误异常
+ */
+ @Test
+ public void testSendMessage() throws WxErrorException {
+ String corpId = "test_corp_id";
+ String accessToken = "test_access_token";
+ String mockResponse = "{\"errcode\":0,\"errmsg\":\"ok\",\"msgid\":\"msg_001\"}";
+
+ when(configStorage.getAccessToken(corpId)).thenReturn(accessToken);
+ String expectedUrl = new WxCpTpDefaultConfigImpl().getApiUrl(MESSAGE_SEND)
+ + "?access_token=" + accessToken;
+ when(wxCpTpService.post(eq(expectedUrl), anyString(), eq(true))).thenReturn(mockResponse);
+
+ WxCpMessage message = WxCpMessage.TEXT().toUser("zhangsan").content("hello").agentId(1).build();
+ WxCpMessageSendResult result = wxCpTpMessageService.send(message, corpId);
+ assertNotNull(result);
+
+ // 验证调用时传入了 withoutSuiteAccessToken=true,确保不会附加 suite_access_token
+ verify(wxCpTpService).post(eq(expectedUrl), anyString(), eq(true));
+ }
+
+ /**
+ * 测试 recall 方法:验证使用了 corpId 对应的 access_token,并以 withoutSuiteAccessToken=true 发起请求.
+ *
+ * @throws WxErrorException 微信错误异常
+ */
+ @Test
+ public void testRecallMessage() throws WxErrorException {
+ String corpId = "test_corp_id";
+ String accessToken = "test_access_token";
+ String msgId = "test_msg_id";
+
+ when(configStorage.getAccessToken(corpId)).thenReturn(accessToken);
+ String expectedUrl = new WxCpTpDefaultConfigImpl().getApiUrl(MESSAGE_RECALL)
+ + "?access_token=" + accessToken;
+ when(wxCpTpService.post(eq(expectedUrl), contains(msgId), eq(true))).thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}");
+
+ wxCpTpMessageService.recall(msgId, corpId);
+
+ // 验证调用时传入了 withoutSuiteAccessToken=true,确保不会附加 suite_access_token
+ verify(wxCpTpService).post(eq(expectedUrl), contains(msgId), eq(true));
+ }
+
+ /**
+ * 测试 getWxCpTpMessageService 方法:验证 BaseWxCpTpServiceImpl 中正确初始化了消息服务.
+ */
+ @Test
+ public void testGetWxCpTpMessageServiceFromBase() {
+ WxCpTpServiceApacheHttpClientImpl tpService = new WxCpTpServiceApacheHttpClientImpl();
+ assertNotNull(tpService.getWxCpTpMessageService());
+ }
+}
diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml
index 48589e709a..6508fc220d 100644
--- a/weixin-java-cp/src/test/resources/testng.xml
+++ b/weixin-java-cp/src/test/resources/testng.xml
@@ -9,6 +9,7 @@
+
From 126750b298e5e9deea68fde99ff3a3fd0e054e46 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Jun 2026 18:29:15 +0800
Subject: [PATCH 183/189] =?UTF-8?q?:art:=20#4022=20=E3=80=90=E8=A7=86?=
=?UTF-8?q?=E9=A2=91=E5=8F=B7=E3=80=91=E5=B0=8F=E5=BA=97=E2=80=9C=E6=8B=92?=
=?UTF-8?q?=E7=BB=9D=E5=94=AE=E5=90=8E=E2=80=9D=E6=8E=A5=E5=8F=A3=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E6=8B=92=E7=BB=9D=E5=87=AD=E8=AF=81=E5=8F=82=E6=95=B0?=
=?UTF-8?q?=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../channel/api/WxChannelAfterSaleService.java | 16 ++++++++++++++++
.../api/impl/WxChannelAfterSaleServiceImpl.java | 8 +++++++-
.../channel/bean/after/AfterSaleRejectParam.java | 16 ++++++++++++++++
.../impl/WxChannelAfterSaleServiceImplTest.java | 12 ++++++++++++
4 files changed, 51 insertions(+), 1 deletion(-)
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 85c945d428..b8d1156b66 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
@@ -77,6 +77,22 @@ AfterSaleListResponse listIds(Long beginCreateTime, Long endCreateTime, String n
*/
WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) throws WxErrorException;
+ /**
+ * 拒绝售后(支持拒绝凭证)
+ * 文档地址 https://developers.weixin.qq.com/doc/channels/API/aftersale/rejectapply.html
+ *
+ * @param afterSaleOrderId 售后单号
+ * @param rejectReason 拒绝原因
+ * @param rejectReasonType 拒绝原因枚举值
+ * @param rejectCertificates 拒绝凭证图片列表,可使用图片上传接口获取media_id
+ * @see #getRejectReason()
+ * @return BaseResponse
+ *
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType,
+ List rejectCertificates) 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 92f865444b..c7cdf9167a 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
@@ -61,7 +61,13 @@ public WxChannelBaseResponse accept(String afterSaleOrderId, String addressId, I
@Override
public WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) throws WxErrorException {
- AfterSaleRejectParam param = new AfterSaleRejectParam(afterSaleOrderId, rejectReason, rejectReasonType);
+ return reject(afterSaleOrderId, rejectReason, rejectReasonType, null);
+ }
+
+ @Override
+ public WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType,
+ List rejectCertificates) throws WxErrorException {
+ AfterSaleRejectParam param = new AfterSaleRejectParam(afterSaleOrderId, rejectReason, rejectReasonType, rejectCertificates);
String resJson = shopService.post(AFTER_SALE_REJECT_URL, param);
return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectParam.java
index cbde459fea..6b19a8058c 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectParam.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectParam.java
@@ -5,6 +5,8 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
+import java.util.List;
+
/**
* 售后单拒绝信息
*
@@ -27,6 +29,12 @@ public class AfterSaleRejectParam extends AfterSaleIdParam {
@JsonProperty("reject_reason_type")
private Integer rejectReasonType;
+ /**
+ * 拒绝凭证图片列表,可使用图片上传接口获取media_id
+ */
+ @JsonProperty("reject_certificates")
+ private List rejectCertificates;
+
public AfterSaleRejectParam() {
}
@@ -40,4 +48,12 @@ public AfterSaleRejectParam(String afterSaleOrderId, String rejectReason, Intege
this.rejectReason = rejectReason;
this.rejectReasonType = rejectReasonType;
}
+
+ public AfterSaleRejectParam(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/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java
index 81122f7a03..529d455d46 100644
--- a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java
+++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java
@@ -70,6 +70,18 @@ public void testReject() throws WxErrorException {
assertTrue(response.isSuccess());
}
+ @Test
+ public void testRejectWithCertificates() throws WxErrorException {
+ WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService();
+ String afterSaleOrderId = "";
+ String rejectReason = null;
+ List rejectCertificates = new ArrayList<>(4);
+ rejectCertificates.add("THE_FILE_ID_1");
+ WxChannelBaseResponse response = afterSaleService.reject(afterSaleOrderId, rejectReason, 1, rejectCertificates);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ }
+
@Test
public void testUploadRefundEvidence() throws WxErrorException {
WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService();
From cae7d4fbf221144cfaa7c55810ec6e2a5af3cf9f Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 6 Jun 2026 20:27:21 +0800
Subject: [PATCH 184/189] =?UTF-8?q?:art:=20#4024=20=E3=80=90=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E3=80=91=E8=A1=A5=E9=BD=90=20V3=20?=
=?UTF-8?q?=E9=80=80=E6=AC=BE=E9=80=9A=E7=9F=A5=E5=9B=9E=E8=B0=83=E4=B8=AD?=
=?UTF-8?q?=20amount=20=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=AE=98=E6=96=B9?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=20refund?=
=?UTF-8?q?=20=E6=98=A0=E5=B0=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../notify/WxPayRefundNotifyV3Result.java | 116 +++++++++++++++++-
.../notify/WxPayRefundNotifyV3ResultTest.java | 61 +++++++++
2 files changed, 174 insertions(+), 3 deletions(-)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3ResultTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java
index c3473ee465..f9a68de684 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java
@@ -5,6 +5,7 @@
import lombok.NoArgsConstructor;
import java.io.Serializable;
+import java.util.List;
/**
* 退款结果通知.
@@ -175,7 +176,7 @@ public static class Amount implements Serializable {
/**
*
* 字段名:退款金额
- * 变量名:refund
+ * 变量名:refund_fee
* 是否必填:是
* 类型:int
* 描述:
@@ -183,8 +184,21 @@ public static class Amount implements Serializable {
* 示例值:999
*
*/
- @SerializedName(value = "refund")
- private Integer refund;
+ @SerializedName(value = "refund_fee", alternate = {"refund"})
+ private Integer refundFee;
+ /**
+ *
+ * 字段名:退款结算金额
+ * 变量名:settlement_refund
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 退款结算金额,单位为分。
+ * 示例值:999
+ *
+ */
+ @SerializedName(value = "settlement_refund")
+ private Integer settlementRefund;
/**
*
* 字段名:用户支付金额
@@ -211,5 +225,101 @@ public static class Amount implements Serializable {
*/
@SerializedName(value = "payer_refund")
private Integer payerRefund;
+ /**
+ *
+ * 字段名:币种
+ * 变量名:currency
+ * 是否必填:否
+ * 类型:string[1,16]
+ * 描述:
+ * 符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
+ * 示例值:CNY
+ *
+ */
+ @SerializedName(value = "currency")
+ private String currency;
+ /**
+ *
+ * 字段名:结算金额
+ * 变量名:settlement_total
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 结算金额,单位为分。
+ * 示例值:999
+ *
+ */
+ @SerializedName(value = "settlement_total")
+ private Integer settlementTotal;
+ /**
+ *
+ * 字段名:优惠券退款金额
+ * 变量名:discount_refund
+ * 是否必填:否
+ * 类型:int
+ * 描述:
+ * 优惠券退款金额,单位为分。
+ * 示例值:0
+ *
+ */
+ @SerializedName(value = "discount_refund")
+ private Integer discountRefund;
+ /**
+ *
+ * 字段名:退款账户来源
+ * 变量名:from
+ * 是否必填:否
+ * 类型:array
+ * 描述:
+ * 退款出资的账户类型及金额信息
+ *
+ */
+ @SerializedName(value = "from")
+ private List from;
+
+ @Deprecated
+ public Integer getRefund() {
+ return this.refundFee;
+ }
+
+ @Deprecated
+ public void setRefund(Integer refund) {
+ this.refundFee = refund;
+ }
+ }
+
+ @Data
+ @NoArgsConstructor
+ public static class FromItem implements Serializable {
+ private static final long serialVersionUID = 1L;
+ /**
+ *
+ * 字段名:退款账户类型
+ * 变量名:account
+ * 是否必填:是
+ * 类型:string[1,32]
+ * 描述:
+ * 下面枚举值多选一。
+ * 枚举值:
+ * AVAILABLE : 可用余额
+ * UNAVAILABLE : 不可用余额
+ * 示例值:AVAILABLE
+ *
+ */
+ @SerializedName(value = "account")
+ private String account;
+ /**
+ *
+ * 字段名:退款账户金额
+ * 变量名:amount
+ * 是否必填:是
+ * 类型:int
+ * 描述:
+ * 对应账户退款金额
+ * 示例值:444
+ *
+ */
+ @SerializedName(value = "amount")
+ private Integer amount;
}
}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3ResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3ResultTest.java
new file mode 100644
index 0000000000..8b3487c51c
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3ResultTest.java
@@ -0,0 +1,61 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.google.gson.Gson;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WxPayRefundNotifyV3ResultTest {
+
+ private static final Gson GSON = new Gson();
+
+ @Test
+ public void shouldDeserializeOfficialAmountFields() {
+ String json = "{"
+ + "\"mchid\":\"1900000100\","
+ + "\"out_trade_no\":\"1217752501201407033233368018\","
+ + "\"transaction_id\":\"4200000000000000000000000000\","
+ + "\"out_refund_no\":\"1217752501201407033233368019\","
+ + "\"refund_id\":\"50000000382019052709732678859\","
+ + "\"refund_status\":\"SUCCESS\","
+ + "\"success_time\":\"2020-12-01T12:00:00+08:00\","
+ + "\"user_received_account\":\"支付用户零钱\","
+ + "\"amount\":{"
+ + "\"refund_fee\":10,"
+ + "\"settlement_refund\":9,"
+ + "\"total\":100,"
+ + "\"currency\":\"CNY\","
+ + "\"payer_total\":90,"
+ + "\"payer_refund\":10,"
+ + "\"settlement_total\":90,"
+ + "\"discount_refund\":1,"
+ + "\"from\":[{\"account\":\"AVAILABLE\",\"amount\":10}]"
+ + "}"
+ + "}";
+
+ WxPayRefundNotifyV3Result.DecryptNotifyResult result =
+ GSON.fromJson(json, WxPayRefundNotifyV3Result.DecryptNotifyResult.class);
+
+ assertThat(result.getAmount().getRefundFee()).isEqualTo(10);
+ assertThat(result.getAmount().getRefund()).isEqualTo(10);
+ assertThat(result.getAmount().getSettlementRefund()).isEqualTo(9);
+ assertThat(result.getAmount().getTotal()).isEqualTo(100);
+ assertThat(result.getAmount().getCurrency()).isEqualTo("CNY");
+ assertThat(result.getAmount().getPayerTotal()).isEqualTo(90);
+ assertThat(result.getAmount().getPayerRefund()).isEqualTo(10);
+ assertThat(result.getAmount().getSettlementTotal()).isEqualTo(90);
+ assertThat(result.getAmount().getDiscountRefund()).isEqualTo(1);
+ assertThat(result.getAmount().getFrom()).hasSize(1);
+ assertThat(result.getAmount().getFrom().get(0).getAccount()).isEqualTo("AVAILABLE");
+ assertThat(result.getAmount().getFrom().get(0).getAmount()).isEqualTo(10);
+ }
+
+ @Test
+ public void shouldKeepBackwardCompatibilityForRefundAlias() {
+ WxPayRefundNotifyV3Result.Amount amount =
+ GSON.fromJson("{\"refund\":88}", WxPayRefundNotifyV3Result.Amount.class);
+
+ assertThat(amount.getRefundFee()).isEqualTo(88);
+ assertThat(amount.getRefund()).isEqualTo(88);
+ }
+}
From 45d529c062e527fd8373d6c0efe6cba158f35b93 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 6 Jun 2026 20:29:18 +0800
Subject: [PATCH 185/189] =?UTF-8?q?:art:=20#3932=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E9=95=BF=E6=97=B6=E9=97=B4=E8=BF=90=E8=A1=8C=E6=97=B6=20Redis?=
=?UTF-8?q?=20=E5=91=BD=E4=BB=A4=E4=B8=AD=E6=96=AD=E5=AF=BC=E8=87=B4=20acc?=
=?UTF-8?q?essToken=20=E5=88=B7=E6=96=B0=E5=A4=B1=E8=B4=A5=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
---
.../RedisTemplateSimpleDistributedLock.java | 6 +-
...ateSimpleDistributedLockInterruptTest.java | 98 +++++++++++
.../impl/AbstractWxCpInRedisConfigImpl.java | 55 +++++-
.../AbstractWxCpInRedisConfigImplTest.java | 156 ++++++++++++++++++
4 files changed, 308 insertions(+), 7 deletions(-)
create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockInterruptTest.java
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImplTest.java
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
index 3f5ce4d692..364b04b574 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
@@ -42,13 +42,17 @@ public RedisTemplateSimpleDistributedLock( StringRedisTemplate redisTemplate, S
@Override
public void lock() {
+ boolean interrupted = false;
while (!tryLock()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
- // Ignore
+ interrupted = true;
}
}
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
}
@Override
diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockInterruptTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockInterruptTest.java
new file mode 100644
index 0000000000..2b8ebccb10
--- /dev/null
+++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockInterruptTest.java
@@ -0,0 +1,98 @@
+package me.chanjar.weixin.common.util.locks;
+
+import org.mockito.Mockito;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * 测试 RedisTemplateSimpleDistributedLock 的线程中断处理行为
+ *
+ * @author GitHub Copilot
+ */
+public class RedisTemplateSimpleDistributedLockInterruptTest {
+
+ private StringRedisTemplate mockRedisTemplate;
+ private ValueOperations mockValueOps;
+ private RedisTemplateSimpleDistributedLock lock;
+
+ @BeforeMethod
+ @SuppressWarnings("unchecked")
+ public void setUp() {
+ mockRedisTemplate = Mockito.mock(StringRedisTemplate.class);
+ mockValueOps = Mockito.mock(ValueOperations.class);
+ Mockito.when(mockRedisTemplate.opsForValue()).thenReturn(mockValueOps);
+ lock = new RedisTemplateSimpleDistributedLock(mockRedisTemplate, "test_interrupt_lock", 60000);
+ }
+
+ /**
+ * 测试 lock() 在 Thread.sleep 被中断时应恢复线程中断标志
+ *
+ * 修复前:InterruptedException 被忽略(// Ignore),线程中断标志丢失
+ * 修复后:调用 Thread.currentThread().interrupt() 恢复中断标志
+ *
+ */
+ @Test(description = "lock() 方法在中断时应恢复线程中断标志")
+ public void testLockRestoresInterruptedFlagAfterSleepInterruption() throws InterruptedException {
+ AtomicBoolean interruptedFlagAfterLock = new AtomicBoolean(false);
+
+ // 第一次 setIfAbsent 返回 false(模拟锁被占用),第二次返回 true(模拟锁释放)
+ Mockito.when(mockValueOps.setIfAbsent(Mockito.anyString(), Mockito.anyString(),
+ Mockito.anyLong(), Mockito.any(TimeUnit.class)))
+ .thenReturn(false)
+ .thenReturn(true);
+ // get() 返回不同的值,确保不走可重入路径
+ Mockito.when(mockValueOps.get(Mockito.anyString())).thenReturn("other-value");
+
+ Thread testThread = new Thread(() -> {
+ // 设置中断标志
+ Thread.currentThread().interrupt();
+ // 调用 lock(),第一次 tryLock 失败,sleep 会因中断标志立即抛出 InterruptedException
+ lock.lock();
+ interruptedFlagAfterLock.set(Thread.currentThread().isInterrupted());
+ });
+
+ testThread.start();
+ testThread.join(5000);
+
+ // 线程应该已经完成(不会永远阻塞)
+ Assert.assertFalse(testThread.isAlive(), "线程应该已完成");
+ // 关键验证:中断标志应被恢复(而非被忽略丢失)
+ Assert.assertTrue(interruptedFlagAfterLock.get(), "lock()执行后线程中断标志应被恢复");
+ }
+
+ /**
+ * 测试 tryLock() 在 Redis 正常响应时的基本行为
+ */
+ @Test(description = "tryLock() 成功获取锁时应返回 true")
+ public void testTryLockSuccessfully() {
+ Mockito.when(mockValueOps.setIfAbsent(Mockito.anyString(), Mockito.anyString(),
+ Mockito.anyLong(), Mockito.any(TimeUnit.class)))
+ .thenReturn(true);
+
+ boolean result = lock.tryLock();
+
+ Assert.assertTrue(result, "应成功获取锁");
+ Assert.assertNotNull(lock.getLockSecretValue(), "锁值不应为null");
+ }
+
+ /**
+ * 测试 tryLock() 在锁已被其他线程持有时应返回 false
+ */
+ @Test(description = "锁被占用时 tryLock() 应返回 false")
+ public void testTryLockWhenLockHeld() {
+ Mockito.when(mockValueOps.setIfAbsent(Mockito.anyString(), Mockito.anyString(),
+ Mockito.anyLong(), Mockito.any(TimeUnit.class)))
+ .thenReturn(false);
+ Mockito.when(mockValueOps.get(Mockito.anyString())).thenReturn("other-lock-value");
+
+ boolean result = lock.tryLock();
+
+ Assert.assertFalse(result, "锁被占用时应返回false");
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java
index a078e8cf9e..448d2b62dd 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java
@@ -1,10 +1,12 @@
package me.chanjar.weixin.cp.config.impl;
import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.redis.WxRedisOps;
import org.apache.commons.lang3.StringUtils;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@@ -12,6 +14,7 @@
* @author yl
* created on 2023/04/23
*/
+@Slf4j
public abstract class AbstractWxCpInRedisConfigImpl extends WxCpDefaultConfigImpl {
private static final long serialVersionUID = 7157341535439380615L;
/**
@@ -120,8 +123,34 @@ public String getAccessToken() {
@Override
public boolean isAccessTokenExpired() {
- Long expire = redisOps.getExpire(this.accessTokenKey);
- return expire == null || expire < 2;
+ try {
+ Long expire = redisOps.getExpire(this.accessTokenKey);
+ return expire == null || expire < 2;
+ } catch (Exception e) {
+ log.warn("获取access_token过期时间时发生异常,将视为已过期以触发刷新,异常信息: {}", e.getMessage());
+ // 仅在当前线程已中断且异常为中断相关时,才清除中断标志,避免吞掉上层的中断语义
+ if (Thread.currentThread().isInterrupted() && isInterruptionRelated(e)) {
+ Thread.interrupted();
+ }
+ return true;
+ }
+ }
+
+ /**
+ * 判断异常及其原因链是否为中断相关异常。
+ *
+ * @param throwable 异常
+ * @return 如果异常链中包含 {@link InterruptedException} 或 {@link CancellationException},返回 true;否则返回 false
+ */
+ private boolean isInterruptionRelated(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof InterruptedException || current instanceof CancellationException) {
+ return true;
+ }
+ current = current.getCause();
+ }
+ return false;
}
@Override
@@ -146,8 +175,13 @@ public String getJsapiTicket() {
@Override
public boolean isJsapiTicketExpired() {
- Long expire = redisOps.getExpire(this.jsapiTicketKey);
- return expire == null || expire < 2;
+ try {
+ Long expire = redisOps.getExpire(this.jsapiTicketKey);
+ return expire == null || expire < 2;
+ } catch (Exception e) {
+ log.warn("获取jsapi_ticket过期时间时发生异常,将视为已过期,异常信息: {}", e.getMessage());
+ return true;
+ }
}
@Override
@@ -177,8 +211,17 @@ public String getAgentJsapiTicket() {
@Override
public boolean isAgentJsapiTicketExpired() {
- Long expire = redisOps.getExpire(this.agentJsapiTicketKey);
- return expire == null || expire < 2;
+ try {
+ Long expire = redisOps.getExpire(this.agentJsapiTicketKey);
+ return expire == null || expire < 2;
+ } catch (Exception e) {
+ log.warn("获取agent_jsapi_ticket过期时间时发生异常,将视为已过期,异常信息: {}", e.getMessage());
+ // 仅在当前线程已中断且异常为中断相关时,才清除中断标志,避免吞掉上层的中断语义
+ if (Thread.currentThread().isInterrupted() && isInterruptionRelated(e)) {
+ Thread.interrupted();
+ }
+ return true;
+ }
}
@Override
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImplTest.java
new file mode 100644
index 0000000000..201286943e
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImplTest.java
@@ -0,0 +1,156 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 测试 AbstractWxCpInRedisConfigImpl 对 Redis 异常的容错处理
+ *
+ * @author GitHub Copilot
+ */
+public class AbstractWxCpInRedisConfigImplTest {
+
+ private WxRedisOps mockRedisOps;
+ private AbstractWxCpInRedisConfigImpl config;
+
+ @BeforeMethod
+ public void setUp() {
+ mockRedisOps = Mockito.mock(WxRedisOps.class);
+ Mockito.when(mockRedisOps.getLock(Mockito.anyString()))
+ .thenReturn(new ReentrantLock());
+
+ config = new AbstractWxCpInRedisConfigImpl(mockRedisOps, "test") {
+ // 使用匿名类提供具体实现用于测试
+ };
+ config.setCorpId("testCorpId");
+ config.setAgentId(1);
+ }
+
+ /**
+ * 测试当 Redis getExpire 抛出异常时,isAccessTokenExpired() 应返回 true(视为已过期),且不影响线程中断标志
+ */
+ @Test
+ public void testIsAccessTokenExpiredWhenRedisThrowsException() {
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString()))
+ .thenThrow(new RuntimeException("Redis command interrupted"));
+
+ boolean expired = config.isAccessTokenExpired();
+
+ Assert.assertTrue(expired, "Redis异常时应将token视为已过期");
+ // 非中断相关异常不应影响线程中断标志
+ Assert.assertFalse(Thread.currentThread().isInterrupted(), "非中断异常时线程中断标志不应被改变");
+ }
+
+ /**
+ * 测试当线程中断状态已设置时,Redis 调用抛出中断相关异常,isAccessTokenExpired() 应处理并清除中断标志
+ */
+ @Test
+ public void testIsAccessTokenExpiredClearsInterruptedFlag() {
+ // 使用包含 InterruptedException cause 的异常,模拟 Lettuce 的 RedisCommandInterruptedException 行为
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString()))
+ .thenThrow(new RuntimeException("wrapped", new InterruptedException("command interrupted")));
+
+ Thread.currentThread().interrupt();
+ try {
+ boolean expired = config.isAccessTokenExpired();
+
+ Assert.assertTrue(expired, "Redis中断异常时应将token视为已过期");
+ // 中断标志应该被清除,允许后续操作正常进行
+ Assert.assertFalse(Thread.currentThread().isInterrupted(), "中断相关异常处理后线程中断标志应被清除");
+ } finally {
+ // 兜底清除当前线程的中断标志,避免影响后续测试用例
+ Thread.interrupted();
+ }
+ }
+
+ /**
+ * 测试正常情况下 isAccessTokenExpired() 的行为
+ */
+ @Test
+ public void testIsAccessTokenExpiredWhenTokenValid() {
+ // 返回60秒后过期(未过期)
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString())).thenReturn(60L);
+
+ boolean expired = config.isAccessTokenExpired();
+
+ Assert.assertFalse(expired, "token未过期时应返回false");
+ }
+
+ /**
+ * 测试 isAccessTokenExpired() 当 expire 为 null 时视为已过期
+ */
+ @Test
+ public void testIsAccessTokenExpiredWhenExpireIsNull() {
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString())).thenReturn(null);
+
+ boolean expired = config.isAccessTokenExpired();
+
+ Assert.assertTrue(expired, "expire为null时应视为已过期");
+ }
+
+ /**
+ * 测试当 Redis getExpire 抛出异常时,isJsapiTicketExpired() 应返回 true(视为已过期),且不影响线程中断标志
+ */
+ @Test
+ public void testIsJsapiTicketExpiredWhenRedisThrowsException() {
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString()))
+ .thenThrow(new RuntimeException("Redis command interrupted"));
+
+ boolean expired = config.isJsapiTicketExpired();
+
+ Assert.assertTrue(expired, "Redis异常时应将jsapi_ticket视为已过期");
+ Assert.assertFalse(Thread.currentThread().isInterrupted(), "非中断异常时线程中断标志不应被改变");
+ }
+
+ /**
+ * 测试当 Redis getExpire 抛出异常时,isAgentJsapiTicketExpired() 应返回 true(视为已过期),且不影响线程中断标志
+ */
+ @Test
+ public void testIsAgentJsapiTicketExpiredWhenRedisThrowsException() {
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString()))
+ .thenThrow(new RuntimeException("Redis command interrupted"));
+
+ boolean expired = config.isAgentJsapiTicketExpired();
+
+ Assert.assertTrue(expired, "Redis异常时应将agent_jsapi_ticket视为已过期");
+ Assert.assertFalse(Thread.currentThread().isInterrupted(), "非中断异常时线程中断标志不应被改变");
+ }
+
+ /**
+ * 测试当线程中断状态已设置时,Redis 调用抛出中断相关异常,isAgentJsapiTicketExpired() 应处理并清除中断标志
+ */
+ @Test
+ public void testIsAgentJsapiTicketExpiredClearsInterruptedFlag() {
+ Mockito.when(mockRedisOps.getExpire(Mockito.anyString()))
+ .thenThrow(new RuntimeException("wrapped", new InterruptedException("command interrupted")));
+
+ Thread.currentThread().interrupt();
+ try {
+ boolean expired = config.isAgentJsapiTicketExpired();
+
+ Assert.assertTrue(expired, "Redis中断异常时应将agent_jsapi_ticket视为已过期");
+ Assert.assertFalse(Thread.currentThread().isInterrupted(), "中断相关异常处理后线程中断标志应被清除");
+ } finally {
+ Thread.interrupted();
+ }
+ }
+
+ /**
+ * 测试提供自定义 Lock 实现时 getAccessTokenLock() 返回正确的锁
+ */
+ @Test
+ public void testGetAccessTokenLockReturnsMockedLock() {
+ Lock mockLock = Mockito.mock(Lock.class);
+ Mockito.when(mockRedisOps.getLock(Mockito.anyString())).thenReturn(mockLock);
+
+ Lock lock = config.getAccessTokenLock();
+
+ Assert.assertNotNull(lock, "获取到的锁不应为null");
+ }
+}
From fab98621c0901e31deee89d878f32ba3775ca2d5 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sat, 6 Jun 2026 20:31:06 +0800
Subject: [PATCH 186/189] =?UTF-8?q?:art:=20=20#3934=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=A3=20SDK=20=E6=AF=8F=E6=AC=A1=20API=20?=
=?UTF-8?q?=E8=B0=83=E7=94=A8=E5=90=8E=E8=A2=AB=E9=94=80=E6=AF=81=E5=B9=B6?=
=?UTF-8?q?=E9=87=8D=E6=96=B0=E5=88=9D=E5=A7=8B=E5=8C=96=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
---
docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md | 19 +-
...SG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md | 2 +-
.../weixin/cp/config/WxCpConfigStorage.java | 8 +-
.../cp/config/impl/WxCpDefaultConfigImpl.java | 12 +-
.../cp/config/impl/WxCpRedisConfigImpl.java | 12 +-
.../WxCpDefaultConfigImplMsgAuditSdkTest.java | 230 ++++++++++++++++++
weixin-java-cp/src/test/resources/testng.xml | 1 +
7 files changed, 260 insertions(+), 24 deletions(-)
create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImplMsgAuditSdkTest.java
diff --git a/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md b/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md
index b64e4612b9..83d9c08158 100644
--- a/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md
+++ b/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md
@@ -196,19 +196,22 @@ msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, data -> {
1. **获取SDK时**:引用计数 +1
2. **使用完成后**:引用计数 -1
-3. **计数归零时**:SDK被自动释放
+3. **计数归零且SDK已过期时**:SDK被销毁并清理缓存
+4. **计数归零但SDK未过期时**:保留缓存,供后续调用直接复用
+
+> **注意**:引用计数归零并不等同于立即销毁SDK。只有在 SDK 已超过有效期的情况下,框架才会调用 `Finance.DestroySdk()` 释放资源。这一机制避免了每次 API 调用后的频繁初始化/销毁循环。
```java
// 框架内部实现(简化版)
public void downloadMediaFile(String sdkFileId, ...) {
- long sdk = initSdk(); // 获取或初始化SDK
+ long sdk = initSdk(); // 获取或初始化SDK(有效期内直接复用缓存)
configStorage.incrementMsgAuditSdkRefCount(sdk); // 引用计数 +1
-
+
try {
// 执行实际操作
getMediaFile(sdk, sdkFileId, ...);
} finally {
- // 确保引用计数一定会减少
+ // 确保引用计数一定会减少;仅在归零且过期时销毁
configStorage.decrementMsgAuditSdkRefCount(sdk); // 引用计数 -1
}
}
@@ -216,13 +219,13 @@ public void downloadMediaFile(String sdkFileId, ...) {
### SDK缓存机制
-SDK初始化后会缓存7200秒(企业微信官方文档规定),避免频繁初始化:
+SDK初始化后会缓存7200秒,避免频繁初始化:
- **首次调用**:初始化新的SDK
-- **7200秒内**:复用缓存的SDK
-- **超过7200秒**:重新初始化SDK
+- **7200秒内**:复用缓存的SDK(即使引用计数曾归零也不重新初始化)
+- **超过7200秒**:下次 `acquireMsgAuditSdk()` 返回0,触发重新初始化,旧SDK在重新初始化时被销毁
-新API的引用计数机制与缓存机制完美配合,确保SDK不会被提前销毁。
+新API的引用计数机制与缓存机制完美配合,确保SDK不会被提前销毁,也不会永久残留。
## 迁移指南
diff --git a/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md b/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md
index 072ceefd0c..1f073905f4 100644
--- a/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md
+++ b/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md
@@ -5,7 +5,7 @@
当前实现(4.8.x)通过"共享SDK + 引用计数 + 7200秒过期"来管理会话存档SDK生命周期。
该方案存在以下核心问题:
-1. **频繁初始化/销毁**:每次调用 `releaseSdk()` 后引用计数归零即销毁SDK。对于"拉取→解密→下载媒体"这类典型串行调用链,每步操作都会触发重新初始化。
+1. ~~**频繁初始化/销毁**:每次调用 `releaseSdk()` 后引用计数归零即销毁SDK。对于"拉取→解密→下载媒体"这类典型串行调用链,每步操作都会触发重新初始化。~~ ✅ 已在 4.8.3.B+ 修复:引用计数归零时,仅在 SDK 已过期的情况下才销毁,有效期内继续复用缓存。
2. **7200秒过期规则无依据**:官方文档FAQ明确说"不需要每次new/init sdk,可以在多次拉取中复用同一个sdk",无任何7200秒过期说明。
3. **线程安全问题**:企微技术人员建议"一个线程一个SDK实例",当前设计多线程共享同一SDK实例,存在并发安全隐患。
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 4159e186f9..7f66f05094 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
@@ -392,10 +392,11 @@ public interface WxCpConfigStorage {
/**
* 减少会话存档SDK的引用计数
- * 当引用计数降为0时,自动销毁SDK以释放资源
+ * 当引用计数降为0且SDK已过期时,才自动销毁SDK以释放资源
+ * 如果SDK尚未过期,保留SDK缓存以供后续调用复用
*
* @param sdk sdk id
- * @return 减少后的引用计数,如果返回0表示SDK已被销毁,如果SDK不匹配返回-1
+ * @return 减少后的引用计数;SDK不匹配或引用计数已为0时返回-1
* @deprecated 引用计数机制已废弃,由 ThreadLocal 模式替代。
*/
@Deprecated
@@ -424,7 +425,8 @@ public interface WxCpConfigStorage {
/**
* 减少SDK引用计数并在必要时释放(原子操作)
- * 此方法确保引用计数递减和SDK检查在同一个同步块内完成
+ * 当引用计数降为0且SDK已过期时,才销毁SDK以释放资源
+ * 如果SDK尚未过期,保留SDK缓存以供后续调用复用,避免频繁初始化和销毁
*
* @param sdk sdk id
* @deprecated 引用计数机制已废弃,由 ThreadLocal 模式替代。
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 8395ca28a5..6435370150 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
@@ -609,9 +609,9 @@ public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
int newCount = --this.msgAuditSdkRefCount;
- // 当引用计数降为0时,自动销毁SDK以释放资源
- // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
- if (newCount == 0 && this.msgAuditSdk == sdk) {
+ // 当引用计数降为0且SDK已过期时,才销毁SDK以释放资源
+ // 如果SDK尚未过期,保留SDK缓存以供后续调用复用,避免频繁初始化和销毁
+ if (newCount == 0 && this.msgAuditSdk == sdk && isMsgAuditSdkExpired()) {
Finance.DestroySdk(sdk);
this.msgAuditSdk = 0;
this.msgAuditSdkExpiresTime = 0;
@@ -646,9 +646,9 @@ public synchronized long acquireMsgAuditSdk() {
public synchronized void releaseMsgAuditSdk(long sdk) {
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
int newCount = --this.msgAuditSdkRefCount;
- // 当引用计数降为0时,自动销毁SDK以释放资源
- // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
- if (newCount == 0 && this.msgAuditSdk == sdk) {
+ // 当引用计数降为0且SDK已过期时,才销毁SDK以释放资源
+ // 如果SDK尚未过期,保留SDK缓存以供后续调用复用,避免频繁初始化和销毁
+ if (newCount == 0 && this.msgAuditSdk == sdk && isMsgAuditSdkExpired()) {
Finance.DestroySdk(sdk);
this.msgAuditSdk = 0;
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 01c61673a5..85d136e01d 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
@@ -589,9 +589,9 @@ public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
int newCount = --this.msgAuditSdkRefCount;
- // 当引用计数降为0时,自动销毁SDK以释放资源
- // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
- if (newCount == 0 && this.msgAuditSdk == sdk) {
+ // 当引用计数降为0且SDK已过期时,才销毁SDK以释放资源
+ // 如果SDK尚未过期,保留SDK缓存以供后续调用复用,避免频繁初始化和销毁
+ if (newCount == 0 && this.msgAuditSdk == sdk && isMsgAuditSdkExpired()) {
Finance.DestroySdk(sdk);
this.msgAuditSdk = 0;
this.msgAuditSdkExpiresTime = 0;
@@ -623,9 +623,9 @@ public synchronized long acquireMsgAuditSdk() {
public synchronized void releaseMsgAuditSdk(long sdk) {
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
int newCount = --this.msgAuditSdkRefCount;
- // 当引用计数降为0时,自动销毁SDK以释放资源
- // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
- if (newCount == 0 && this.msgAuditSdk == sdk) {
+ // 当引用计数降为0且SDK已过期时,才销毁SDK以释放资源
+ // 如果SDK尚未过期,保留SDK缓存以供后续调用复用,避免频繁初始化和销毁
+ if (newCount == 0 && this.msgAuditSdk == sdk && isMsgAuditSdkExpired()) {
Finance.DestroySdk(sdk);
this.msgAuditSdk = 0;
this.msgAuditSdkExpiresTime = 0;
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImplMsgAuditSdkTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImplMsgAuditSdkTest.java
new file mode 100644
index 0000000000..f006ce0a70
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImplMsgAuditSdkTest.java
@@ -0,0 +1,230 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Field;
+
+/**
+ * 测试 WxCpDefaultConfigImpl 中会话存档 SDK 引用计数的正确性
+ * 验证修复:SDK 在引用计数降为 0 但尚未过期时,不应被销毁
+ *
+ * @author GitHub Copilot
+ */
+public class WxCpDefaultConfigImplMsgAuditSdkTest {
+
+ /**
+ * 用于测试的未过期时间偏移量(毫秒),模拟 SDK 有效状态
+ */
+ private static final long VALID_EXPIRATION_TIME_OFFSET = 7_000_000L;
+
+ private WxCpDefaultConfigImpl config;
+
+ @BeforeMethod
+ public void setUp() {
+ config = new WxCpDefaultConfigImpl();
+ }
+
+ /**
+ * 通过反射设置内部字段
+ */
+ private void setField(String fieldName, Object value) throws Exception {
+ Field field = WxCpDefaultConfigImpl.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(config, value);
+ }
+
+ /**
+ * 通过反射获取内部字段值
+ */
+ private Object getField(String fieldName) throws Exception {
+ Field field = WxCpDefaultConfigImpl.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field.get(config);
+ }
+
+ /**
+ * 验证 acquireMsgAuditSdk 在 SDK 有效时能正确返回 SDK 并增加引用计数
+ */
+ @Test
+ public void testAcquireMsgAuditSdkWhenSdkValid() throws Exception {
+ long fakeSdk = 12345L;
+ // 设置一个有效的(未过期的)SDK
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET);
+ setField("msgAuditSdkRefCount", 0);
+
+ long acquired = config.acquireMsgAuditSdk();
+
+ Assert.assertEquals(acquired, fakeSdk, "应返回已缓存的有效 SDK");
+ int refCount = (int) getField("msgAuditSdkRefCount");
+ Assert.assertEquals(refCount, 1, "引用计数应增加到 1");
+ }
+
+ /**
+ * 验证 acquireMsgAuditSdk 在 SDK 已过期时返回 0
+ */
+ @Test
+ public void testAcquireMsgAuditSdkWhenSdkExpired() throws Exception {
+ long fakeSdk = 12345L;
+ // 设置已过期的 SDK
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkExpiresTime", System.currentTimeMillis() - 1000L);
+ setField("msgAuditSdkRefCount", 0);
+
+ long acquired = config.acquireMsgAuditSdk();
+
+ Assert.assertEquals(acquired, 0L, "SDK 已过期,应返回 0");
+ int refCount = (int) getField("msgAuditSdkRefCount");
+ Assert.assertEquals(refCount, 0, "引用计数不应改变");
+ }
+
+ /**
+ * 核心测试:验证当引用计数降为 0 但 SDK 尚未过期时,SDK 不会被销毁
+ * 这是修复 issue 的关键验证:避免每次 API 调用后频繁销毁和重新初始化 SDK
+ */
+ @Test
+ public void testReleaseMsgAuditSdkShouldNotDestroyWhenNotExpired() throws Exception {
+ long fakeSdk = 12345L;
+ // 设置一个有效的(未过期的)SDK,引用计数为 1
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET);
+ setField("msgAuditSdkRefCount", 1);
+
+ // 释放引用,引用计数应降为 0,但 SDK 尚未过期,不应被销毁
+ config.releaseMsgAuditSdk(fakeSdk);
+
+ long sdkAfterRelease = (long) getField("msgAuditSdk");
+ int refCountAfterRelease = (int) getField("msgAuditSdkRefCount");
+
+ Assert.assertEquals(sdkAfterRelease, fakeSdk, "SDK 尚未过期,引用计数归零后不应被销毁,应继续缓存");
+ Assert.assertEquals(refCountAfterRelease, 0, "引用计数应为 0");
+ }
+
+ /**
+ * 验证:SDK 在未过期、引用计数为 0 时,下次调用 acquireMsgAuditSdk 应直接复用,无需重新初始化
+ * 这是修复后的核心行为:避免频繁初始化
+ */
+ @Test
+ public void testSdkReuseAfterReleaseWhenNotExpired() throws Exception {
+ long fakeSdk = 99999L;
+ // 模拟:SDK 有效,引用计数为 1(正在被使用)
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET);
+ setField("msgAuditSdkRefCount", 1);
+
+ // 模拟方法调用结束,释放引用
+ config.releaseMsgAuditSdk(fakeSdk);
+
+ // 模拟下一次方法调用,应该直接复用缓存的 SDK
+ long reacquired = config.acquireMsgAuditSdk();
+
+ Assert.assertEquals(reacquired, fakeSdk, "SDK 应被复用,而不是返回 0(需要重新初始化)");
+ int refCount = (int) getField("msgAuditSdkRefCount");
+ Assert.assertEquals(refCount, 1, "复用后引用计数应为 1");
+ }
+
+ /**
+ * 验证:多次 acquire/release 的引用计数正确性(串行验证)
+ */
+ @Test
+ public void testMultipleAcquireAndReleaseSequential() throws Exception {
+ long fakeSdk = 77777L;
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET);
+ setField("msgAuditSdkRefCount", 0);
+
+ // 三次 acquire,引用计数依次递增
+ long sdk1 = config.acquireMsgAuditSdk();
+ long sdk2 = config.acquireMsgAuditSdk();
+ long sdk3 = config.acquireMsgAuditSdk();
+
+ Assert.assertEquals(sdk1, fakeSdk);
+ Assert.assertEquals(sdk2, fakeSdk);
+ Assert.assertEquals(sdk3, fakeSdk);
+ Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 3, "应有 3 个引用");
+
+ // 逐一释放,SDK 未过期,不应被销毁
+ config.releaseMsgAuditSdk(fakeSdk);
+ Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 2, "释放一个后应有 2 个引用");
+ Assert.assertEquals((long) getField("msgAuditSdk"), fakeSdk, "SDK 仍有引用,不应被销毁");
+
+ config.releaseMsgAuditSdk(fakeSdk);
+ Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 1, "释放两个后应有 1 个引用");
+
+ config.releaseMsgAuditSdk(fakeSdk);
+ Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 0, "全部释放后引用计数应为 0");
+ // SDK 未过期,不应被销毁
+ Assert.assertEquals((long) getField("msgAuditSdk"), fakeSdk, "SDK 未过期,全部引用释放后不应被销毁");
+ }
+
+ /**
+ * 验证 incrementMsgAuditSdkRefCount 在 SDK 匹配时正确增加引用计数
+ */
+ @Test
+ public void testIncrementRefCount() throws Exception {
+ long fakeSdk = 11111L;
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkRefCount", 2);
+
+ int result = config.incrementMsgAuditSdkRefCount(fakeSdk);
+
+ Assert.assertEquals(result, 3, "引用计数应增加到 3");
+ }
+
+ /**
+ * 验证 incrementMsgAuditSdkRefCount 在 SDK 不匹配时返回 -1
+ */
+ @Test
+ public void testIncrementRefCountWithWrongSdk() throws Exception {
+ setField("msgAuditSdk", 11111L);
+ setField("msgAuditSdkRefCount", 2);
+
+ int result = config.incrementMsgAuditSdkRefCount(99999L);
+
+ Assert.assertEquals(result, -1, "SDK 不匹配时应返回 -1");
+ }
+
+ /**
+ * 验证 getMsgAuditSdkRefCount 的正确性
+ */
+ @Test
+ public void testGetMsgAuditSdkRefCount() throws Exception {
+ long fakeSdk = 55555L;
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkRefCount", 5);
+
+ int count = config.getMsgAuditSdkRefCount(fakeSdk);
+ Assert.assertEquals(count, 5, "应返回正确的引用计数");
+
+ int wrongCount = config.getMsgAuditSdkRefCount(99L);
+ Assert.assertEquals(wrongCount, -1, "SDK 不匹配时应返回 -1");
+ }
+
+ /**
+ * 验证:引用计数归零且 SDK 已过期时,releaseMsgAuditSdk 应尝试销毁 SDK
+ * 由于 Finance.DestroySdk 是原生方法,测试环境中不加载原生库时会抛出 UnsatisfiedLinkError,
+ * 但引用计数已在 Finance 调用前递减(可验证代码路径已进入销毁分支)。
+ * 当原生库可用时,应进一步断言 msgAuditSdk 和 msgAuditSdkExpiresTime 均被清零。
+ */
+ @Test
+ public void testReleaseMsgAuditSdkShouldDestroyWhenExpired() throws Exception {
+ long fakeSdk = 22222L;
+ // 设置已过期的 SDK,引用计数为 1
+ setField("msgAuditSdk", fakeSdk);
+ setField("msgAuditSdkExpiresTime", System.currentTimeMillis() - 1000L); // 已过期
+ setField("msgAuditSdkRefCount", 1);
+
+ try {
+ config.releaseMsgAuditSdk(fakeSdk);
+ // 原生库可用:断言字段已清零
+ Assert.assertEquals((long) getField("msgAuditSdk"), 0L, "过期且引用归零后 msgAuditSdk 应被清零");
+ Assert.assertEquals((long) getField("msgAuditSdkExpiresTime"), 0L, "过期时间应被清零");
+ } catch (UnsatisfiedLinkError e) {
+ // 测试环境未加载原生库:Finance.DestroySdk 被调用但抛出 UnsatisfiedLinkError
+ // 这证明代码路径正确进入了"过期时销毁 SDK"的分支,与"未过期时跳过销毁"的分支形成对比
+ Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 0, "引用计数应已递减到 0(Finance 调用前完成)");
+ }
+ }
+}
diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml
index 6508fc220d..f63d3f30f5 100644
--- a/weixin-java-cp/src/test/resources/testng.xml
+++ b/weixin-java-cp/src/test/resources/testng.xml
@@ -8,6 +8,7 @@
+
From 1037dfd89cb2b85f9c29c3410a26c3b005796bb1 Mon Sep 17 00:00:00 2001
From: buaazyl
Date: Mon, 8 Jun 2026 17:58:38 +0800
Subject: [PATCH 187/189] =?UTF-8?q?:art:=20=20#4049=20=E3=80=90=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=91=E4=BC=98=E5=8C=96=E8=AE=A2=E9=98=85?=
=?UTF-8?q?=E6=B6=88=E6=81=AF=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=A4=84=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wx/miniapp/bean/WxMaSubscribeMessage.java | 128 +++++++++++++++++-
1 file changed, 127 insertions(+), 1 deletion(-)
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
index 984e9573db..e372f27039 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
@@ -4,10 +4,12 @@
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
import lombok.*;
import lombok.experimental.Accessors;
+import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import java.util.regex.Pattern;
/**
* 订阅消息
@@ -24,6 +26,19 @@
public class WxMaSubscribeMessage implements Serializable {
private static final long serialVersionUID = 6846729898251286686L;
+ /**
+ * 预编译正则,避免每次调用 resetValue 时重复编译
+ */
+ private static final Pattern NUMBER_PATTERN = Pattern.compile("[^0-9.\\-]");
+ private static final Pattern NUMBER_VALID_PATTERN = Pattern.compile("-?\\d+\\.?\\d*|-?\\.\\d+");
+ private static final Pattern LETTER_PATTERN = Pattern.compile("[^a-zA-Z]");
+ private static final Pattern SYMBOL_PATTERN = Pattern.compile("[a-zA-Z0-9\\u4e00-\\u9fa5]");
+ private static final Pattern PHONE_PATTERN = Pattern.compile("[^0-9+\\-]");
+ private static final Pattern NAME_PATTERN = Pattern.compile("[^\\u4e00-\\u9fa5a-zA-Z \\u00b7.\\u3001\\uff0c\\u3002\\-]");
+ private static final Pattern CHARACTER_STRING_PATTERN = Pattern.compile("[\\u4e00-\\u9fa5]");
+ private static final Pattern CHINESE_PATTERN = Pattern.compile("[\\u4e00-\\u9fa5]");
+ private static final Pattern PHRASE_PATTERN = Pattern.compile("[^\\u4e00-\\u9fa5]");
+
/**
* 接收者(用户)的 openid.
*
@@ -75,15 +90,126 @@ public class WxMaSubscribeMessage implements Serializable {
private String lang = WxMaConstants.MiniProgramLang.ZH_CN;
public WxMaSubscribeMessage addData(MsgData datum) {
+ if (datum == null) {
+ return this;
+ }
if (this.data == null) {
this.data = new ArrayList<>();
}
- this.data.add(datum);
+ this.data.add(resetValue(datum));
return this;
}
+ /**
+ * 处理订阅消息字符串长度及格式问题
+ *
+ * @link 发送订阅消息
+ */
+ private MsgData resetValue(MsgData datum) {
+ String name = datum.getName();
+ String value = datum.getValue();
+
+ if (StringUtils.isBlank(value)) {
+ // 空值会发送失败,改为-
+ datum.setValue("-");
+ return datum;
+ }
+
+ if (StringUtils.startsWith(name, "thing") && value.length() > 20) {
+ // thing.DATA: 20个以内字符,可汉字、数字、字母或符号组合
+ value = StringUtils.substring(value, 0, 17) + "...";
+ } else if (StringUtils.startsWith(name, "number")) {
+ // number.DATA: 32位以内数字,只能数字,可带小数
+ value = NUMBER_PATTERN.matcher(value).replaceAll("");
+ if (!NUMBER_VALID_PATTERN.matcher(value).matches()) {
+ value = "0";
+ }
+ if (value.length() > 32) {
+ value = StringUtils.substring(value, 0, 32);
+ }
+ } else if (StringUtils.startsWith(name, "letter")) {
+ // letter.DATA: 32位以内字母,只能字母
+ value = LETTER_PATTERN.matcher(value).replaceAll("");
+ if (value.isEmpty()) {
+ value = "A";
+ }
+ if (value.length() > 32) {
+ value = StringUtils.substring(value, 0, 32);
+ }
+ } else if (StringUtils.startsWith(name, "symbol")) {
+ // symbol.DATA: 5位以内符号,只能符号(除中文、英文、数字外的常见符号)
+ value = SYMBOL_PATTERN.matcher(value).replaceAll("");
+ if (value.isEmpty()) {
+ value = "-";
+ }
+ if (value.length() > 5) {
+ value = StringUtils.substring(value, 0, 5);
+ }
+ } else if (StringUtils.startsWith(name, "character_string")) {
+ // character_string.DATA: 32位以内,可数字、字母或符号组合(不含中文)
+ value = CHARACTER_STRING_PATTERN.matcher(value).replaceAll("");
+ if (value.isEmpty()) {
+ value = "0";
+ }
+ if (value.length() > 32) {
+ value = StringUtils.substring(value, 0, 32);
+ }
+ } else if (StringUtils.startsWith(name, "phone_number")) {
+ // phone_number.DATA: 17位以内,数字、符号
+ value = PHONE_PATTERN.matcher(value).replaceAll("");
+ // 只允许一个前导+号,且必须在开头
+ if (value.startsWith("+")) {
+ value = "+" + value.substring(1).replace("+", "");
+ } else {
+ value = value.replace("+", "");
+ }
+ if (value.isEmpty()) {
+ value = "0";
+ }
+ if (value.length() > 17) {
+ value = StringUtils.substring(value, 0, 17);
+ }
+ } else if (StringUtils.startsWith(name, "car_number")) {
+ // car_number.DATA: 8位以内,第一位与最后一位可为汉字,其余为字母或数字
+ if (value.length() > 8) {
+ value = StringUtils.substring(value, 0, 8);
+ }
+ } else if (StringUtils.startsWith(name, "name")) {
+ // name.DATA: 10个以内纯汉字或20个以内纯字母或符号,中文和字母混合按中文名算10个字内
+ // 过滤非法字符,不保留数字(name 类型不允许数字)
+ value = NAME_PATTERN.matcher(value).replaceAll("");
+ if (value.isEmpty()) {
+ value = "-";
+ }
+ boolean containsChinese = CHINESE_PATTERN.matcher(value).find();
+ if (containsChinese) {
+ // 含中文,按中文名算,10个字内
+ if (value.length() > 10) {
+ value = StringUtils.substring(value, 0, 7) + "...";
+ }
+ } else {
+ // 纯字母或符号,20个以内
+ if (value.length() > 20) {
+ value = StringUtils.substring(value, 0, 17) + "...";
+ }
+ }
+ } else if (StringUtils.startsWith(name, "phrase")) {
+ // phrase.DATA: 5个以内纯汉字
+ value = PHRASE_PATTERN.matcher(value).replaceAll("");
+ if (value.isEmpty()) {
+ value = "好";
+ }
+ if (value.length() > 5) {
+ value = StringUtils.substring(value, 0, 5);
+ }
+ }
+
+ datum.setValue(value);
+ return datum;
+ }
+
public String toJson() {
return WxMaGsonBuilder.create().toJson(this);
}
From a49d6e1461752c06b752d2afd8aeeb7e6e78cefe Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Mon, 8 Jun 2026 23:14:35 +0800
Subject: [PATCH 188/189] =?UTF-8?q?:bookmark:=20=E5=8F=91=E5=B8=83=204.8.4?=
=?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
---
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 +-
.../wx-java-open-multi-spring-boot-starter/pom.xml | 2 +-
spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml | 2 +-
.../wx-java-pay-multi-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-aispeech/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 +-
wx-java-bom/pom.xml | 2 +-
39 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/pom.xml b/pom.xml
index 09d30e185f..ea7cf980e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
pom
WxJava - Weixin/Wechat Java SDK
微信开发Java SDK
diff --git a/solon-plugins/pom.xml b/solon-plugins/pom.xml
index 87401a2c97..620193dd4f 100644
--- a/solon-plugins/pom.xml
+++ b/solon-plugins/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.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 d99f9a67c1..682f1531ed 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.8.3.B
+ 4.8.4.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 a26072f8c4..1e002c1e72 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.8.3.B
+ 4.8.4.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 9ccd05578b..48b3fae27e 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.8.3.B
+ 4.8.4.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 367d2a338c..4682d570cd 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.8.3.B
+ 4.8.4.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 9ea8b7caff..7cfdbcbaf2 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.8.3.B
+ 4.8.4.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 0651e3b9b5..a540df9492 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.8.3.B
+ 4.8.4.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 4dc7eae667..359fffe823 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.8.3.B
+ 4.8.4.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 e0c79f79bf..8678e82a10 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.8.3.B
+ 4.8.4.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 4cd4b1ac56..b4d9bc904c 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.8.3.B
+ 4.8.4.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 607c138fd3..e158b9f1a6 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.8.3.B
+ 4.8.4.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 f83c8a8066..b03fa0e22f 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.8.3.B
+ 4.8.4.B
4.0.0
diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml
index 07a1226e6f..6d3c7c7e00 100644
--- a/spring-boot-starters/pom.xml
+++ b/spring-boot-starters/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.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 c3c3441c9b..d3cf55aac1 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.8.3.B
+ 4.8.4.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 f74d3bfaae..d36164d58a 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.8.3.B
+ 4.8.4.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 0cb592a7fc..e4c3b1ba8d 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.8.3.B
+ 4.8.4.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 881064d493..f2b6d12f8e 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.8.3.B
+ 4.8.4.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 b3bd632cad..fc3b490242 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.8.3.B
+ 4.8.4.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 744ba094a1..ebb6deba33 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.8.3.B
+ 4.8.4.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 1088b711e7..9ebb1ee540 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.8.3.B
+ 4.8.4.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 de88f187a7..37bc9faedb 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.8.3.B
+ 4.8.4.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 672cf2e35c..6e3d6da48c 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.8.3.B
+ 4.8.4.B
4.0.0
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml
index dea66a5a35..e1a4509c6f 100644
--- a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.8.3.B
+ 4.8.4.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 22dbd864df..e0d5779c6b 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.8.3.B
+ 4.8.4.B
4.0.0
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml
index c416b5ba40..a2320eca2a 100644
--- a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
wx-java-spring-boot-starters
com.github.binarywang
- 4.8.3.B
+ 4.8.4.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 3c1313bc22..67a26da606 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.8.3.B
+ 4.8.4.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 d9b845adb1..b07dc4276e 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.8.3.B
+ 4.8.4.B
4.0.0
diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml
index 9c23e95add..eb12b9a728 100644
--- a/weixin-graal/pom.xml
+++ b/weixin-graal/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-graal
diff --git a/weixin-java-aispeech/pom.xml b/weixin-java-aispeech/pom.xml
index 2ca8aa84d8..40c6557065 100644
--- a/weixin-java-aispeech/pom.xml
+++ b/weixin-java-aispeech/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-aispeech
diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml
index 0d332daa20..f8eb264ed6 100644
--- a/weixin-java-channel/pom.xml
+++ b/weixin-java-channel/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-channel
diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml
index ad124f8052..b93a52b1d1 100644
--- a/weixin-java-common/pom.xml
+++ b/weixin-java-common/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-common
diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml
index d9d8694352..e789f7a73d 100644
--- a/weixin-java-cp/pom.xml
+++ b/weixin-java-cp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-cp
diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml
index ca426c4e8b..5a8bc55162 100644
--- a/weixin-java-miniapp/pom.xml
+++ b/weixin-java-miniapp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-miniapp
diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml
index b21ac9bb26..1c3b4239b5 100644
--- a/weixin-java-mp/pom.xml
+++ b/weixin-java-mp/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-mp
diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml
index f7701d1809..3d60efa951 100644
--- a/weixin-java-open/pom.xml
+++ b/weixin-java-open/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-open
diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml
index 80893a55ab..f661e62c75 100644
--- a/weixin-java-pay/pom.xml
+++ b/weixin-java-pay/pom.xml
@@ -5,7 +5,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
4.0.0
diff --git a/weixin-java-qidian/pom.xml b/weixin-java-qidian/pom.xml
index b7aa187817..567efd7adb 100644
--- a/weixin-java-qidian/pom.xml
+++ b/weixin-java-qidian/pom.xml
@@ -7,7 +7,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
weixin-java-qidian
diff --git a/wx-java-bom/pom.xml b/wx-java-bom/pom.xml
index 359d552499..4cb9489362 100644
--- a/wx-java-bom/pom.xml
+++ b/wx-java-bom/pom.xml
@@ -6,7 +6,7 @@
com.github.binarywang
wx-java
- 4.8.3.B
+ 4.8.4.B
wx-java-bom
From 85f728d81dbeb7901d679acb166a11f1ceb896ba Mon Sep 17 00:00:00 2001
From: Binary Wang
Date: Tue, 9 Jun 2026 15:32:42 +0800
Subject: [PATCH 189/189] :memo: Remove a sponsorship from README
Removed ccflow sponsorship section from README.
---
README.md | 7 -------
1 file changed, 7 deletions(-)
diff --git a/README.md b/README.md
index ad3e59ace7..026529a1c8 100644
--- a/README.md
+++ b/README.md
@@ -23,13 +23,6 @@
### 特别赞助
-
-
-
-
-
-
-