Quellcode durchsuchen

:new: #2443【微信支付】增加服务商微工卡相关功能接口以及微信批量转账到零钱的服务商接口实现

Co-authored-by: xiaoqiang <dengmingqiang@youmengbang.com>
xiaoguaiYJ vor 3 Jahren
Ursprung
Commit
c1d0b68d32
36 geänderte Dateien mit 5099 neuen und 1 gelöschten Zeilen
  1. 135 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/AuthRecordRequest.java
  2. 213 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/AuthRecordResult.java
  3. 182 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/AuthenticationsResult.java
  4. 83 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/MerchantIncomeRecordsRequest.java
  5. 286 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/MerchantIncomeRecordsResult.java
  6. 97 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PartnerIncomeRecordsRequest.java
  7. 286 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PartnerIncomeRecordsResult.java
  8. 125 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderRequest.java
  9. 111 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderResult.java
  10. 169 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthRequest.java
  11. 110 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthResult.java
  12. 83 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/RelationsRequest.java
  13. 114 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/RelationsResult.java
  14. 100 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/SubFundFlowBillResult.java
  15. 128 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/TokensRequest.java
  16. 97 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/TokensResult.java
  17. 53 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsRequest.java
  18. 239 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
  19. 96 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberRequest.java
  20. 393 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberResult.java
  21. 143 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BillReceiptResult.java
  22. 65 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/DownloadRequest.java
  23. 70 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsRequest.java
  24. 137 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsResult.java
  25. 97 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/MerchantBatchRequest.java
  26. 289 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferRequest.java
  27. 67 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferResult.java
  28. 35 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ReceiptBillRequest.java
  29. 201 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerTransferService.java
  30. 103 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayrollService.java
  31. 14 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
  32. 8 1
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
  33. 311 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PartnerTransferServiceImpl.java
  34. 192 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImpl.java
  35. 139 0
      weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PartnerTransferServiceImplTest.java
  36. 128 0
      weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java

+ 135 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/AuthRecordRequest.java

@@ -0,0 +1,135 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 查询核身记录
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_5.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class AuthRecordRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:应用ID
+   * 变量名:appid
+   * 是否必填:二选一
+   * 类型:string[1, 32]
+   * 描述:
+   *   是服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID(与服务商主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。
+   *  当输入应用ID时,会校验其与服务商商户号的绑定关系。服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "appid")
+  private String appid;
+
+  /**
+   * <pre>
+   * 字段名:子商户应用ID
+   * 变量名:sub_appid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  是特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID(与特约商户主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。当输入子商户应用ID时,会校验其与特约商户号的绑定关系。 服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   * 示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:核身日期
+   * 变量名:authenticate_date
+   * 是否必填:是
+   * 类型:string[8, 8]
+   * 描述:
+   *  query核身日期,一次只能查询一天,最久可查询90天内的记录,格式为“YYYY-MM-DD”
+   *     示例值:2020-12-25
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_date")
+  private String authenticateDate;
+  /**
+   * <pre>
+   * 字段名:核身状态
+   * 变量名:authenticate_state
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   * query核身状态,列表查询仅提供成功状态的核身记录查询,故此字段固定默认值即可
+   *     示例值:AUTHENTICATE_SUCCESS
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_state")
+  private String authenticateState;
+  /**
+   * <pre>
+   * 字段名:本次查询偏移量
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   * query非负整数,表示该次请求资源的起始位置,从0开始计数。调用方选填,默认为0。offset为10,limit为10时,查询第10-19条数据
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private Integer offset;
+  /**
+   * <pre>
+   * 字段名:本次请求最大查询条数
+   * 变量名:limit
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   * query非0非负的整数,该次请求可返回的最大资源条数,默认值为10,最大支持10条。
+   *     示例值:10
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private Integer limit;
+}

+ 213 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/AuthRecordResult.java

@@ -0,0 +1,213 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * <pre>
+ * 查询核身记录
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_5.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class AuthRecordResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  @SerializedName(value = "data")
+  private List<RecordData> dataList;
+
+  @Data
+  @NoArgsConstructor
+  public static class RecordData implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * <pre>
+     * 字段名:商户号
+     * 变量名:mchid
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  微信服务商商户的商户号,由微信支付生成并下发。
+     *  示例值:1111111
+     * </pre>
+     */
+    @SerializedName(value = "mchid")
+    private Integer mchid;
+    /**
+     * <pre>
+     * 字段名:子商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  微信服务商下特约商户的商户号,由微信支付生成并下发
+     *  示例值:111111
+     * </pre>
+     */
+    @SerializedName(value = "sub_mchid")
+    private Integer subMchid;
+    /**
+     * <pre>
+     * 字段名:用户标识
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  用户在商户对应appid下的唯一标识
+     *  示例值:onqOjjmo8wmTOOtSKwXtGjg9Gb58
+     * </pre>
+     */
+    @SerializedName(value = "openid")
+    private Integer openid;
+    /**
+     * <pre>
+     * 字段名:核身渠道
+     * 变量名:authenticate_scene
+     * 是否必填:是
+     * 类型:string[1, 16]
+     * 描述:
+     *  核身渠道,发起核身时的来源渠道,如通过小程序,硬件设备等
+     *         FROM_MINI_APP:来自小程序方式核身
+     *         FROM_HARDWARE:来自硬件设备方式核身
+     *  示例值:FROM_HARDWARE
+     * </pre>
+     */
+    @SerializedName(value = "authenticate_scene")
+    private Integer authenticateScene;
+    /**
+     * <pre>
+     * 字段名:核身渠道标识
+     * 变量名:authenticate_source
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  核身渠道标识,用于定位渠道具体来源,如果是扫码打卡渠道标识就是具体的小程序appid,若是硬件设备,则是设备的序列号等
+     *  示例值:wdiooewl7587443649000
+     * </pre>
+     */
+    @SerializedName(value = "authenticate_source")
+    private Integer authenticateSource;
+    /**
+     * <pre>
+     * 字段名:项目名称
+     * 变量名:project_name
+     * 是否必填:是
+     * 类型:string[1, 12]
+     * 描述:
+     *  该项目的名称
+     *  示例值:某项目
+     * </pre>
+     */
+    @SerializedName(value = "project_name")
+    private Integer projectName;
+    /**
+     * <pre>
+     * 字段名:单位名称
+     * 变量名:employer_name
+     * 是否必填:是
+     * 类型:string[1, 12]
+     * 描述:
+     *    该用户所属的单位名称。
+     *     示例值:某单位名称
+     * </pre>
+     */
+    @SerializedName(value = "employer_name")
+    private String employerName;
+    /**
+     * <pre>
+     * 字段名:核身状态
+     * 变量名:authenticate_state
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *    核身状态
+     *     AUTHENTICATE_PROCESSING:核身中
+     *     AUTHENTICATE_SUCCESS:核身成功
+     *     AUTHENTICATE_FAILED:核身失败
+     *  示例值:AUTHENTICATE_PROCESSING
+     * </pre>
+     */
+    @SerializedName(value = "authenticate_state")
+    private String authenticateState;
+
+    /**
+     * <pre>
+     * 字段名:核身时间
+     * 变量名:authenticate_time
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *   核身时间,遵循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
+     * </pre>
+     */
+    @SerializedName(value = "authenticate_time")
+    private String authenticateTime;
+    /**
+     * <pre>
+     * 字段名:商家核身单号
+     * 变量名:authenticate_number
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  商户系统内部的商家核身单号,要求此参数只能由数字、大小写字母组成,在服务商内部唯一
+     *  示例值:mcdhehfgisdhfjghed39384564i83
+     * </pre>
+     */
+    @SerializedName(value = "authenticate_number")
+    private String authenticateNumber;
+  }
+
+  /**
+   * <pre>
+   * 字段名:总记录条数
+   * 变量名:total_count
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  经过条件筛选,查询到的记录总数
+   *  示例值:9
+   * </pre>
+   */
+  @SerializedName(value = "total_count")
+  private Integer totalCount;
+
+  /**
+   * <pre>
+   * 字段名:记录起始位置
+   * 变量名:offset
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  该次请求资源的起始位置,请求中包含偏移量时应答消息返回相同偏移量,否则返回默认值0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private Integer offset;
+
+  /**
+   * <pre>
+   * 字段名:本次返回条数
+   * 变量名:limit
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  经过条件筛选,本次查询到的记录条数
+   *  示例值:10
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private Integer limit;
+}

+ 182 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/AuthenticationsResult.java

@@ -0,0 +1,182 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 获取核身结果
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_4.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/{authenticate_number}
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class AuthenticationsResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:商户号
+   * 变量名:mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  query微信服务商下特约商户的商户号,由微信支付生成并下发
+   *     示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "mchid")
+  private String mchid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  query微信服务商下特约商户的商户号,由微信支付生成并下发
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   * 示例值:onqOjjmo8wmTOOtSKwXtGjg9Gb58
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:核身渠道
+   * 变量名:authenticate_scene
+   * 是否必填:是
+   * 类型:string[1, 16]
+   * 描述:
+   *   核身渠道,发起核身时的来源渠道,如通过小程序,硬件设备等
+   *     FROM_MINI_APP:来自小程序方式核身
+   *     FROM_HARDWARE:来自硬件设备方式核身
+   *  示例值:onqOjjmo8wmTOOtSKwXtGjg9Gb58
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_scene")
+  private String authenticateScene;
+
+  /**
+   * <pre>
+   * 字段名:核身渠道标识
+   * 变量名:authenticate_source
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *    核身渠道标识,用于定位渠道具体来源,如果是扫码打卡渠道标识就是具体的小程序appid,若是硬件设备,则是设备的序列号等
+   * 示例值:wdiooewl7587443649000
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_source")
+  private String authenticateSource;
+
+  /**
+   * <pre>
+   * 字段名:项目名称
+   * 变量名:project_name
+   * 是否必填:是
+   * 类型:string[1, 12]
+   * 描述:
+   *    该项目的名称
+   *  示例值:某项目
+   * </pre>
+   */
+  @SerializedName(value = "project_name")
+  private String projectName;
+
+  /**
+   * <pre>
+   * 字段名:单位名称
+   * 变量名:employer_name
+   * 是否必填:是
+   * 类型:string[1, 12]
+   * 描述:
+   *    该用户所属的单位名称。
+   *  示例值:某单位名称
+   * </pre>
+   */
+  @SerializedName(value = "employer_name")
+  private String employerName;
+
+  /**
+   * <pre>
+   * 字段名:核身状态
+   * 变量名:authenticate_state
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *    核身状态
+   *     AUTHENTICATE_PROCESSING:核身中
+   *     AUTHENTICATE_SUCCESS:核身成功
+   *     AUTHENTICATE_FAILED:核身失败
+   *  示例值:AUTHENTICATE_PROCESSING
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_state")
+  private String authenticateState;
+  /**
+   * <pre>
+   * 字段名:核身时间
+   * 变量名:authenticate_time
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *   核身时间,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_time")
+  private String authenticateTime;
+  /**
+   * <pre>
+   * 字段名:商家核身单号
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  商户系统内部的商家核身单号,要求此参数只能由数字、大小写字母组成,在服务商内部唯一
+   *  示例值:mcdhehfgisdhfjghed39384564i83
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_number")
+  private String authenticateNumber;
+
+  /**
+   * <pre>
+   * 字段名:核身失败原因
+   * 变量名:authenticate_failed_reason
+   * 是否必填:否
+   * 类型:string[1, 128]
+   * 描述:
+   *  结果为核身失败时的原因描述,仅在失败记录返回
+   *  示例值:人脸验证未通过
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_failed_reason")
+  private String authenticateFailedReason;
+}

+ 83 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/MerchantIncomeRecordsRequest.java

@@ -0,0 +1,83 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 服务商银行来账查询
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_28.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/merchantfund/merchant/income-records
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/7
+ */
+@Data
+@NoArgsConstructor
+public class MerchantIncomeRecordsRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * query需查询银行来账记录商户的账户类型。
+   *     枚举值:
+   *     BASIC:基本账户
+   *     OPERATION:运营账户
+   *     FEES:手续费账户
+   * 示例值:BASIC
+   * </pre>
+   */
+  @SerializedName(value = "account_type")
+  private String accountType;
+  /**
+   * <pre>
+   * 字段名:日期
+   * 变量名:date
+   * 是否必填:是
+   * 类型:string[10, 10]
+   * 描述:
+   * query查询的日期,一次只能查询一天,最久可查询90天内的记录,格式为“YYYY-MM-DD”。
+   * 示例值:2019-06-11
+   * </pre>
+   */
+  @SerializedName(value = "date")
+  private String date;
+  /**
+   * <pre>
+   * 字段名:本次查询偏移量
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   * query非负整数,表示该次请求资源的起始位置,从0开始计数。调用方选填,默认为0。offset为20,limit为100时,查询第20-119条数据。
+   * 示例值:0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private int offset;
+  /**
+   * <pre>
+   * 字段名:本次请求最大查询条数
+   * 变量名:limit
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * query非0非负的整数,该次请求可返回的最大资源条数,最大支持100条。
+   * 示例值:100
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private int limit;
+}

+ 286 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/MerchantIncomeRecordsResult.java

@@ -0,0 +1,286 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * <pre>
+ * 服务商银行来账查询
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_28.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/merchantfund/merchant/income-records
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/7
+ */
+@Data
+@NoArgsConstructor
+public class MerchantIncomeRecordsResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:查询数据总条数
+   * 变量名:total_count
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  经过条件筛选,查询到的银行来账记录总数 。
+   * 示例值:20
+   * </pre>
+   */
+  @SerializedName(value = "total_count")
+  private int totalCount;
+
+  /**
+   * <pre>
+   * 字段名:本次查询偏移量
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   * 该次请求资源的起始位置,请求中包含偏移量时应答消息返回相同偏移量,否则返回默认值0。
+   * 示例值:0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private int offset;
+  /**
+   * <pre>
+   * 字段名:本次请求最大查询条数
+   * 变量名:limit
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * 经过条件筛选,本次查询到的银行来账记录条数。
+   * 示例值:100
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private int limit;
+  /**
+   * <pre>
+   * 字段名:银行来账记录列表
+   * 变量名:data
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *  单次查询返回的银行来账记录列表结果,如果查询结果为空时,则为空数组。
+   * </pre>
+   */
+  @SerializedName(value = "data")
+  private List<PartnerIncomeRecordsResult.IncomeRecordData> incomeRecordDataList;
+
+
+  @Data
+  @NoArgsConstructor
+  public static class IncomeRecordData implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * <pre>
+     * 字段名:商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  需查询银行来账记录列表的商户号
+     * 示例值:2480253391
+     * </pre>
+     */
+    @SerializedName(value = "mchid")
+    private String mchid;
+
+    /**
+     * <pre>
+     * 字段名:账户类型
+     * 变量名:account_type
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  需查询银行来账记录商户的账户类型。
+     *     枚举值:
+     *     BASIC:基本账户
+     *     OPERATION:运营账户
+     *     FEES:手续费账户
+     * 示例值:BASIC
+     * </pre>
+     */
+    @SerializedName(value = "account_type")
+    private String accountType;
+    /**
+     * <pre>
+     * 字段名:银行来账类型
+     * 变量名:income_record_type
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  银行来账类型,后续会有所扩展。
+     *     枚举值:
+     *     OFFLINERECHARGE:转账充值
+     *     ENTERPRISEDIRECTREVENUE:企业直收
+     * 示例值:OFFLINERECHARGE
+     * </pre>
+     */
+    @SerializedName(value = "income_record_type")
+    private String incomeRecordType;
+    /**
+     * <pre>
+     * 字段名:银行来账微信单号
+     * 变量名:income_record_id
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  银行来账的微信单号
+     * 示例值:4200000811202011056138519459
+     * </pre>
+     */
+    @SerializedName(value = "income_record_id")
+    private String incomeRecordId;
+    /**
+     * <pre>
+     * 字段名:银行来账金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  银行来账金额,单位为分,只能为整数。
+     * 示例值:2734921
+     * </pre>
+     */
+    @SerializedName(value = "amount")
+    private String amount;
+    /**
+     * <pre>
+     * 字段名:银行来账完成时间
+     * 变量名:success_time
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  银行来账完成时间,遵循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秒。
+     * 示例值:2017-12-08T00:08:00.00+08:00
+     * </pre>
+     */
+    @SerializedName(value = "success_time")
+    private String successTime;
+    /**
+     * <pre>
+     * 字段名:付款方银行名称
+     * 变量名:bank_name
+     * 是否必填:是
+     * 类型:string[1, 256]
+     * 描述:
+     *  银行来账的付款方银行名称,由于部分银行的数据获取限制,该字段有可能为空。
+     * 示例值:招商银行
+     * </pre>
+     */
+    @SerializedName(value = "bank_name")
+    private String bankName;
+    /**
+     * <pre>
+     * 字段名:付款方银行户名
+     * 变量名:bank_account_name
+     * 是否必填:是
+     * 类型:string[1, 256]
+     * 描述:
+     *  银行来账的付款方银行账户信息,户名为全称、明文,由于部分银行的数据获取限制,该字段有可能为空。
+     * 示例值:北京三快科技有限公司
+     * </pre>
+     */
+    @SerializedName(value = "bank_account_name")
+    private String bankAccountName;
+    /**
+     * <pre>
+     * 字段名:付款方银行卡号
+     * 变量名:bank_account_number
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  四位掩码+付款方银行卡尾号后四位。
+     * 示例值:****6473
+     * </pre>
+     */
+    @SerializedName(value = "bank_account_number")
+    private String bankAccountNumber;
+    /**
+     * <pre>
+     * 字段名:银行备注
+     * 变量名:recharge_remark
+     * 是否必填:是
+     * 类型:string[1, 256]
+     * 描述:
+     *  随银行转账时,商户填入的附言、摘要等信息,目前支持的银行及填写指引请查看各银行对账详情
+     * 示例值:单号:202106010001
+     * </pre>
+     */
+    @SerializedName(value = "recharge_remark")
+    private String rechargeRemark;
+  }
+
+  /**
+   * <pre>
+   * 字段名:分页链接
+   * 变量名:links
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  返回前后页和当前页面的访问链接
+   * </pre>
+   */
+  @SerializedName(value = "links")
+  private List<PartnerIncomeRecordsResult.LinksData> linksDataList;
+
+
+  @Data
+  @NoArgsConstructor
+  public static class LinksData implements Serializable {
+    private static final long serialVersionUID = 109053454401492L;
+
+    /**
+     * <pre>
+     * 字段名:下一页链接
+     * 变量名:next
+     * 是否必填:是
+     * 类型:string[1, 2048]
+     * 描述:
+     *  使用同样的limit进行下一页查询时的相对请求链接,使用方需要自行根据当前域名进行拼接。如果已经到最后时,为空 。
+     * 示例值:/v3/merchantfund/partner/income-records?offset=10&limit=5
+     * </pre>
+     */
+    @SerializedName(value = "next")
+    private String next;
+    /**
+     * <pre>
+     * 字段名:上一页链接
+     * 变量名:prev
+     * 是否必填:是
+     * 类型:string[1, 2048]
+     * 描述:
+     *  使用同样的limit进行上一页查询时的相对请求链接,使用方需要自行根据当前域名进行拼接。如果是第一页,为空。
+     * 示例值:/v3/merchantfund/partner/income-records?offset=0&limit=5
+     * </pre>
+     */
+    @SerializedName(value = "prev")
+    private String prev;
+    /**
+     * <pre>
+     * 字段名:当前链接
+     * 变量名:self
+     * 是否必填:是
+     * 类型:string[1, 2048]
+     * 描述:
+     *  当前的相对请求链接,使用方需要自行根据当前域名进行拼接。
+     * 示例值:/v3/merchantfund/partner/income-records?offset=5&limit=5
+     * </pre>
+     */
+    @SerializedName(value = "self")
+    private String self;
+  }
+}

+ 97 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PartnerIncomeRecordsRequest.java

@@ -0,0 +1,97 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 特约商户银行来账查询
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_27.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/merchantfund/partner/income-records
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/7
+ */
+@Data
+@NoArgsConstructor
+public class PartnerIncomeRecordsRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:特约商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  query需查询银行来账记录列表的特约商户的商户号,该商户号须为服务商的特约商户号。
+   *  示例值:2480253391
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * query需查询银行来账记录商户的账户类型。
+   *     枚举值:
+   *     BASIC:基本账户
+   *     OPERATION:运营账户
+   *     FEES:手续费账户
+   * 示例值:BASIC
+   * </pre>
+   */
+  @SerializedName(value = "account_type")
+  private String accountType;
+  /**
+   * <pre>
+   * 字段名:日期
+   * 变量名:date
+   * 是否必填:是
+   * 类型:string[10, 10]
+   * 描述:
+   * query查询的日期,一次只能查询一天,最久可查询90天内的记录,格式为“YYYY-MM-DD”。
+   * 示例值:2019-06-11
+   * </pre>
+   */
+  @SerializedName(value = "date")
+  private String date;
+  /**
+   * <pre>
+   * 字段名:本次查询偏移量
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   * query非负整数,表示该次请求资源的起始位置,从0开始计数。调用方选填,默认为0。offset为20,limit为100时,查询第20-119条数据。
+   * 示例值:0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private int offset;
+  /**
+   * <pre>
+   * 字段名:本次请求最大查询条数
+   * 变量名:limit
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * query非0非负的整数,该次请求可返回的最大资源条数,最大支持100条。
+   * 示例值:100
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private int limit;
+}

+ 286 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PartnerIncomeRecordsResult.java

@@ -0,0 +1,286 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * <pre>
+ * 特约商户银行来账查询
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_27.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/merchantfund/partner/income-records
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/7
+ */
+@Data
+@NoArgsConstructor
+public class PartnerIncomeRecordsResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:查询数据总条数
+   * 变量名:total_count
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  经过条件筛选,查询到的银行来账记录总数 。
+   * 示例值:20
+   * </pre>
+   */
+  @SerializedName(value = "total_count")
+  private int totalCount;
+
+  /**
+   * <pre>
+   * 字段名:本次查询偏移量
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   * 该次请求资源的起始位置,请求中包含偏移量时应答消息返回相同偏移量,否则返回默认值0。
+   * 示例值:0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private int offset;
+  /**
+   * <pre>
+   * 字段名:本次请求最大查询条数
+   * 变量名:limit
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * 经过条件筛选,本次查询到的银行来账记录条数。
+   * 示例值:100
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private int limit;
+  /**
+   * <pre>
+   * 字段名:银行来账记录列表
+   * 变量名:data
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *  单次查询返回的银行来账记录列表结果,如果查询结果为空时,则为空数组。
+   * </pre>
+   */
+  @SerializedName(value = "data")
+  private List<IncomeRecordData> incomeRecordDataList;
+
+
+  @Data
+  @NoArgsConstructor
+  public static class IncomeRecordData implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * <pre>
+     * 字段名:特约商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  需查询银行来账记录列表的特约商户的商户号,该商户号须为服务商的特约商户号。
+     * 示例值:2480253391
+     * </pre>
+     */
+    @SerializedName(value = "sub_mchid")
+    private String subMchid;
+
+    /**
+     * <pre>
+     * 字段名:账户类型
+     * 变量名:account_type
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  需查询银行来账记录商户的账户类型。
+     *     枚举值:
+     *     BASIC:基本账户
+     *     OPERATION:运营账户
+     *     FEES:手续费账户
+     * 示例值:BASIC
+     * </pre>
+     */
+    @SerializedName(value = "account_type")
+    private String accountType;
+    /**
+     * <pre>
+     * 字段名:银行来账类型
+     * 变量名:income_record_type
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  银行来账类型,后续会有所扩展。
+     *     枚举值:
+     *     OFFLINERECHARGE:转账充值
+     *     ENTERPRISEDIRECTREVENUE:企业直收
+     * 示例值:OFFLINERECHARGE
+     * </pre>
+     */
+    @SerializedName(value = "income_record_type")
+    private String incomeRecordType;
+    /**
+     * <pre>
+     * 字段名:银行来账微信单号
+     * 变量名:income_record_id
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  银行来账的微信单号
+     * 示例值:4200000811202011056138519459
+     * </pre>
+     */
+    @SerializedName(value = "income_record_id")
+    private String incomeRecordId;
+    /**
+     * <pre>
+     * 字段名:银行来账金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  银行来账金额,单位为分,只能为整数。
+     * 示例值:2734921
+     * </pre>
+     */
+    @SerializedName(value = "amount")
+    private String amount;
+    /**
+     * <pre>
+     * 字段名:银行来账完成时间
+     * 变量名:success_time
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  银行来账完成时间,遵循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秒。
+     * 示例值:2017-12-08T00:08:00.00+08:00
+     * </pre>
+     */
+    @SerializedName(value = "success_time")
+    private String successTime;
+    /**
+     * <pre>
+     * 字段名:付款方银行名称
+     * 变量名:bank_name
+     * 是否必填:是
+     * 类型:string[1, 256]
+     * 描述:
+     *  银行来账的付款方银行名称,由于部分银行的数据获取限制,该字段有可能为空。
+     * 示例值:招商银行
+     * </pre>
+     */
+    @SerializedName(value = "bank_name")
+    private String bankName;
+    /**
+     * <pre>
+     * 字段名:付款方银行户名
+     * 变量名:bank_account_name
+     * 是否必填:是
+     * 类型:string[1, 256]
+     * 描述:
+     *  银行来账的付款方银行账户信息,户名为全称、明文,由于部分银行的数据获取限制,该字段有可能为空。
+     * 示例值:北京三快科技有限公司
+     * </pre>
+     */
+    @SerializedName(value = "bank_account_name")
+    private String bankAccountName;
+    /**
+     * <pre>
+     * 字段名:付款方银行卡号
+     * 变量名:bank_account_number
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  四位掩码+付款方银行卡尾号后四位。
+     * 示例值:****6473
+     * </pre>
+     */
+    @SerializedName(value = "bank_account_number")
+    private String bankAccountNumber;
+    /**
+     * <pre>
+     * 字段名:银行备注
+     * 变量名:recharge_remark
+     * 是否必填:是
+     * 类型:string[1, 256]
+     * 描述:
+     *  随银行转账时,商户填入的附言、摘要等信息,目前支持的银行及填写指引请查看各银行对账详情
+     * 示例值:单号:202106010001
+     * </pre>
+     */
+    @SerializedName(value = "recharge_remark")
+    private String rechargeRemark;
+  }
+
+  /**
+   * <pre>
+   * 字段名:分页链接
+   * 变量名:links
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  返回前后页和当前页面的访问链接
+   * </pre>
+   */
+  @SerializedName(value = "links")
+  private List<LinksData> linksDataList;
+
+
+  @Data
+  @NoArgsConstructor
+  public static class LinksData implements Serializable {
+    private static final long serialVersionUID = 109053454401492L;
+
+    /**
+     * <pre>
+     * 字段名:下一页链接
+     * 变量名:next
+     * 是否必填:是
+     * 类型:string[1, 2048]
+     * 描述:
+     *  使用同样的limit进行下一页查询时的相对请求链接,使用方需要自行根据当前域名进行拼接。如果已经到最后时,为空 。
+     * 示例值:/v3/merchantfund/partner/income-records?offset=10&limit=5
+     * </pre>
+     */
+    @SerializedName(value = "next")
+    private String next;
+    /**
+     * <pre>
+     * 字段名:上一页链接
+     * 变量名:prev
+     * 是否必填:是
+     * 类型:string[1, 2048]
+     * 描述:
+     *  使用同样的limit进行上一页查询时的相对请求链接,使用方需要自行根据当前域名进行拼接。如果是第一页,为空。
+     * 示例值:/v3/merchantfund/partner/income-records?offset=0&limit=5
+     * </pre>
+     */
+    @SerializedName(value = "prev")
+    private String prev;
+    /**
+     * <pre>
+     * 字段名:当前链接
+     * 变量名:self
+     * 是否必填:是
+     * 类型:string[1, 2048]
+     * 描述:
+     *  当前的相对请求链接,使用方需要自行根据当前域名进行拼接。
+     * 示例值:/v3/merchantfund/partner/income-records?offset=5&limit=5
+     * </pre>
+     */
+    @SerializedName(value = "self")
+    private String self;
+  }
+}

+ 125 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderRequest.java

@@ -0,0 +1,125 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 微工卡核身预下单
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_3.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order
+ * 请求方式:POST
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class PreOrderRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:应用ID
+   * 变量名:appid
+   * 是否必填:二选一
+   * 类型:string[1, 32]
+   * 描述:
+   *   是服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID(与服务商主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。
+   *  当输入应用ID时,会校验其与服务商商户号的绑定关系。服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "appid")
+  private String appid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   * 示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:子商户应用ID
+   * 变量名:sub_appid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  是特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID(与特约商户主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。当输入子商户应用ID时,会校验其与特约商户号的绑定关系。 服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+
+  /**
+   * <pre>
+   * 字段名:商家核身单号
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  body商户系统内部的商家核身单号,要求此参数只能由数字、大小写字母组成,在服务商内部唯一
+   *  示例值:mcdhehfgisdhfjghed39384564i83
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_number")
+  private String authenticateNumber;
+
+  /**
+   * <pre>
+   * 字段名:项目名称
+   * 变量名:project_name
+   * 是否必填:是
+   * 类型:string[1, 12]
+   * 描述:
+   * body该项目的名称
+   * 示例值:某项目
+   * </pre>
+   */
+  @SerializedName(value = "project_name")
+  private String projectName;
+  /**
+   * <pre>
+   * 字段名:单位名称
+   * 变量名:employer_name
+   * 是否必填:是
+   * 类型:string[1, 12]
+   * 描述:
+   * body该用户所属的单位名称
+   * 示例值:某单位名称
+   * </pre>
+   */
+  @SerializedName(value = "employer_name")
+  private String employerName;
+
+}

+ 111 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderResult.java

@@ -0,0 +1,111 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 微工卡核身预下单
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_3.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order
+ * 请求方式:POST
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class PreOrderResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:商家核身单号
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  商户系统内部的商家核身单号,要求此参数只能由数字、大小写字母组成,在服务商内部唯一
+   *  示例值:mcdhehfgisdhfjghed39384564i83
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_number")
+  private String authenticateNumber;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:服务商商户号
+   * 变量名:mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商商户的商户号,由微信支付生成并下发。
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "mchid")
+  private String mchid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:授权token
+   * 变量名:token
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  授权token
+   *  示例值:abcdefghijklmn
+   * </pre>
+   */
+  @SerializedName(value = "token")
+  private String token;
+
+  /**
+   * <pre>
+   * 字段名:token有效时间
+   * 变量名:expires_in
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * token有效时间,单位秒
+   * 示例值:1800
+   * </pre>
+   */
+  @SerializedName(value = "expires_in")
+  private Integer expiresIn;
+
+}

+ 169 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthRequest.java

@@ -0,0 +1,169 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 微工卡核身预下单(流程中完成授权)
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_1.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order-with-auth
+ * 请求方式:POST
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class PreOrderWithAuthRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:应用ID
+   * 变量名:appid
+   * 是否必填:二选一
+   * 类型:string[1, 32]
+   * 描述:
+   *   是服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID(与服务商主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。
+   *  当输入应用ID时,会校验其与服务商商户号的绑定关系。服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "appid")
+  private String appid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   * 示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:子商户应用ID
+   * 变量名:sub_appid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  是特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID(与特约商户主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。当输入子商户应用ID时,会校验其与特约商户号的绑定关系。 服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+
+  /**
+   * <pre>
+   * 字段名:商家核身单号
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  商户系统内部的商家核身单号,要求此参数只能由数字、大小写字母组成,在服务商内部唯一
+   *  示例值:mcdhehfgisdhfjghed39384564i83
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_number")
+  private String authenticateNumber;
+
+  /**
+   * <pre>
+   * 字段名:项目名称
+   * 变量名:project_name
+   * 是否必填:是
+   * 类型:string[1, 12]
+   * 描述:
+   *  该项目的名称
+   *  示例值:某项目
+   * </pre>
+   */
+  @SerializedName(value = "project_name")
+  private String projectName;
+  /**
+   * <pre>
+   * 字段名:用工单位名称
+   * 变量名:employer_name
+   * 是否必填:是
+   * 类型:string[1, 12]
+   * 描述:
+   *    该用户所属的单位名称。
+   *     示例值:某单位名称
+   * </pre>
+   */
+  @SerializedName(value = "employer_name")
+  private String employerName;
+
+  /**
+   * <pre>
+   * 字段名:用户实名
+   * 变量名:user_name
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  用户证件号,该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+   * 示例值:7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5
+   * </pre>
+   */
+  @SerializedName(value = "user_name")
+  private String userName;
+
+  /**
+   * <pre>
+   * 字段名:用户证件号
+   * 变量名:id_card_number
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  用户证件号,该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+   * 示例值:7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5
+   * </pre>
+   */
+  @SerializedName(value = "id_card_number")
+  private String idCardNumber;
+
+  /**
+   * <pre>
+   * 字段名:用工类型
+   * 变量名:employment_type
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  微工卡服务仅支持用于与商户有用工关系的用户,需明确用工类型;参考值:
+   * LONG_TERM_EMPLOYMENT:长期用工,
+   * SHORT_TERM_EMPLOYMENT: 短期用工,
+   * COOPERATION_EMPLOYMENT:合作关系
+   * 示例值:LONG_TERM_EMPLOYMENT
+   * </pre>
+   */
+  @SerializedName(value = "employment_type")
+  private String employmentType;
+}

+ 110 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/PreOrderWithAuthResult.java

@@ -0,0 +1,110 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 微工卡核身预下单(流程中完成授权)
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_1.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order-with-auth
+ * 请求方式:POST
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class PreOrderWithAuthResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:商家核身单号
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  商户系统内部的商家核身单号,要求此参数只能由数字、大小写字母组成,在服务商内部唯一
+   *  示例值:mcdhehfgisdhfjghed39384564i83
+   * </pre>
+   */
+  @SerializedName(value = "authenticate_number")
+  private String authenticateNumber;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:authenticate_number
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   * 示例值:onqOjjmo8wmTOOtSKwXtGjg9Gb58
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:商户号
+   * 变量名:mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商商户的商户号,由微信支付生成并下发
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "mchid")
+  private Integer mchid;
+
+  /**
+   * <pre>
+   * 字段名:特约商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   *  示例值:1900000109
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:授权token
+   * 变量名:token
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  授权token
+   *  示例值:abcdefghijklmn
+   * </pre>
+   */
+  @SerializedName(value = "token")
+  private String token;
+
+  /**
+   * <pre>
+   * 字段名:token有效时间
+   * 变量名:expires_in
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * token有效时间,单位秒
+   * 示例值:300
+   * </pre>
+   */
+  @SerializedName(value = "expires_in")
+  private Integer expiresIn;
+}

+ 83 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/RelationsRequest.java

@@ -0,0 +1,83 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 查询微工卡授权关系
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_2.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/relations/{openid}
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class RelationsRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   * 示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:应用ID
+   * 变量名:appid
+   * 是否必填:二选一
+   * 类型:string[1, 32]
+   * 描述:
+   *   是服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID(与服务商主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。
+   *  当输入应用ID时,会校验其与服务商商户号的绑定关系。服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "appid")
+  private String appid;
+
+  /**
+   * <pre>
+   * 字段名:子商户应用ID
+   * 变量名:sub_appid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  是特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID(与特约商户主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。当输入子商户应用ID时,会校验其与特约商户号的绑定关系。 服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+}

+ 114 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/RelationsResult.java

@@ -0,0 +1,114 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 查询微工卡授权关系
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_2.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/relations/{openid}
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class RelationsResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:服务商商户号
+   * 变量名:mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商商户的商户号,由微信支付生成并下发。
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "mchid")
+  private String mchid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:授权状态
+   * 变量名:authorize_state
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  授权状态:
+   *      UNAUTHORIZED:未授权
+   *      AUTHORIZED:已授权
+   *      DEAUTHORIZED:已取消授权
+   * 示例值:UNAUTHORIZED
+   * </pre>
+   */
+  @SerializedName(value = "authorize_state")
+  private String authorizeState;
+
+  /**
+   * <pre>
+   * 字段名:授权时间
+   * 变量名:authorize_time
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  授权时间,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "authorize_time")
+  private String authorizeTime;
+
+  /**
+   * <pre>
+   * 字段名:取消授权时间
+   * 变量名:deauthorize_time
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  取消授权时间,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "deauthorize_time")
+  private String deauthorizeTime;
+
+}

+ 100 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/SubFundFlowBillResult.java

@@ -0,0 +1,100 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 申请单个子商户资金账单
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_25.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/bill/sub-merchant-fundflowbill
+ * 请求方式:GET
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/7
+ */
+@Data
+@NoArgsConstructor
+public class SubFundFlowBillResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  query下载指定子商户的账单。
+   *  示例值:19000000001
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:账单日期
+   * 变量名:bill_date
+   * 是否必填:是
+   * 类型:string[10, 10]
+   * 描述:
+   *  query格式YYYY-MM-DD
+   *  示例值:2019-06-11
+   * </pre>
+   */
+  @SerializedName(value = "bill_date")
+  private String billDate;
+
+  /**
+   * <pre>
+   * 字段名:资金账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string[1, 16]
+   * 描述:
+   * query枚举值:
+   *     BASIC:基本账户
+   *     OPERATION:运营账户
+   *     FEES:手续费账户
+   * 示例值:BASIC
+   * </pre>
+   */
+  @SerializedName(value = "account_type")
+  private String accountType;
+  /**
+   * <pre>
+   * 字段名:加密算法
+   * 变量名:algorithm
+   * 是否必填:是
+   * 类型:string[1, 31]
+   * 描述:
+   * query枚举值:
+   *     AEAD_AES_256_GCM:AEAD_AES_256_GCM加密算法
+   * 示例值:AEAD_AES_256_GCM
+   * </pre>
+   */
+  @SerializedName(value = "algorithm")
+  private String algorithm;
+  /**
+   * <pre>
+   * 字段名:压缩格式
+   * 变量名:tar_type
+   * 是否必填:否
+   * 类型:string[1, 8]
+   * 描述:
+   * query不填则以不压缩的方式返回数据流
+   *     枚举值:
+   *     GZIP:返回格式为.gzip的压缩包账单
+   * 示例值:GZIP
+   * </pre>
+   */
+  @SerializedName(value = "tar_type")
+  private String tarType;
+}

+ 128 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/TokensRequest.java

@@ -0,0 +1,128 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 生成授权token
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_1.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/tokens
+ * 请求方式:POST
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class TokensRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:应用ID
+   * 变量名:appid
+   * 是否必填:二选一
+   * 类型:string[1, 32]
+   * 描述:
+   *   是服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID(与服务商主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。
+   *  当输入应用ID时,会校验其与服务商商户号的绑定关系。服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "appid")
+  private String appid;
+
+  /**
+   * <pre>
+   * 字段名:子商户应用ID
+   * 变量名:sub_appid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  是特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID(与特约商户主体一致),登录平台为mp.weixin.qq.com或open.weixin.qq.com。当输入子商户应用ID时,会校验其与特约商户号的绑定关系。 服务商应用ID和与子商户应用ID至少输入一个,且必须要有拉起微工卡时使用的APPID。
+   *  示例值:wxa1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   * 示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:用户实名
+   * 变量名:user_name
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  用户证件号,该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+   * 示例值:7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5
+   * </pre>
+   */
+  @SerializedName(value = "user_name")
+  private String userName;
+
+  /**
+   * <pre>
+   * 字段名:用户证件号
+   * 变量名:id_card_number
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  用户证件号,该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+   * 示例值:7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5
+   * </pre>
+   */
+  @SerializedName(value = "id_card_number")
+  private String idCardNumber;
+
+  /**
+   * <pre>
+   * 字段名:用工类型
+   * 变量名:employment_type
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  微工卡服务仅支持用于与商户有用工关系的用户,需明确用工类型;参考值:
+   * LONG_TERM_EMPLOYMENT:长期用工,
+   * SHORT_TERM_EMPLOYMENT: 短期用工,
+   * COOPERATION_EMPLOYMENT:合作关系
+   * 示例值:LONG_TERM_EMPLOYMENT
+   * </pre>
+   */
+  @SerializedName(value = "employment_type")
+  private String employmentType;
+}

+ 97 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/payroll/TokensResult.java

@@ -0,0 +1,97 @@
+package com.github.binarywang.wxpay.bean.marketing.payroll;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 生成授权token
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_1.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/tokens
+ * 请求方式:POST
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Data
+@NoArgsConstructor
+public class TokensResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   *  用户在商户对应appid下的唯一标识
+   *  示例值:9x111111
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+
+  /**
+   * <pre>
+   * 字段名:服务商商户号
+   * 变量名:mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商商户的商户号,由微信支付生成并下发。
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "mchid")
+  private String mchid;
+
+  /**
+   * <pre>
+   * 字段名:子商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信服务商下特约商户的商户号,由微信支付生成并下发
+   *  示例值:1111111
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:授权token
+   * 变量名:token
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   *  授权token
+   *  示例值:abcdefghijklmn
+   * </pre>
+   */
+  @SerializedName(value = "token")
+  private String token;
+
+  /**
+   * <pre>
+   * 字段名:token有效时间
+   * 变量名:expires_in
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * token有效时间,单位秒
+   * 示例值:1800
+   * </pre>
+   */
+  @SerializedName(value = "expires_in")
+  private Integer expiresIn;
+
+}

+ 53 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsRequest.java

@@ -0,0 +1,53 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 微信支付明细单号查询明细单API
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_3.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}
+ * 请求方式:GET
+ * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class BatchDetailsRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:微信支付批次单号
+   * 变量名:need_query_detail
+   * 是否必填:是
+   * 类型:string[32, 64]
+   * 描述:
+   *  path微信支付批次单号,微信商家转账系统返回的唯一标识
+   *  示例值:1030000071100999991182020050700019480001
+   * </pre>
+   */
+  @SerializedName(value = "batch_id")
+  private String batchId;
+  /**
+   * <pre>
+   * 字段名:微信明细单号
+   * 变量名:need_query_detail
+   * 是否必填:是
+   * 类型:string[32, 64]
+   * 描述:
+   *  path微信支付系统内部区分转账批次单下不同转账明细单的唯一标识
+   *  示例值:1040000071100999991182020050700019500100
+   * </pre>
+   */
+  @SerializedName(value = "detail_id")
+  private String detailId;
+}

+ 239 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java

@@ -0,0 +1,239 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <pre>
+ * 微信支付明细单号查询明细单API
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_3.shtml
+ *
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}
+ * 请求方式:GET
+ * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class BatchDetailsResult implements Serializable {
+  public static final float serialVersionUID = 1L;
+
+  @Override
+  public String toString() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * <pre>
+   * 字段名:服务商商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信支付分配的商户号,此处为服务商商户号
+   *  示例值:1900001109
+   * </pre>
+   */
+  @SerializedName(value = "sp_mchid")
+  private String spMchid;
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  商户系统内部的商家批次单号,在商户系统内部唯一
+   *     示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+  /**
+   * <pre>
+   * 字段名:微信支付批次单号
+   * 变量名:batch_id
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  微信支付批次单号,微信商家转账系统返回的唯一标识
+   *     示例值:1030000071100999991182020050700019480001
+   * </pre>
+   */
+  @SerializedName(value = "batch_id")
+  private String batchId;
+  /**
+   * <pre>
+   * 字段名:商户的appid
+   * 变量名:
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   * 微信分配的特约商户公众账号ID。特约商户授权类型为 INFORMATION_AUTHORIZATION_TYPE和
+   *     INFORMATION_AND_FUND_AUTHORIZATION_TYPE时对应的是特约商户的appid,
+   *     特约商户授权类型为FUND_AUTHORIZATION_TYPE时为服务商的appid
+   * 例值:wxf636efh567hg4356
+   * </pre>
+   */
+  @Expose
+  @SerializedName(value = "appid")
+  private String appId;
+  /**
+   * <pre>
+   * 字段名:商家明细单号
+   * 变量名:out_detail_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   * 商户系统内部区分转账批次单下不同转账明细单的唯一标识
+   *     示例值:x23zy545Bd5436
+   * </pre>
+   */
+  @SerializedName(value = "out_detail_no")
+  private String outDetailNo;
+  /**
+   * <pre>
+   * 字段名:微信支付明细单号
+   * 变量名:detail_id
+   * 是否必填:是
+   * 类型:string[32, 64]
+   * 描述:
+   * 微信支付系统内部区分转账批次单下不同转账明细单的唯一标识
+   *     示例值:1040000071100999991182020050700019500100
+   * </pre>
+   */
+  @SerializedName(value = "detail_id")
+  private String detailId;
+  /**
+   * <pre>
+   * 字段名:明细状态
+   * 变量名:detail_status
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * 枚举值:
+   *     PROCESSING:转账中。正在处理中,转账结果尚未明确
+   *     SUCCESS:转账成功
+   *     FAIL:转账失败。需要确认失败原因后,再决定是否重新发起对该笔明细单的转账(并非整个转账批次单)
+   * 示例值:SUCCESS
+   * </pre>
+   */
+  @SerializedName(value = "detail_status")
+  private String detailStatus;
+  /**
+   * <pre>
+   * 字段名:转账金额
+   * 变量名:transfer_amount
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   * 转账金额单位为“分”
+   *     示例值:200000
+   * </pre>
+   */
+  @SerializedName(value = "transfer_amount")
+  private Integer transferAmount;
+  /**
+   * <pre>
+   * 字段名:转账备注
+   * 变量名:transfer_remark
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
+   *     示例值:2020年4月报销
+   * </pre>
+   */
+  @SerializedName(value = "transfer_remark")
+  private String transferRemark;
+  /**
+   * <pre>
+   * 字段名:明细失败原因
+   * 变量名:fail_reason
+   * 是否必填:否
+   * 类型:string[1, 64]
+   * 描述:
+   * 如果转账失败则有失败原因:
+   *     ACCOUNT_FROZEN:账户冻结
+   *     REAL_NAME_CHECK_FAIL:用户未实名
+   *     NAME_NOT_CORRECT:用户姓名校验失败
+   *     OPENID_INVALID:Openid校验失败
+   *     TRANSFER_QUOTA_EXCEED:超过用户单笔收款额度
+   *     DAY_RECEIVED_QUOTA_EXCEED:超过用户单日收款额度
+   *     MONTH_RECEIVED_QUOTA_EXCEED:超过用户单月收款额度
+   *     DAY_RECEIVED_COUNT_EXCEED:超过用户单日收款次数
+   *     PRODUCT_AUTH_CHECK_FAIL:产品权限校验失败
+   *     OVERDUE_CLOSE:转账关闭
+   *     ID_CARD_NOT_CORRECT:用户身份证校验失败
+   *     ACCOUNT_NOT_EXIST:用户账户不存在
+   *     TRANSFER_RISK:转账存在风险
+   * 示例值:ACCOUNT_FROZEN
+   * </pre>
+   */
+  @SerializedName(value = "fail_reason")
+  private String failReason;
+  /**
+   * <pre>
+   * 字段名:收款用户openid
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string[1, 64]
+   * 描述:
+   * 收款用户openid。如果转账特约商户授权类型是INFORMATION_AUTHORIZATION_TYPE,对应的是特约商户公众号下的openid;
+   *     如果转账特约商户授权类型是FUND_AUTHORIZATION_TYPE,对应的是服务商商户公众号下的openid。
+   * 示例值:o-MYE42l80oelYMDE34nYD456Xoy
+   * </pre>
+   */
+  @SerializedName(value = "openid")
+  private String openid;
+  /**
+   * <pre>
+   * 字段名:收款用户姓名
+   * 变量名:username
+   * 是否必填:是
+   * 类型:string[1, 1024]
+   * 描述:
+   * 1、收款方姓名。采用标准RSA算法,公钥由微信侧提供
+   * 2、该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+   *     示例值:757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45
+   * </pre>
+   */
+  @SerializedName(value = "username")
+  private String userName;
+  /**
+   * <pre>
+   * 字段名:转账发起时间
+   * 变量名:initiate_time
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * 转账发起的时间,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "initiate_time")
+  private String initiateTime;
+  /**
+   * <pre>
+   * 字段名:明细更新时间
+   * 变量名:initiate_time
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * 明细最后一次状态变更的时间,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "update_time")
+  private Date updateTime;
+}

+ 96 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberRequest.java

@@ -0,0 +1,96 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ * 微信支付批次单号查询批次单API
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_2.shtml
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class BatchNumberRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:微信支付批次单号
+   * 变量名:batch_id
+   * 是否必填:是
+   * 类型:string[32, 64]
+   * 描述:
+   *  path微信支付批次单号,微信商家转账系统返回的唯一标识
+   *  示例值:1030000071100999991182020050700019480001
+   * </pre>
+   */
+  @SerializedName(value = "batch_id")
+  private String batchId;
+
+  /**
+   * <pre>
+   * 字段名:是否查询转账明细单
+   * 变量名:need_query_detail
+   * 是否必填:是
+   * 类型:boolean 默认否
+   * 描述:
+   *  商户可选择是否查询指定状态的转账明细单,当转账批次单状态为“FINISHED”(已完成)时,才会返回满足条件的转账明细单
+   *  示例值:true
+   * </pre>
+   */
+  @SerializedName(value = "need_query_detail")
+  private Boolean needQueryDetail;
+
+  /**
+   * <pre>
+   * 字段名:请求资源起始位置
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  query该次请求资源的起始位置。返回的明细是按照设置的明细条数进行分页展示的,一次查询可能无法返回所有明细,我们使用该参数标识查询开始位置,默认值为0
+   *  示例值:0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private Integer offset;
+
+  /**
+   * <pre>
+   * 字段名:最大资源条数
+   * 变量名:limit
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  query该次请求可返回的最大明细条数,最小20条,最大100条,不传则默认20条。不足20条按实际条数返回
+   * 示例值:20
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private Integer limit;
+
+  /**
+   * <pre>
+   * 字段名:明细状态
+   * 变量名:detail_status
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  query查询指定状态的转账明细单,不传没有明细状态信息返回。当need_query_detail为true时,该字段必填
+   *  枚举值:
+   *     ALL:全部。需要同时查询转账成功和转账失败的明细单
+   *     SUCCESS:转账成功。只查询转账成功的明细单
+   *     FAIL:转账失败。只查询转账失败的明细单
+   *  示例值:FAIL
+   * </pre>
+   */
+  @SerializedName(value = "detail_status")
+  private String detailStatus;
+}

+ 393 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberResult.java

@@ -0,0 +1,393 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * <pre>
+ * 微信支付批次单号查询批次单API
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_2.shtml
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class BatchNumberResult implements Serializable {
+  public static final float serialVersionUID = 1L;
+
+  @Override
+  public String toString() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * <pre>
+   * 字段名:服务商商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信支付分配的服务商商户号
+   *  示例值:1900001109
+   * </pre>
+   */
+  @SerializedName(value = "sp_mchid")
+  private String spMchid;
+
+  /**
+   * <pre>
+   * 字段名:特约商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信支付分配的特约商户号
+   *  示例值:1900000109
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  商户系统内部的商家批次单号,在商户系统内部唯一
+   *  示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:微信支付批次单号
+   * 变量名:batch_id
+   * 是否必填:是
+   * 类型:string[32, 64]
+   * 描述:
+   *  微信支付批次单号,微信商家转账系统返回的唯一标识
+   *  示例值:1030000071100999991182020050700019480001
+   * </pre>
+   */
+  @SerializedName(value = "batch_id")
+  private String batchId;
+  /**
+   * <pre>
+   * 字段名:特约商户appid
+   * 变量名:sub_appid
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  微信分配的特约商户公众账号ID。特约商户appid
+   *   示例值:wxf636efh567hg4356
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+  /**
+   * <pre>
+   * 字段名:批次状态
+   * 变量名:batch_status
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  枚举值:
+   *     WAIT_PAY:待付款,商户员工确认付款阶段。
+   *     ACCEPTED:已受理。批次已受理成功,若发起批量转账的30分钟后,转账批次单仍处于该状态,可能原因是商户账户余额不足等。商户可查询账户资金流水,若该笔转账批次单的扣款已经发生,则表示批次已经进入转账中,请再次查单确认
+   *     PROCESSING:转账中。已开始处理批次内的转账明细单
+   *     FINISHED:已完成。批次内的所有转账明细单都已处理完成
+   *     CLOSED:已关闭。可查询具体的批次关闭原因确认
+   *  示例值:ACCEPTED
+   * </pre>
+   */
+  @SerializedName(value = "batch_status")
+  private String batchStatus;
+
+  /**
+   * <pre>
+   * 字段名:批次类型
+   * 变量名:batch_type
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  枚举值:
+   *     API:API方式发起
+   *     WEB:页面方式发起
+   * 示例值:API
+   * </pre>
+   */
+  @SerializedName(value = "batch_type")
+  private String batchType;
+
+  /**
+   * <pre>
+   * 字段名:特约商户授权类型
+   * 变量名:authorization_type
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  特约商户授权类型:
+   *     INFORMATION_AUTHORIZATION_TYPE:信息授权类型
+   *     FUND_AUTHORIZATION_TYPE:资金授权类型
+   *     INFORMATION_AND_FUND_AUTHORIZATION_TYPE:信息和资金授权类型
+   * 示例值:INFORMATION_AUTHORIZATION_TYPE
+   * </pre>
+   */
+  @SerializedName(value = "authorization_type")
+  private String authorizationType;
+
+  /**
+   * <pre>
+   * 字段名:批次名称
+   * 变量名:batch_name
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   * 示例值:2019年1月深圳分部报销单
+   * </pre>
+   */
+  @SerializedName(value = "batch_name")
+  private String batchName;
+
+  /**
+   * <pre>
+   * 字段名:批次备注
+   * 变量名:batch_remark
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *      转账说明,UTF8编码,最多允许32个字符
+   *  示例值:2019年1月深圳分部报销单
+   * </pre>
+   */
+  @SerializedName(value = "batch_remark")
+  private String batchRemark;
+
+  /**
+   * <pre>
+   * 字段名:批次关闭原因
+   * 变量名:close_reason
+   * 是否必填:否
+   * 类型:string[1, 64]
+   * 描述:
+   *    如果批次单状态为“CLOSED”(已关闭),则有关闭原因:
+   *     MERCHANT_REVOCATION:商户主动撤销
+   *     OVERDUE_CLOSE:系统超时关闭
+   * 示例值:OVERDUE_CLOSE
+   * </pre>
+   */
+  @SerializedName(value = "close_reason")
+  private String closeReason;
+
+  /**
+   * <pre>
+   * 字段名:转账总金额
+   * 变量名:total_amount
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *   转账金额单位为“分”
+   * 示例值:4000000
+   * </pre>
+   */
+  @SerializedName(value = "total_amount")
+  private Integer totalAmount;
+
+  /**
+   * <pre>
+   * 字段名:转账总笔数
+   * 变量名:total_num
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *   一个转账批次单最多发起三千笔转账
+   * 示例值:200
+   * </pre>
+   */
+  @SerializedName(value = "total_num")
+  private Integer totalNum;
+  /**
+   * <pre>
+   * 字段名:批次创建时间
+   * 变量名:create_time
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  批次受理成功时返回,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "create_time")
+  private String createTime;
+  /**
+   * <pre>
+   * 字段名:批次更新时间
+   * 变量名:update_time
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  批次最近一次状态变更的时间,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "update_time")
+  private String updateTime;
+  /**
+   * <pre>
+   * 字段名:转账成功金额
+   * 变量名:success_amount
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  转账成功的金额,单位为“分”。当批次状态为“PROCESSING”(转账中)时,转账成功金额随时可能变化
+   *  示例值:3900000
+   * </pre>
+   */
+  @SerializedName(value = "success_amount")
+  private Integer successAmount;
+  /**
+   * <pre>
+   * 字段名:转账成功笔数
+   * 变量名:success_num
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  转账成功的笔数。当批次状态为“PROCESSING”(转账中)时,转账成功笔数随时可能变化
+   * 示例值:199
+   * </pre>
+   */
+  @SerializedName(value = "success_num")
+  private Integer successNum;
+  /**
+   * <pre>
+   * 字段名:转账失败金额
+   * 变量名:fail_amount
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  转账失败的金额,单位为“分”
+   *     示例值:100000
+   * </pre>
+   */
+  @SerializedName(value = "fail_amount")
+  private Integer failAmount;
+  /**
+   * <pre>
+   * 字段名:转账失败笔数
+   * 变量名:fail_amount
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  转账失败的笔数
+   *     示例值:1
+   * </pre>
+   */
+  @SerializedName(value = "fail_num")
+  private Integer failNum;
+  /**
+   * <pre>
+   * 字段名:转账明细单列表
+   * 变量名:transfer_detail_list
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  当批次状态为“FINISHED”(已完成),且成功查询到转账明细单时返回。包括微信明细单号、明细状态信息
+   * </pre>
+   */
+  @SerializedName(value = "transfer_detail_list")
+  private List<TransferDetail> transferDetailList;
+
+  @Data
+  @NoArgsConstructor
+  public static class TransferDetail implements Serializable {
+    private static final long serialVersionUID = 10941148801492L;
+    /**
+     * <pre>
+     * 字段名:微信支付明细单号
+     * 变量名:detail_id
+     * 是否必填:是
+     * 类型:string[32, 64]
+     * 描述:
+     *  微信支付系统内部区分转账批次单下不同转账明细单的唯一标识
+     *  示例值:1040000071100999991182020050700019500100
+     * </pre>
+     */
+    @SerializedName(value = "detail_id")
+    private String detailId;
+
+    /**
+     * <pre>
+     * 字段名:商家明细单号
+     * 变量名:out_detail_no
+     * 是否必填:是
+     * 类型:string[5, 32]
+     * 描述:
+     *  商户系统内部区分转账批次单下不同转账明细单的唯一标识
+     *  示例值:x23zy545Bd5436
+     * </pre>
+     */
+    @SerializedName(value = "out_detail_no")
+    private String outDetailNo;
+
+    /**
+     * <pre>
+     * 字段名:明细状态
+     * 变量名:detail_status
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *     枚举值:
+     *         PROCESSING:转账中。正在处理中,转账结果尚未明确
+     *         SUCCESS:转账成功
+     *         FAIL:转账失败。需要确认失败原因后,再决定是否重新发起对该笔明细单的转账(并非整个转账批次单)
+     * 示例值:SUCCESS
+     * </pre>
+     */
+    @SerializedName(value = "detail_status")
+    private String detailStatus;
+  }
+
+  /**
+   * <pre>
+   * 字段名:服务商的appid
+   * 变量名:sp_appid
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *    微信分配的服务商商户公众账号ID,特约商户授权类型为FUND_AUTHORIZATION_TYPE时才有该字段
+   *     示例值:wxf636efh567hg4388
+   * </pre>
+   */
+  @SerializedName(value = "sp_appid")
+  private String spAppid;
+  /**
+   * <pre>
+   * 字段名:批量转账用途
+   * 变量名:transfer_purpose
+   * 是否必填:否
+   * 类型:string[1,16]
+   * 描述:
+   *   批量转账用途:
+   *     GOODSPAYMENT:货款
+   *     COMMISSION:佣金
+   *     REFUND:退款
+   *     REIMBURSEMENT:报销
+   *     FREIGHT:运费
+   *     OTHERS:其他
+   * 示例值:COMMISSION
+   * </pre>
+   */
+  @SerializedName(value = "transfer_purpose")
+  private String transferPurpose;
+}

+ 143 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BillReceiptResult.java

@@ -0,0 +1,143 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 转账电子回单申请受理API
+ * 接口说明
+ * 适用对象:直连商户 服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt
+ * 请求方式:POST
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class BillReceiptResult implements Serializable {
+  public static final float serialVersionUID = 1L;
+
+  @Override
+  public String toString() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5,32]
+   * 描述:
+   *  商户系统内部的商家批次单号,在商户系统内部唯一。需要电子回单的批次单号
+   *  示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:电子回单申请单号
+   * 变量名:signature_no
+   * 是否必填:是
+   * 类型:string[3.45]
+   * 描述:
+   *  电子回单申请单号,申请单据的唯一标识
+   *     示例值:1050000010509999485212020110200058820001
+   * </pre>
+   */
+  @SerializedName(value = "signature_no")
+  private String signatureNo;
+
+  /**
+   * <pre>
+   * 字段名:电子回单状态
+   * 变量名:signature_status
+   * 是否必填:否
+   * 类型:string[1,10]
+   * 描述:
+   *  枚举值:
+   *     ACCEPTED:已受理,电子签章已受理成功
+   *     FINISHED:已完成。电子签章已处理完成
+   *     示例值:ACCEPTED
+   * </pre>
+   */
+  @SerializedName(value = "signature_status")
+  private String signatureStatus;
+
+  /**
+   * <pre>
+   * 字段名:电子回单文件的hash方法
+   * 变量名:hash_type
+   * 是否必填:否
+   * 类型:string[1,20]
+   * 描述:
+   *  电子回单文件的hash方法,回单状态为:FINISHED时返回。
+   *     示例值:SHA256
+   * </pre>
+   */
+  @SerializedName(value = "hash_type")
+  private String hashType;
+
+  /**
+   * <pre>
+   * 字段名:电子回单文件的hash值
+   * 变量名:hash_value
+   * 是否必填:否
+   * 类型:string[3,1000]
+   * 描述:
+   *  电子回单文件的hash值,用于下载之后验证文件的完整、正确性,回单状态为:FINISHED时返回。
+   *     示例值:DE731F35146A0BEFADE5DB9D1E468D96C01CA8898119C674FEE9F11F4DBE5529
+   * </pre>
+   */
+  @SerializedName(value = "hash_value")
+  private String hashValue;
+
+
+  /**
+   * <pre>
+   * 字段名:电子回单文件的下载地址
+   * 变量名:hash_value
+   * 是否必填:否
+   * 类型:string[10,3000]
+   * 描述:
+   *  电子回单文件的下载地址,回单状态为:FINISHED时返回。URL有效时长为10分钟,10分钟后需要重新去获取下载地址
+   *     示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+   * </pre>
+   */
+  @SerializedName(value = "download_url")
+  private String downloadUrl;
+
+  /**
+   * <pre>
+   * 字段名:创建时间
+   * 变量名:create_time
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   * 	电子签章单创建时间,遵循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秒
+   *     示例值:2020-05-20T13:29:35.120+08:00
+   * </pre>
+   */
+  @SerializedName(value = "create_time")
+  private String createTime;
+  /**
+   * <pre>
+   * 字段名:更新时间
+   * 变量名:update_time
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   * 	电子签章单最近一次状态变更的时间,遵循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秒
+   *     示例值:2020-05-21T13:29:35.120+08:00
+   * </pre>
+   */
+  @SerializedName(value = "update_time")
+  private String updateTime;
+}

+ 65 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/DownloadRequest.java

@@ -0,0 +1,65 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 下载电子回单API
+ * 接口说明
+ * 适用对象:直连商户 服务商
+ * 请求URL:通过申请账单接口获取到“download_url”,URL有效期10min
+ * 请求方式:GET
+ * 前置条件:调用申请账单接口并获取到“download_url”
+ * 接口规则:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class DownloadRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:电子回单文件的hash方法
+   * 变量名:hash_type
+   * 是否必填:否
+   * 类型:string[1, 20]
+   * 描述:
+   *  电子回单文件的hash方法,回单状态为:FINISHED时返回
+   *  例值:SHA256
+   * </pre>
+   */
+  @SerializedName(value = "hash_type")
+  private String hashType;
+  /**
+   * <pre>
+   * 字段名:电子回单文件的hash值
+   * 变量名:hash_value
+   * 是否必填:否
+   * 类型:string[3, 1000]
+   * 描述:
+   *  电子回单文件的hash值,用于下载之后验证文件的完整、正确性,回单状态为:FINISHED时返回
+   *  示例值:DE731F35146A0BEFADE5DB9D1E468D96C01CA8898119C674FEE9F11F4DBE5529
+   * </pre>
+   */
+  @SerializedName(value = "hash_value")
+  private String hashValue;
+  /**
+   * <pre>
+   * 字段名:电子回单文件的下载地址
+   * 变量名:download_url
+   * 是否必填:否
+   * 类型:string[10, 3000]
+   * 描述:
+   *  电子回单文件的下载地址,回单状态为:FINISHED时返回。URL有效时长为10分钟,10分钟后需要重新去获取下载地址(但不需要走受理)
+   * 示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+   * </pre>
+   */
+  @SerializedName(value = "download_url")
+  private String downloadUrl;
+}

+ 70 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsRequest.java

@@ -0,0 +1,70 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 转账明细电子回单受理API
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/transfer-detail/electronic-receipts
+ * 请求方式:POST
+ * 前置条件:只支持受理最近90天内的转账明细单
+ * 接口规则:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class ElectronicReceiptsRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:受理类型
+   * 变量名:accept_type
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  body电子回单受理类型:
+   *     BATCH_TRANSFER:批量转账明细电子回单
+   *     TRANSFER_TO_POCKET:企业付款至零钱电子回单
+   *     TRANSFER_TO_BANK:企业付款至银行卡电子回单
+   * 示例值:BATCH_TRANSFER
+   * </pre>
+   */
+  @SerializedName(value = "accept_type")
+  private String acceptType;
+
+  /**
+   * <pre>
+   * 字段名:商家转账批次单号
+   * 变量名:out_batch_no
+   * 是否必填:否
+   * 类型:string[5, 32]
+   * 描述:
+   *  body需要电子回单的批量转账明细单所在的转账批次单号,该单号为商户申请转账时生成的商户单号。受理类型为BATCH_TRANSFER时该单号必填,否则该单号留空。
+   *  示例值:GD2021011610162610BBdkkIwcu3
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:商家转账明细单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  body该单号为商户申请转账时生成的商家转账明细单号。
+   *             1.受理类型为BATCH_TRANSFER时填写商家批量转账明细单号。
+   *             2. 受理类型为TRANSFER_TO_POCKET或TRANSFER_TO_BANK时填写商家转账单号。
+   *  示例值:mx0911231610162610v4CNkO4HAf
+   * </pre>
+   */
+  @SerializedName(value = "out_detail_no")
+  private String outDetailNo;
+}

+ 137 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsResult.java

@@ -0,0 +1,137 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 转账明细电子回单受理API
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/transfer-detail/electronic-receipts
+ * 请求方式:POST
+ * 前置条件:只支持受理最近90天内的转账明细单
+ * 接口规则:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class ElectronicReceiptsResult implements Serializable {
+  public static final float serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:受理类型
+   * 变量名:accept_type
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  电子回单受理类型:
+   *     BATCH_TRANSFER:批量转账明细电子回单
+   *     TRANSFER_TO_POCKET:企业付款至零钱电子回单
+   *     TRANSFER_TO_BANK:企业付款至银行卡电子回单
+   *  示例值:BATCH_TRANSFER
+   * </pre>
+   */
+  @SerializedName(value = "accept_type")
+  private String acceptType;
+
+  /**
+   * <pre>
+   * 字段名:商家转账批次单号
+   * 变量名:out_batch_no
+   * 是否必填:否
+   * 类型:string[5, 32]
+   * 描述:
+   *  需要电子回单的批量转账明细单所在的转账批次单号,该单号为商户申请转账时生成的商户单号。受理类型为BATCH_TRANSFER时该单号必填,否则该单号留空。
+   *  示例值:GD2021011610162610BBdkkIwcu3
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:商家转账明细单号
+   * 变量名:out_detail_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  该单号为商户申请转账时生成的商家转账明细单号。
+   *     1.受理类型为BATCH_TRANSFER时填写商家批量转账明细单号。
+   *     2. 受理类型为TRANSFER_TO_POCKET或TRANSFER_TO_BANK时填写商家转账单号。
+   *  示例值:mx0911231610162610v4CNkO4HAf
+   * </pre>
+   */
+  @SerializedName(value = "out_detail_no")
+  private String outDetailNo;
+  /**
+   * <pre>
+   * 字段名:电子回单受理单号
+   * 变量名:signature_no
+   * 是否必填:是
+   * 类型:string[3, 256]
+   * 描述:
+   *  电子回单受理单号,受理单据的唯一标识
+   *  示例值:1050000010509999485212020110200058820001
+   * </pre>
+   */
+  @SerializedName(value = "signature_no")
+  private String signatureNo;
+  /**
+   * <pre>
+   * 字段名:电子回单状态
+   * 变量名:signature_status
+   * 是否必填:否
+   * 类型:string[1, 10]
+   * 描述:
+   *  枚举值:
+   *     ACCEPTED:已受理,电子签章已受理成功
+   *     FINISHED:已完成。电子签章已处理完成
+   *  示例值:ACCEPTED
+   * </pre>
+   */
+  @SerializedName(value = "signature_status")
+  private String signatureStatus;
+  /**
+   * <pre>
+   * 字段名:电子回单文件的hash方法
+   * 变量名:hash_type
+   * 是否必填:否
+   * 类型:string[1, 20]
+   * 描述:
+   *  电子回单文件的hash方法,回单状态为:FINISHED时返回
+   *  例值:SHA256
+   * </pre>
+   */
+  @SerializedName(value = "hash_type")
+  private String hashType;
+  /**
+   * <pre>
+   * 字段名:电子回单文件的hash值
+   * 变量名:hash_value
+   * 是否必填:否
+   * 类型:string[3, 1000]
+   * 描述:
+   *  电子回单文件的hash值,用于下载之后验证文件的完整、正确性,回单状态为:FINISHED时返回
+   *  示例值:DE731F35146A0BEFADE5DB9D1E468D96C01CA8898119C674FEE9F11F4DBE5529
+   * </pre>
+   */
+  @SerializedName(value = "hash_value")
+  private String hashValue;
+  /**
+   * <pre>
+   * 字段名:电子回单文件的下载地址
+   * 变量名:download_url
+   * 是否必填:否
+   * 类型:string[10, 3000]
+   * 描述:
+   *  电子回单文件的下载地址,回单状态为:FINISHED时返回。URL有效时长为10分钟,10分钟后需要重新去获取下载地址(但不需要走受理)
+   * 示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+   * </pre>
+   */
+  @SerializedName(value = "download_url")
+  private String downloadUrl;
+}

+ 97 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/MerchantBatchRequest.java

@@ -0,0 +1,97 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 商家批次单号查询批次单API
+ * 适用对象:服务商
+ * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/out-batch-no/{out_batch_no}
+ * 请求方式:GET
+ * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+ * 接口规则:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class MerchantBatchRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  path商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
+   *     示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:是否查询转账明细单
+   * 变量名:need_query_detail
+   * 是否必填:是
+   * 类型:boolean 默认否
+   * 描述:
+   *  商户可选择是否查询指定状态的转账明细单,当转账批次单状态为“FINISHED”(已完成)时,才会返回满足条件的转账明细单
+   *  示例值:true
+   * </pre>
+   */
+  @SerializedName(value = "need_query_detail")
+  private Boolean needQueryDetail;
+
+  /**
+   * <pre>
+   * 字段名:请求资源起始位置
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  query该次请求资源的起始位置。返回的明细是按照设置的明细条数进行分页展示的,一次查询可能无法返回所有明细,我们使用该参数标识查询开始位置,默认值为0
+   *  示例值:0
+   * </pre>
+   */
+  @SerializedName(value = "offset")
+  private Integer offset;
+
+  /**
+   * <pre>
+   * 字段名:最大资源条数
+   * 变量名:limit
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  query该次请求可返回的最大明细条数,最小20条,最大100条,不传则默认20条。不足20条按实际条数返回
+   * 示例值:20
+   * </pre>
+   */
+  @SerializedName(value = "limit")
+  private Integer limit;
+
+  /**
+   * <pre>
+   * 字段名:明细状态
+   * 变量名:detail_status
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  query查询指定状态的转账明细单,不传没有明细状态信息返回。当need_query_detail为true时,该字段必填
+   *  枚举值:
+   *     ALL:全部。需要同时查询转账成功和转账失败的明细单
+   *     SUCCESS:转账成功。只查询转账成功的明细单
+   *     FAIL:转账失败。只查询转账失败的明细单
+   *  示例值:FAIL
+   * </pre>
+   */
+  @SerializedName(value = "detail_status")
+  private String detailStatus;
+}

+ 289 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferRequest.java

@@ -0,0 +1,289 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 发起批量转账API
+ * <pre>
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_1.shtml
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class PartnerTransferRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+
+  /**
+   * <pre>
+   * 字段名:特约商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  特约商户号
+   *  示例值:1900000109
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:特约商户appid
+   * 变量名:sub_appid
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  示例值:wxf636efh567hg4356
+   * </pre>
+   */
+  @SerializedName(value = "sub_appid")
+  private String subAppid;
+
+  /**
+   * <pre>
+   * 字段名:特约商户授权类型
+   * 变量名:authorization_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  特约商户授权类型:
+   *    INFORMATION_AUTHORIZATION_TYPE:特约商户信息授权类型
+   *    FUND_AUTHORIZATION_TYPE:特约商户资金授权类型
+   *    INFORMATION_AND_FUND_AUTHORIZATION_TYPE:特约商户信息和资金授权类型
+   * 示例值:INFORMATION_AUTHORIZATION_TYPE
+   * </pre>
+   */
+  @SerializedName(value = "authorization_type")
+  private String authorizationType;
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string(5-32)
+   * 描述:
+   *    商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
+   *    示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:批次名称
+   * 变量名:batch_name
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  body该笔批量转账的名称
+   *  示例值:2019年1月深圳分部报销单
+   * </pre>
+   */
+  @SerializedName(value = "batch_name")
+  private String batchName;
+
+  /**
+   * <pre>
+   * 字段名:批次备注
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  body转账说明,UTF8编码,最多允许32个字符
+   *   示例值:2019年1月深圳分部报销单
+   * </pre>
+   */
+  @SerializedName(value = "batch_remark")
+  private String batchRemark;
+
+  /**
+   * <pre>
+   * 字段名:转账总金额
+   * 变量名:time_expire
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  body转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作
+   *  示例值:4000000
+   * </pre>
+   */
+  @SerializedName(value = "total_amount")
+  private Integer totalAmount;
+
+  /**
+   * <pre>
+   * 字段名:转账总笔数
+   * 变量名:total_num
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  body一个转账批次单最多发起三千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作
+   *  示例值:200
+   * </pre>
+   */
+  @SerializedName(value = "total_num")
+  private Integer totalNum;
+
+  /**
+   * <pre>
+   * 字段名:转账明细列表
+   * 变量名:transfer_detail_list
+   * 是否必填:是
+   * 类型:array
+   * 描述:
+   *  body发起批量转账的明细列表,最多三千笔
+   * </pre>
+   */
+  @SerializedName(value = "transfer_detail_list")
+  private List<TransferDetail> transferDetailList;
+
+  @Data
+  @NoArgsConstructor
+  public static class TransferDetail implements Serializable {
+    private static final long serialVersionUID = 109053454401492L;
+
+    /**
+     * <pre>
+     * 字段名:商家明细单号
+     * 变量名:out_detail_no
+     * 是否必填:是
+     * 类型:string[5, 32]
+     * 描述:
+     *  商户系统内部区分转账批次单下不同转账明细单的唯一标识,要求此参数只能由数字、大小写字母组成
+     *  示例值:x23zy545Bd5436
+     * </pre>
+     */
+    @SerializedName(value = "out_detail_no")
+    private String outDetailNo;
+
+
+    /**
+     * <pre>
+     * 字段名:转账金额
+     * 变量名:transfer_amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  转账金额单位为“分”
+     *  示例值:200000
+     * </pre>
+     */
+    @SerializedName(value = "transfer_amount")
+    private Integer transferAmount;
+
+    /**
+     * <pre>
+     * 字段名:转账备注
+     * 变量名:transfer_amount
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
+     *  示例值:2020年4月报销
+     * </pre>
+     */
+    @SerializedName(value = "transfer_remark")
+    private String transferRemark;
+
+    /**
+     * <pre>
+     * 字段名:收款用户openid
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string[1, 64]
+     * 描述:
+     *  收款用户openid。如果转账特约商户授权类型是INFORMATION_AUTHORIZATION_TYPE,对应的是特约商户公众号下的openid。
+     *  示例值:o-MYE42l80oelYMDE34nYD456Xoy
+     * </pre>
+     */
+    @SerializedName(value = "openid")
+    private String openid;
+
+    /**
+     * <pre>
+     * 字段名:收款用户姓名
+     * 变量名:user_name
+     * 是否必填:是
+     * 类型:string[1, 1024]
+     * 描述:
+     *  1、收款用户姓名。采用标准RSA算法,公钥由微信侧提供
+     *  2、该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+     *  示例值:757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45
+     * </pre>
+     */
+    @SerializedName(value = "user_name")
+    private String userName;
+    /**
+     * <pre>
+     * 字段名:收款用户身份证
+     * 变量名:user_id_card
+     * 是否必填:否
+     * 类型:string[1, 1024]
+     * 描述:
+     *  1、收款方身份证号,可不用填(采用标准RSA算法,公钥由微信侧提供)
+     *  2、该字段需进行加密处理,加密方法详见敏感信息加密说明。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+     *  示例值:8609cb22e1774a50a930e414cc71eca06121bcd266335cda230d24a7886a8d9f
+     * </pre>
+     */
+    @SerializedName(value = "user_id_card")
+    private String userIdCard;
+  }
+
+  /**
+   * <pre>
+   * 字段名:服务商的appid
+   * 变量名:sp_appid
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  特约商户授权类型为FUND_AUTHORIZATION_TYPE时需要填写
+   *  示例值:wxf636efh567hg4388
+   * </pre>
+   */
+  @SerializedName(value = "sp_appid")
+  private String spAppid;
+
+
+  /**
+   * <pre>
+   * 字段名:批量转账用途
+   * 变量名:transfer_purpose
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  body批量转账用途,枚举值:
+   *        GOODSPAYMENT:货款、COMMISSION:佣金、REFUND:退款、REIMBURSEMENT:报销
+   *        FREIGHT:运费、OTHERS:其他
+   *  示例值:COMMISSION
+   * </pre>
+   */
+  @SerializedName(value = "transfer_purpose")
+  private String transferPurpose;
+
+  /**
+   * <pre>
+   * 字段名:转账场景【微工卡】
+   * 变量名:transfer_scene
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  body商户的转账场景
+   *     ORDINARY_TRANSFER:普通转账,可转入用户的零钱账户
+   *     PAYROLL_CARD_TRANSFER:微工卡转账,可转入用户在微工卡选择的收款账户(零钱/银行卡)
+   *  示例值:ORDINARY_TRANSFER
+   * </pre>
+   */
+  @SerializedName(value = "transfer_scene")
+  private String transferScene;
+
+}

+ 67 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferResult.java

@@ -0,0 +1,67 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 发起批量转账API
+ * <pre>
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_1.shtml
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class PartnerTransferResult implements Serializable {
+  public static final float serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  商户系统内部的商家批次单号,在商户系统内部唯一
+   *  示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+
+  /**
+   * <pre>
+   * 字段名:微信支付批次单号
+   * 变量名:batch_id
+   * 是否必填:是
+   * 类型:string[32, 64]
+   * 描述:
+   *  微信支付批次单号,微信商家转账系统返回的唯一标识
+   *  示例值:1030000071100999991182020050700019480001
+   * </pre>
+   */
+  @SerializedName(value = "batch_id")
+  private String batchId;
+
+  /**
+   * <pre>
+   * 字段名:批次创建时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  批次受理成功时返回,遵循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
+   * </pre>
+   */
+  @SerializedName(value = "create_time")
+  private String createTime;
+
+}

+ 35 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ReceiptBillRequest.java

@@ -0,0 +1,35 @@
+package com.github.binarywang.wxpay.bean.marketing.transfer;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 转账电子回单申请受理API
+ * <pre>
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
+ * </pre>
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Data
+@NoArgsConstructor
+public class ReceiptBillRequest implements Serializable {
+  public static final float serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:商家批次单号
+   * 变量名:out_batch_no
+   * 是否必填:是
+   * 类型:string[5, 32]
+   * 描述:
+   *  body商户系统内部的商家批次单号,在商户系统内部唯一。需要电子回单的批次单号
+   *  示例值:plfk2020042013
+   * </pre>
+   */
+  @SerializedName(value = "out_batch_no")
+  private String outBatchNo;
+}

+ 201 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerTransferService.java

@@ -0,0 +1,201 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.ecommerce.FundBalanceResult;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
+import com.github.binarywang.wxpay.bean.marketing.transfer.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+import javax.crypto.BadPaddingException;
+import java.io.InputStream;
+
+/**
+ * 微信批量转账到零钱【V3接口】服务商API
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+public interface PartnerTransferService {
+
+  /**
+   * 发起批量转账API
+   * 适用对象:服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_1.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches
+   * 请求方式:POST
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  PartnerTransferResult batchTransfer(PartnerTransferRequest request) throws WxPayException;
+
+  /**
+   * 微信支付批次单号查询批次单API
+   * 接口说明
+   * 适用对象:服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_2.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/{batch_id}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  BatchNumberResult queryBatchByBatchId(BatchNumberRequest request) throws WxPayException;
+
+  /**
+   * 微信支付明细单号查询明细单API
+   * 接口说明
+   * 适用对象:服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_3.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param batchId  微信批次单号
+   * @param detailId 微信明细单号
+   * @return 返回数据 fund balance result
+   * @throws WxPayException      the wx pay exception
+   * @throws BadPaddingException the wx decrypt exception
+   */
+  BatchDetailsResult queryBatchDetailByWeChat(String batchId, String detailId) throws WxPayException, BadPaddingException;
+
+  /**
+   * 商家批次单号查询批次单API
+   * 接口说明
+   * 适用对象:服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_4.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/out-batch-no/{out_batch_no}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  BatchNumberResult queryBatchByOutBatchNo(MerchantBatchRequest request) throws WxPayException;
+
+  /**
+   * 商家明细单号查询明细单API
+   * 接口说明
+   * 适用对象:服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_5.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param outBatchNo  商家明细单号
+   * @param outDetailNo 商家批次单号
+   * @return 返回数据 fund balance result
+   * @throws WxPayException      the wx pay exception
+   * @throws BadPaddingException the wx decrypt exception
+   */
+  BatchDetailsResult queryBatchDetailByMch(String outBatchNo, String outDetailNo) throws WxPayException, BadPaddingException;
+
+
+  /**
+   * 转账电子回单申请受理API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt
+   * 请求方式:POST
+   *
+   * @param request 商家批次单号
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  BillReceiptResult receiptBill(ReceiptBillRequest request) throws WxPayException;
+
+
+  /**
+   * 查询转账电子回单API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_2.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt/{out_batch_no}
+   * 请求方式:GET
+   *
+   * @param outBatchNo 商家批次单号
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  BillReceiptResult queryBillReceipt(String outBatchNo) throws WxPayException;
+
+  /**
+   * 转账明细电子回单受理API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_4.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer-detail/electronic-receipts
+   * 请求方式:POST
+   * 前置条件:只支持受理最近90天内的转账明细单
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  ElectronicReceiptsResult transferElectronic(ElectronicReceiptsRequest request) throws WxPayException;
+
+  /**
+   * 查询转账明细电子回单受理结果API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_5.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer-detail/electronic-receipts
+   * 请求方式:GET
+   * 前置条件:只支持查询最近90天内的转账明细单
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  ElectronicReceiptsResult queryTransferElectronicResult(ElectronicReceiptsRequest request) throws WxPayException;
+
+  /**
+   * 下载电子回单API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_3.shtml
+   * 请求URL:通过申请账单接口获取到“download_url”,URL有效期10min
+   * 请求方式:GET
+   * 前置条件:调用申请账单接口并获取到“download_url”
+   *
+   * @param url 微信返回的电子回单地址。
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  InputStream transferDownload(String url) throws WxPayException;
+
+  /**
+   * <pre>
+   * 查询账户实时余额API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_1.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/merchant/fund/balance/{account_type}
+   * 请求方式:GET
+   * </pre>
+   *
+   * @param accountType 服务商账户类型 {@link SpAccountTypeEnum}
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  FundBalanceResult fundBalance(SpAccountTypeEnum accountType) throws WxPayException;
+
+  /**
+   * <pre>
+   * 服务商账户日终余额
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_2.shtml
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+   * </pre>
+   *
+   * @param accountType 服务商账户类型
+   * @param date        查询日期 2020-09-11
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  FundBalanceResult spDayEndBalance(SpAccountTypeEnum accountType, String date);
+}

+ 103 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayrollService.java

@@ -0,0 +1,103 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.marketing.payroll.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * 微工卡-对接微信api
+ *
+ * @author xiaoqiang
+ * @date 2021/12/7 14:26
+ */
+public interface PayrollService {
+  /**
+   * 生成授权token
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/tokens
+   * 请求方式:POST
+   *
+   * @param request 请求参数
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  TokensResult payrollCardTokens(TokensRequest request) throws WxPayException;
+
+  /**
+   * 查询微工卡授权关系API
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/relations/{openid}
+   * 请求方式:GET
+   *
+   * @param request 请求参数
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  RelationsResult payrollCardRelations(RelationsRequest request) throws WxPayException;
+
+  /**
+   * 微工卡核身预下单API
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order
+   * 请求方式:POST
+   *
+   * @param request 请求参数
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  PreOrderResult payrollCardPreOrder(PreOrderRequest request) throws WxPayException;
+
+  /**
+   * 获取核身结果API
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/{authenticate_number}
+   * 请求方式:GET
+   *
+   * @param subMchid           子商户号
+   * @param authenticateNumber 商家核身单号
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  AuthenticationsResult payrollCardAuthenticationsNumber(String subMchid, String authenticateNumber) throws WxPayException;
+
+  /**
+   * 查询核身记录API
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications
+   * 请求方式:GET
+   *
+   * @param request 请求参数
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  AuthRecordResult payrollCardAuthentications(AuthRecordRequest request) throws WxPayException;
+
+  /**
+   * 微工卡核身预下单(流程中完成授权)
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order-with-auth
+   * 请求方式:POST
+   *
+   * @param request 请求参数
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  PreOrderWithAuthResult payrollCardPreOrderWithAuth(PreOrderWithAuthRequest request) throws WxPayException;
+
+  /**
+   * 按日下载提现异常文件API
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/merchant/fund/withdraw/bill-type/{bill_type}
+   * 请求方式:GET
+   *
+   * @param billType 账单类型
+   *                 NO_SUCC:提现异常账单,包括提现失败和提现退票账单。
+   *                 示例值:NO_SUCC
+   * @param billDate 账单日期 表示所在日期的提现账单,格式为YYYY-MM-DD。
+   *                 例如:2008-01-01日发起的提现,2008-01-03日银行返回提现失败,则该提现数据将出现在bill_date为2008-01-03日的账单中。
+   *                 示例值:2019-08-17
+   * @return 返回数据
+   * @throws WxPayException the wx pay exception
+   */
+  PreOrderWithAuthResult merchantFundWithdrawBillType(String billType, String billDate) throws WxPayException;
+
+}

+ 14 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java

@@ -180,6 +180,20 @@ public interface WxPayService {
   WxEntrustPapService getWxEntrustPapService();
 
   /**
+   * 获取批量转账到零钱服务类.
+   *
+   * @return the Batch transfer to change service
+   */
+  PartnerTransferService getPartnerTransferService();
+
+  /**
+   * 微工卡
+   *
+   * @return the micro card
+   */
+  PayrollService getPayrollService();
+
+  /**
    * 获取企业付款服务类.
    *
    * @return the ent pay service

+ 8 - 1
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java

@@ -75,7 +75,8 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   private final MarketingFavorService marketingFavorService = new MarketingFavorServiceImpl(this);
   private final MarketingBusiFavorService marketingBusiFavorService = new MarketingBusiFavorServiceImpl(this);
   private final WxEntrustPapService wxEntrustPapService = new WxEntrustPapServiceImpl(this);
-
+  private final PartnerTransferService partnerTransferService = new PartnerTransferServiceImpl(this);
+  private final PayrollService payrollService = new PayrollServiceImpl(this);
 
   protected Map<String, WxPayConfig> configMap;
 
@@ -145,6 +146,12 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   }
 
   @Override
+  public PartnerTransferService getPartnerTransferService(){return partnerTransferService;}
+
+  @Override
+  public PayrollService getPayrollService(){return payrollService;}
+
+  @Override
   public WxPayConfig getConfig() {
     if (this.configMap.size() == 1) {
       // 只有一个商户号,直接返回其配置即可

+ 311 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PartnerTransferServiceImpl.java

@@ -0,0 +1,311 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.ecommerce.FundBalanceResult;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
+import com.github.binarywang.wxpay.bean.marketing.transfer.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.PartnerTransferService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import jodd.util.StringUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import java.io.InputStream;
+
+/**
+ * 批量转账到零钱(服务商)
+ *
+ * @author xiaoqiang
+ * @date 2021-12-06
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class PartnerTransferServiceImpl implements PartnerTransferService {
+
+  private static final Gson GSON = new GsonBuilder().create();
+  private final WxPayService payService;
+
+  /**
+   * 发起批量转账API
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches
+   * 请求方式:POST
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param request 请求对象
+   * @return 返回数据 {@link PartnerTransferResult}
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public PartnerTransferResult batchTransfer(PartnerTransferRequest request) throws WxPayException {
+    request.getTransferDetailList().stream().forEach(p -> {
+      try {
+        String userName = RsaCryptoUtil.encryptOAEP(p.getUserName(), this.payService.getConfig().getVerifier().getValidCertificate());
+        p.setUserName(userName);
+      } catch (IllegalBlockSizeException e) {
+        throw new RuntimeException("姓名转换异常!", e);
+      }
+    });
+    String url = String.format("%s/v3/partner-transfer/batches", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
+    return GSON.fromJson(response, PartnerTransferResult.class);
+  }
+
+  /**
+   * 微信支付批次单号查询批次单API
+   * 接口说明
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/{batch_id}
+   * https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/1030000071100999991182020050700019480001?need_query_detail=true&offset=1
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param request 请求对象
+   * @return 返回数据 {@link BatchNumberResult}
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public BatchNumberResult queryBatchByBatchId(BatchNumberRequest request) throws WxPayException {
+    String url = String.format("%s/v3/partner-transfer/batches/batch-id/%s", this.payService.getPayBaseUrl(), request.getBatchId());
+    if (request.getOffset() == null) {
+      request.setOffset(0);
+    }
+    if (request.getLimit() == null || request.getLimit() <= 0) {
+      request.setLimit(20);
+    }
+    String query = String.format("?need_query_detail=true&detail_status=ALL&offset=%s&limit=%s", request.getNeedQueryDetail(), request.getOffset(), request.getLimit());
+    if (StringUtil.isNotBlank(request.getDetailStatus())){
+      query += "&detail_status="+request.getDetailStatus();
+    }
+    String response = this.payService.getV3(url + query);
+    return GSON.fromJson(response, BatchNumberResult.class);
+  }
+
+  /**
+   * 商家批次单号查询批次单API
+   * 接口说明
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/out-batch-no/{out_batch_no}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param request 请求对象
+   * @return 返回数据 {@link BatchNumberResult}
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public BatchNumberResult queryBatchByOutBatchNo(MerchantBatchRequest request) throws WxPayException {
+    String url = String.format("%s/v3/partner-transfer/batches/out-batch-no/%s", this.payService.getPayBaseUrl(), request.getOutBatchNo());
+    if (request.getOffset() == null) {
+      request.setOffset(0);
+    }
+    if (request.getLimit() == null || request.getLimit() <= 0) {
+      request.setLimit(20);
+    }
+    String query = String.format("?need_query_detail=true&offset=%s&limit=%s", request.getNeedQueryDetail(), request.getOffset(), request.getLimit());
+    if (StringUtil.isNotBlank(request.getDetailStatus())){
+      query += "&detail_status="+request.getDetailStatus();
+    }
+    String response = this.payService.getV3(url + query);
+    return GSON.fromJson(response, BatchNumberResult.class);
+  }
+
+  /**
+   * 微信支付明细单号查询明细单API
+   * 接口说明
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param batchId  微信批次单号
+   * @param detailId 微信明细单号
+   * @return 返回数据 {@link BatchDetailsResult}
+   * @throws WxPayException      the wx pay exception
+   * @throws BadPaddingException the wx decrypt exception
+   */
+  @Override
+  public BatchDetailsResult queryBatchDetailByWeChat(String batchId, String detailId) throws WxPayException, BadPaddingException {
+    String url = String.format("%s/v3/partner-transfer/batches/batch-id/%s/details/detail-id/%s", this.payService.getPayBaseUrl(), batchId, detailId);
+    String response = this.payService.getV3(url);
+    BatchDetailsResult batchDetailsResult = GSON.fromJson(response, BatchDetailsResult.class);
+    String userName = RsaCryptoUtil.decryptOAEP(batchDetailsResult.getUserName(), this.payService.getConfig().getPrivateKey());
+    batchDetailsResult.setUserName(userName);
+    return batchDetailsResult;
+  }
+
+  /**
+   * 商家明细单号查询明细单API
+   * 接口说明
+   * 适用对象:服务商
+   * 请求URL:https://api.mch.weixin.qq.com/v3/partner-transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no}
+   * 请求方式:GET
+   * 接口限频:单个服务商 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
+   *
+   * @param outBatchNo  商家明细单号
+   * @param outDetailNo 商家批次单号
+   * @return 返回数据 {@link BatchDetailsResult}
+   * @throws WxPayException      the wx pay exception
+   * @throws BadPaddingException the wx decrypt exception
+   */
+  @Override
+  public BatchDetailsResult queryBatchDetailByMch(String outBatchNo, String outDetailNo) throws WxPayException, BadPaddingException {
+    String url = String.format("%s/v3/partner-transfer/batches/out-batch-no/%s/details/out-detail-no/%s", this.payService.getPayBaseUrl(), outBatchNo, outDetailNo);
+    String response = this.payService.getV3(url);
+    BatchDetailsResult batchDetailsResult = GSON.fromJson(response, BatchDetailsResult.class);
+    String userName = RsaCryptoUtil.decryptOAEP(batchDetailsResult.getUserName(), this.payService.getConfig().getPrivateKey());
+    batchDetailsResult.setUserName(userName);
+    return batchDetailsResult;
+  }
+
+
+  /**
+   * 转账电子回单申请受理API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt
+   * 请求方式:POST
+   *
+   * @param request 商家批次单号
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public BillReceiptResult receiptBill(ReceiptBillRequest request) throws WxPayException {
+    String url = String.format("%s/v3/transfer/bill-receipt", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, BillReceiptResult.class);
+  }
+
+
+  /**
+   * 查询转账电子回单API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_2.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt/{out_batch_no}
+   * 请求方式:GET
+   *
+   * @param outBatchNo 商家批次单号
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public BillReceiptResult queryBillReceipt(String outBatchNo) throws WxPayException {
+    String url = String.format("%s/v3/transfer/bill-receipt/%s", this.payService.getPayBaseUrl(), outBatchNo);
+    String response = this.payService.getV3(url);
+    return GSON.fromJson(response, BillReceiptResult.class);
+  }
+
+  /**
+   * 转账明细电子回单受理API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_4.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer-detail/electronic-receipts
+   * 请求方式:POST
+   * 前置条件:只支持受理最近90天内的转账明细单
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public ElectronicReceiptsResult transferElectronic(ElectronicReceiptsRequest request) throws WxPayException {
+    String url = String.format("%s/v3/transfer-detail/electronic-receipts", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, ElectronicReceiptsResult.class);
+  }
+
+
+  /**
+   * 查询转账明细电子回单受理结果API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_5.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/transfer-detail/electronic-receipts
+   * 请求方式:GET
+   * 前置条件:只支持查询最近90天内的转账明细单
+   *
+   * @param request 请求对象
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public ElectronicReceiptsResult queryTransferElectronicResult(ElectronicReceiptsRequest request) throws WxPayException {
+    String url = String.format("%s/v3/transfer-detail/electronic-receipts", this.payService.getPayBaseUrl());
+    String query = String.format("?accept_type=%s&out_batch_no=%s&out_detail_no=%s", request.getAcceptType(), request.getOutBatchNo(), request.getOutDetailNo());
+    String response = this.payService.getV3(url + query);
+    return GSON.fromJson(response, ElectronicReceiptsResult.class);
+  }
+
+  /**
+   * 下载电子回单API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_3.shtml
+   * 请求URL:通过申请账单接口获取到“download_url”,URL有效期10min
+   * 请求方式:GET
+   * 前置条件:调用申请账单接口并获取到“download_url”
+   *
+   * @param url 微信返回的电子回单地址。
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public InputStream transferDownload(String url) throws WxPayException {
+    InputStream response = this.payService.downloadV3(url);
+    return response;
+  }
+
+  /**
+   * <pre>
+   * 查询账户实时余额API
+   * 接口说明
+   * 适用对象:直连商户 服务商
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_1.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/merchant/fund/balance/{account_type}
+   * 请求方式:GET
+   * </pre>
+   *
+   * @param accountType 服务商账户类型 {@link SpAccountTypeEnum}
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public FundBalanceResult fundBalance(SpAccountTypeEnum accountType) throws WxPayException {
+    String url = String.format("%s/v3/merchant/fund/balance/%s", this.payService.getPayBaseUrl(), accountType);
+    String response = this.payService.getV3(url);
+    return GSON.fromJson(response, FundBalanceResult.class);
+  }
+
+  /**
+   * <pre>
+   * 服务商账户日终余额
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_2.shtml
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+   * </pre>
+   *
+   * @param accountType 服务商账户类型
+   * @param date        查询日期 2020-09-11
+   * @return 返回数据 fund balance result
+   * @throws WxPayException the wx pay exception
+   */
+  @Override
+  public FundBalanceResult spDayEndBalance(SpAccountTypeEnum accountType, String date) {
+    try {
+      return this.payService.getEcommerceService().spDayEndBalance(accountType, date);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    return null;
+  }
+
+
+}

+ 192 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImpl.java

@@ -0,0 +1,192 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.marketing.payroll.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.PayrollService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.crypto.IllegalBlockSizeException;
+
+/**
+ * 微信支付-微工卡
+ *
+ * @author xiaoqiang
+ * @date 2021/12/2
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class PayrollServiceImpl implements PayrollService {
+
+  private static final Gson GSON = new GsonBuilder().create();
+
+  private final WxPayService payService;
+
+    /**
+     * 生成授权token
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/tokens
+     * 请求方式:POST
+     *
+     * @param request 请求参数
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public TokensResult payrollCardTokens(TokensRequest request) throws WxPayException {
+        String url = String.format("%s/v3/payroll-card/tokens", payService.getPayBaseUrl());
+        try {
+            String userName = RsaCryptoUtil.encryptOAEP(request.getUserName(), payService.getConfig().getVerifier().getValidCertificate());
+            request.setUserName(userName);
+            String idCardNumber = RsaCryptoUtil.encryptOAEP(request.getIdCardNumber(), payService.getConfig().getVerifier().getValidCertificate());
+            request.setIdCardNumber(idCardNumber);
+        } catch (IllegalBlockSizeException e) {
+            throw new RuntimeException("加密异常!", e);
+        }
+        String response = payService.postV3(url, GSON.toJson(request));
+        return GSON.fromJson(response, TokensResult.class);
+    }
+
+    /**
+     * 查询微工卡授权关系API
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/relations/{openid}
+     * 请求方式:GET
+     *
+     * @param request 请求参数
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public RelationsResult payrollCardRelations(RelationsRequest request) throws WxPayException {
+        String url = String.format("%s/v3/payroll-card/relations/%s",
+                payService.getPayBaseUrl(), request.getOpenid());
+        String query = String.format("?sub_mchid=%s", request.getSubMchid());
+        if (StringUtils.isNotEmpty(request.getAppid())) {
+            query += "&appid=" + request.getAppid();
+        }
+        if (StringUtils.isNotEmpty(request.getSubAppid())) {
+            query += "&sub_appid=" + request.getSubAppid();
+        }
+        String response = payService.getV3(url + query);
+        return GSON.fromJson(response, RelationsResult.class);
+    }
+
+    /**
+     * 微工卡核身预下单API
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order
+     * 请求方式:POST
+     *
+     * @param request 请求参数
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public PreOrderResult payrollCardPreOrder(PreOrderRequest request) throws WxPayException {
+        String url = String.format("%s/v3/payroll-card/authentications/pre-order", payService.getPayBaseUrl());
+        String response = payService.postV3(url, GSON.toJson(request));
+        return GSON.fromJson(response, PreOrderResult.class);
+    }
+
+    /**
+     * 获取核身结果API
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/{authenticate_number}
+     * 请求方式:GET
+     *
+     * @param subMchid           子商户号
+     * @param authenticateNumber 商家核身单号
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public AuthenticationsResult payrollCardAuthenticationsNumber(String subMchid, String authenticateNumber) throws WxPayException {
+        String url = String.format("%s/v3/payroll-card/authentications/%s", payService.getPayBaseUrl(), authenticateNumber);
+        String query = String.format("?sub_mchid=%s", subMchid);
+        String response = payService.getV3(url + query);
+        return GSON.fromJson(response, AuthenticationsResult.class);
+    }
+
+    /**
+     * 查询核身记录API
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications
+     * 请求方式:GET
+     *
+     * @param request 请求参数
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public AuthRecordResult payrollCardAuthentications(AuthRecordRequest request) throws WxPayException {
+        String url = String.format("%s/v3/payroll-card/authentications", payService.getPayBaseUrl());
+        String query = String.format("?openid=%s&sub_mchid=%s&authenticate_date=%s",
+                request.getOpenid(), request.getAppid(), request.getSubMchid(), request.getAuthenticateDate());
+        if (StringUtils.isNotEmpty(request.getAppid())) {
+            query += "&appid=" + request.getAppid();
+        }
+        if (StringUtils.isNotEmpty(request.getAppid())) {
+            query += "&sub_appid=" + request.getSubAppid();
+        }
+        if (StringUtils.isNotEmpty(request.getAuthenticateState())) {
+            query += "&authenticate_state=" + request.getAuthenticateState();
+        }
+        String response = payService.getV3(url + query);
+        return GSON.fromJson(response, AuthRecordResult.class);
+    }
+
+    /**
+     * 微工卡核身预下单(流程中完成授权)
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/authentications/pre-order-with-auth
+     * 请求方式:POST
+     *
+     * @param request 请求参数
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public PreOrderWithAuthResult payrollCardPreOrderWithAuth(PreOrderWithAuthRequest request) throws WxPayException {
+        String url = String.format("%s/v3/payroll-card/authentications/pre-order-with-auth", payService.getPayBaseUrl());
+        try {
+            String userName = RsaCryptoUtil.encryptOAEP(request.getUserName(), payService.getConfig().getVerifier().getValidCertificate());
+            request.setUserName(userName);
+            String idCardNumber = RsaCryptoUtil.encryptOAEP(request.getIdCardNumber(), payService.getConfig().getVerifier().getValidCertificate());
+            request.setIdCardNumber(idCardNumber);
+        } catch (IllegalBlockSizeException e) {
+            throw new RuntimeException("敏感信息加密异常!", e);
+        }
+        String response = payService.postV3(url, GSON.toJson(request));
+        return GSON.fromJson(response, PreOrderWithAuthResult.class);
+    }
+
+    /**
+     * 按日下载提现异常文件API
+     * 适用对象:服务商
+     * 请求URL:https://api.mch.weixin.qq.com/v3/merchant/fund/withdraw/bill-type/{bill_type}
+     * 请求方式:GET
+     *
+     * @param billType 账单类型
+     *                 NO_SUCC:提现异常账单,包括提现失败和提现退票账单。
+     *       示例值:NO_SUCC
+     * @param billDate 账单日期 表示所在日期的提现账单,格式为YYYY-MM-DD。
+     *                 例如:2008-01-01日发起的提现,2008-01-03日银行返回提现失败,则该提现数据将出现在bill_date为2008-01-03日的账单中。
+     *       示例值:2019-08-17
+     * @return 返回数据
+     * @throws WxPayException the wx pay exception
+     */
+    @Override
+    public PreOrderWithAuthResult merchantFundWithdrawBillType(String billType, String billDate) throws WxPayException {
+        String url = String.format("%s/v3/merchant/fund/withdraw/bill-type/%s", payService.getPayBaseUrl(), billType);
+        String query = String.format("?bill_date=%s", billDate);
+        String response = payService.getV3(url + query);
+        return GSON.fromJson(response, PreOrderWithAuthResult.class);
+    }
+
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 139 - 0
weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PartnerTransferServiceImplTest.java


+ 128 - 0
weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayrollServiceImplTest.java

@@ -0,0 +1,128 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.marketing.payroll.*;
+import com.github.binarywang.wxpay.bean.marketing.transfer.PartnerTransferRequest;
+import com.github.binarywang.wxpay.bean.marketing.transfer.PartnerTransferResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+/**
+ * 微工卡(服务商)
+ *
+ * @author xiaoqiang
+ * @date 2021/12/9
+ */
+@Slf4j
+@Test
+@Guice(modules = ApiTestModule.class)
+public class PayrollServiceImplTest {
+
+
+  @Inject
+  private WxPayService wxPayService;
+
+  private static final Gson GSON = new GsonBuilder().create();
+
+  @Test
+  public void payrollCardTokens() throws WxPayException {
+    TokensRequest request = new TokensRequest();
+    request.setOpenid("onqOjjmo8wmTOOtSKwXtGjg9Gb58");
+    request.setAppid("wxa1111111");
+    request.setSubMchid("1111111");
+    request.setSubAppid("wxa1111111");
+    request.setUserName("LP7bT4hQXUsOZCEvK2YrSiqFsnP0oRMfeoLN0vBg");
+    request.setIdCardNumber("7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5");
+    request.setEmploymentType("LONG_TERM_EMPLOYMENT");
+    TokensResult tokensResult = wxPayService.getPayrollService().payrollCardTokens(request);
+    log.info(tokensResult.toString());
+
+  }
+
+  @Test
+  public void payrollCardRelations() throws WxPayException {
+    RelationsRequest request = new RelationsRequest();
+    request.setOpenid("onqOjjmo8wmTOOtSKwXtGjg9Gb58");
+    request.setSubMchid("1111111");
+    request.setAppid("wxa1111111");
+    request.setSubAppid("wxa1111111");
+    RelationsResult relationsResult = wxPayService.getPayrollService().payrollCardRelations(request);
+    log.info(relationsResult.toString());
+
+  }
+
+
+  @Test
+  public void payrollCardPreOrder() throws WxPayException {
+    PreOrderRequest request = new PreOrderRequest();
+    request.setOpenid("onqOjjmo8wmTOOtSKwXtGjg9Gb58");
+    request.setSubMchid("1111111");
+    request.setAppid("wxa1111111");
+    request.setSubAppid("wxa1111111");
+    request.setAuthenticateNumber("mcdhehfgisdhfjghed39384564i83");
+    request.setProjectName("某项目");
+    request.setEmployerName("某单位名称");
+    PreOrderResult preOrderResult = wxPayService.getPayrollService().payrollCardPreOrder(request);
+    log.info(preOrderResult.toString());
+
+  }
+
+  @Test
+  public void payrollCardAuthenticationsNumber() throws WxPayException {
+    String subMchid = "1111111";
+    String authenticateNumber = "mcdhehfgisdhfjghed39384564i83";
+    AuthenticationsResult authenticationsResult = wxPayService.getPayrollService().payrollCardAuthenticationsNumber(subMchid, authenticateNumber);
+    log.info(authenticationsResult.toString());
+
+  }
+
+  @Test
+  public void payrollCardAuthentications() throws WxPayException {
+    AuthRecordRequest request = new AuthRecordRequest();
+    request.setOpenid("onqOjjmo8wmTOOtSKwXtGjg9Gb58");
+    request.setSubMchid("1111111");
+    request.setAppid("wxa1111111");
+    request.setSubAppid("wxa1111111");
+    request.setAuthenticateDate("2020-12-25");
+    request.setAuthenticateState("AUTHENTICATE_SUCCESS");
+    request.setOffset(0);
+    request.setLimit(10);
+    AuthRecordResult authRecordResult = wxPayService.getPayrollService().payrollCardAuthentications(request);
+    log.info(authRecordResult.toString());
+
+  }
+
+  @Test
+  public void payrollCardPreOrderWithAuth() throws WxPayException {
+    PreOrderWithAuthRequest request = new PreOrderWithAuthRequest();
+    request.setOpenid("onqOjjmo8wmTOOtSKwXtGjg9Gb58");
+    request.setSubMchid("1111111");
+    request.setAppid("wxa1111111");
+    request.setSubAppid("wxa1111111");
+    request.setAuthenticateNumber("mcdhehfgisdhfjghed39384564i83");
+    request.setEmployerName("某用工企业");
+    request.setEmploymentType("LONG_TERM_EMPLOYMENT");
+    request.setIdCardNumber("7FzH5XksJG3a8HLLsaaUV6K54y1OnPMY5");
+    request.setProjectName("某项目");
+    request.setUserName("LP7bT4hQXUsOZCEvK2YrSiqFsnP0oRMfeoLN0vBg");
+    PreOrderWithAuthResult preOrderWithAuthResult = wxPayService.getPayrollService().payrollCardPreOrderWithAuth(request);
+    log.info(preOrderWithAuthResult.toString());
+
+  }
+
+  @Test
+  public void merchantFundWithdrawBillType() throws WxPayException {
+    String billType = "NO_SUCC";
+    String billDate = "2019-08-17";
+    PreOrderWithAuthResult preOrderWithAuthResult = wxPayService.getPayrollService().merchantFundWithdrawBillType(billType, billDate);
+    log.info(preOrderWithAuthResult.toString());
+
+  }
+
+}