Browse Source

:new: #2562 【微信支付】增加微信消费者投诉2.0接口

大森林 3 years ago
parent
commit
94e6d6518b
18 changed files with 1430 additions and 0 deletions
  1. 36 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailRequest.java
  2. 236 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailResult.java
  3. 36 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlRequest.java
  4. 44 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlResult.java
  5. 77 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintRequest.java
  6. 58 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintResult.java
  7. 48 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/CompleteRequest.java
  8. 57 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryRequest.java
  9. 190 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryResult.java
  10. 96 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ResponseRequest.java
  11. 64 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/ComplaintNotifyResult.java
  12. 132 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/ComplaintService.java
  13. 37 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
  14. 30 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
  15. 106 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ComplaintServiceImpl.java
  16. 18 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
  17. 10 0
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
  18. 155 0
      weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/ComplaintServiceImplTest.java

+ 36 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailRequest.java

@@ -0,0 +1,36 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信消费者投诉2.0
+ * 查询投诉单详情请求实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class ComplaintDetailRequest implements Serializable {
+
+  private static final long serialVersionUID = 3244929701614280801L;
+
+  /**
+   * <pre>
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * </pre>
+   */
+  @SerializedName("complaint_id")
+  private String complaintId;
+
+}

+ 236 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailResult.java

@@ -0,0 +1,236 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+
+import com.github.binarywang.wxpay.v3.SpecEncrypt;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 微信消费者投诉2.0
+ * 查询投诉单列表返回的实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+public class ComplaintDetailResult implements Serializable {
+
+  private static final long serialVersionUID = -6201692411535927503L;
+
+  /**
+   * <pre>
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * </pre>
+   */
+  @SerializedName("complaint_id")
+  private String complaintId;
+
+  /**
+   * <pre>
+   * 字段名:投诉时间
+   * 是否必填:是
+   * 描述:投诉时间,遵循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("complaint_time")
+  private String complaintTime;
+
+  /**
+   * <pre>
+   * 字段名:投诉详情
+   * 是否必填:是
+   * 投诉的具体描述
+   * </pre>
+   */
+  @SerializedName("complaint_detail")
+  private String complaintDetail;
+
+  /**
+   * <pre>
+   * 字段名:被诉商户号
+   * 是否必填:是
+   * 投诉单对应的被诉商户号。
+   * </pre>
+   */
+  @SerializedName("complainted_mchid")
+  private String complaintedMchid;
+
+  /**
+   * <pre>
+   * 字段名:投诉单状态
+   * 是否必填:是
+   * 标识当前投诉单所处的处理阶段,具体状态如下所示:
+   * PENDING:待处理
+   * PROCESSING:处理中
+   * PROCESSED:已处理完成
+   * </pre>
+   */
+  @SerializedName("complaint_state")
+  private String complaintState;
+
+  /**
+   * <pre>
+   * 字段名:投诉人联系方式
+   * 是否必填:否
+   * 投诉人联系方式。该字段已做加密处理,具体解密方法详见敏感信息加密说明。
+   * </pre>
+   */
+  @SerializedName("payer_phone")
+  @SpecEncrypt
+  private String payerPhone;
+
+  /**
+   * <pre>
+   * 字段名:投诉人openid
+   * 是否必填:是
+   * 投诉人在商户appid下的唯一标识
+   * </pre>
+   */
+  @SerializedName("payer_openid")
+  private String payerOpenid;
+
+
+  /**
+   * <pre>
+   * 字段名:投诉资料列表
+   * 是否必填:是
+   * 用户上传的投诉相关资料,包括图片凭证等
+   * </pre>
+   */
+  @SerializedName("complaint_media_list")
+  private List<ComplaintMedia> complaintMediaList;
+
+  @Data
+  public static class ComplaintMedia implements Serializable {
+    private static final long serialVersionUID = 4240983048700956803L;
+
+    /**
+     * <pre>
+     * 字段名:媒体文件业务类型
+     * 是否必填:是
+     * 描述:
+     * 媒体文件对应的业务类型
+     * USER_COMPLAINT_IMAGE:用户投诉图片,用户提交投诉时上传的图片凭证
+     * OPERATION_IMAGE:操作流水图片,用户、商户、微信支付客服在协商解决投诉时,上传的图片凭证
+     * 注:用户上传的图片凭证会以白名单的形式提供给商户,若希望查看用户图片,联系微信支付客服
+     * 示例值:USER_COMPLAINT_IMAGE
+     * </pre>
+     */
+    @SerializedName("media_type")
+    private String mediaType;
+
+    /**
+     * <pre>
+     * 字段名:媒体文件请求url
+     * 是否必填:是
+     * 描述:
+     * 微信返回的媒体文件请求url
+     * </pre>
+     */
+    @SerializedName("media_url")
+    private String mediaUrl;
+
+  }
+
+  /**
+   * <pre>
+   * 字段名:投诉单关联订单信息
+   * 是否必填:是
+   * 投诉单关联订单信息
+   * 注:投诉单和订单目前是一对一关系,array是预留未来一对多的扩展
+   * </pre>
+   */
+  @SerializedName("complaint_order_info")
+  private List<ComplaintOrder> complaintOrderInfo;
+
+  @Data
+  public static class ComplaintOrder implements Serializable {
+    private static final long serialVersionUID = 4240983048700956804L;
+
+    /**
+     * <pre>
+     * 字段名:微信订单号
+     * 是否必填:是
+     * 描述:
+     * 投诉单关联的微信订单号
+     * </pre>
+     */
+    @SerializedName("transaction_id")
+    private String transactionId;
+
+    /**
+     * <pre>
+     * 字段名:商户订单号
+     * 是否必填:是
+     * 描述:
+     * 投诉单关联的商户订单号
+     * </pre>
+     */
+    @SerializedName("out_trade_no")
+    private String outTradeNo;
+
+    /**
+     * <pre>
+     * 字段名:订单金额
+     * 是否必填:是
+     * 描述:
+     * 订单金额,单位(分)
+     * </pre>
+     */
+    @SerializedName("amount")
+    private Integer amount;
+
+  }
+
+  /**
+   * <pre>
+   * 字段名:投诉单是否已全额退款
+   * 是否必填:是
+   * 描述:
+   * 投诉单下所有订单是否已全部全额退款
+   * </pre>
+   */
+  @SerializedName("complaint_full_refunded")
+  private Boolean complaintFullRefunded;
+
+  /**
+   * <pre>
+   * 字段名:是否有待回复的用户留言
+   * 是否必填:是
+   * 描述:
+   * 投诉单是否有待回复的用户留言
+   * </pre>
+   */
+  @SerializedName("incoming_user_response")
+  private Boolean incomingUserResponse;
+
+  /**
+   * <pre>
+   * 字段名:问题描述
+   * 是否必填:是
+   * 描述:
+   * 用户发起投诉前选择的faq标题(2021年7月15日之后的投诉单均包含此信息)
+   * </pre>
+   */
+  @SerializedName("problem_description")
+  private String problemDescription;
+
+  /**
+   * <pre>
+   * 字段名:用户投诉次数
+   * 是否必填:是
+   * 描述:
+   * 用户投诉次数。用户首次发起投诉记为1次,用户每有一次继续投诉就加1
+   * </pre>
+   */
+  @SerializedName("user_complaint_times")
+  private Integer userComplaintTimes;
+}

+ 36 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlRequest.java

@@ -0,0 +1,36 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信消费者投诉2.0
+ * 投诉通知请求实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class ComplaintNotifyUrlRequest implements Serializable {
+
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * <pre>
+   * 字段名:通知地址
+   * 是否必填:是
+   * 描述:通知地址,仅支持https。
+   * </pre>
+   */
+  @SerializedName("url")
+  private String url;
+
+}

+ 44 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlResult.java

@@ -0,0 +1,44 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+
+import com.github.binarywang.wxpay.bean.media.MarketingImageUploadResult;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 微信消费者投诉2.0
+ * 投诉通知地址返回的实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+public class ComplaintNotifyUrlResult implements Serializable {
+
+  private static final long serialVersionUID = -6201692411535927502L;
+
+  /**
+   * <pre>
+   * 字段名:商户号
+   * 是否必填:是
+   * 描述:返回创建回调地址的商户号,由微信支付生成并下发。
+   * </pre>
+   */
+  @SerializedName("mchid")
+  private String mchid;
+
+  /**
+   * <pre>
+   * 字段名:通知地址
+   * 是否必填:是
+   * 描述:通知地址,仅支持https。
+   * </pre>
+   */
+  @SerializedName("url")
+  private String url;
+
+}

+ 77 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintRequest.java

@@ -0,0 +1,77 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信消费者投诉2.0
+ * 查询投诉单列表请求实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class ComplaintRequest implements Serializable {
+
+  private static final long serialVersionUID = 3244929701614280800L;
+
+  /**
+   * <pre>
+   * 字段名:分页大小
+   * 是否必填:否
+   * 描述:设置该次请求返回的最大投诉条数,范围【1,50】,商户自定义字段,不传默认为10。
+   * 注:如遇到提示“当前查询结果数据量过大”,是回包触发微信支付下行数据包大小限制,请缩小入参limit并重试。
+   * </pre>
+   */
+  @SerializedName("limit")
+  private Integer limit = 10;
+
+  /**
+   * <pre>
+   * 字段名:分页开始位置
+   * 是否必填:否
+   * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0 。
+   * </pre>
+   */
+  @SerializedName("offset")
+  private Integer offset = 0;
+
+  /**
+   * <pre>
+   * 字段名:开始日期
+   * 是否必填:是
+   * 描述:投诉发生的开始日期,格式为yyyy-MM-DD。注意,查询日期跨度不超过30天,当前查询为实时查询
+   * </pre>
+   */
+  @SerializedName("begin_date")
+  private String beginDate;
+
+  /**
+   * <pre>
+   * 字段名:结束日期
+   * 是否必填:是
+   * 描述:投诉发生的结束日期,格式为yyyy-MM-DD。注意,查询日期跨度不超过30天,当前查询为实时查询
+   * </pre>
+   */
+  @SerializedName("end_date")
+  private String endDate;
+
+  /**
+   * <pre>
+   * 字段名:被诉商户号
+   * 是否必填:否
+   * 描述:投诉单对应的被诉商户号。
+   * </pre>
+   */
+  @SerializedName("complainted_mchid")
+  private String complaintedMchid;
+
+}

+ 58 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintResult.java

@@ -0,0 +1,58 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 微信消费者投诉2.0
+ * 查询投诉单列表返回的实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+public class ComplaintResult implements Serializable {
+
+  private static final long serialVersionUID = -6201692411535927502L;
+
+  /**
+   * <pre>
+   * 字段名:分页大小
+   * 是否必填:是
+   * 描述:设置该次请求返回的最大投诉条数,范围【1,50】
+   * </pre>
+   */
+  @SerializedName("limit")
+  private Integer limit;
+
+  /**
+   * <pre>
+   * 字段名:分页开始位置
+   * 是否必填:是
+   * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回。
+   * </pre>
+   */
+  @SerializedName("offset")
+  private Integer offset;
+
+  /**
+   * <pre>
+   * 字段名:投诉总条数
+   * 是否必填:否
+   * 描述:投诉总条数,当offset=0时返回
+   * </pre>
+   */
+  @SerializedName("total_count")
+  private Integer totalCount;
+
+  /**
+   * 用户投诉信息详情
+   */
+  @SerializedName("data")
+  private List<ComplaintDetailResult> data;
+
+}

+ 48 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/CompleteRequest.java

@@ -0,0 +1,48 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信消费者投诉2.0
+ * 反馈处理完成请求实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class CompleteRequest implements Serializable {
+
+  private static final long serialVersionUID = 3243229701614220801L;
+
+  /**
+   * <pre>
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * </pre>
+   */
+  @SerializedName("complaint_id")
+  @Expose
+  private String complaintId;
+
+  /**
+   * <pre>
+   * 字段名:被诉商户号
+   * 是否必填:是
+   * 描述:投诉单对应的被诉商户号
+   * </pre>
+   */
+  @SerializedName("complainted_mchid")
+  private String complaintedMchid;
+
+}

+ 57 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryRequest.java

@@ -0,0 +1,57 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信消费者投诉2.0
+ * 查询投诉协商历史请求实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class NegotiationHistoryRequest implements Serializable {
+
+  private static final long serialVersionUID = 3244929701614280806L;
+
+  /**
+   * <pre>
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * </pre>
+   */
+  @SerializedName("complaint_id")
+  private String complaintId;
+
+  /**
+   * <pre>
+   * 字段名:分页大小
+   * 是否必填:否
+   * 描述:设置该次请求返回的最大投诉条数,范围【1,50】,商户自定义字段,不传默认为10。
+   * 注:如遇到提示“当前查询结果数据量过大”,是回包触发微信支付下行数据包大小限制,请缩小入参limit并重试。
+   * </pre>
+   */
+  @SerializedName("limit")
+  private Integer limit = 10;
+
+  /**
+   * <pre>
+   * 字段名:分页开始位置
+   * 是否必填:否
+   * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0 。
+   * </pre>
+   */
+  @SerializedName("offset")
+  private Integer offset = 0;
+
+}

+ 190 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryResult.java

@@ -0,0 +1,190 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 微信消费者投诉2.0
+ * 查询投诉单协商历史返回的实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+public class NegotiationHistoryResult implements Serializable {
+
+  private static final long serialVersionUID = -6201692411535927502L;
+
+  /**
+   * <pre>
+   * 字段名:分页大小
+   * 是否必填:是
+   * 描述:设置该次请求返回的最大投诉条数,范围【1,50】
+   * </pre>
+   */
+  @SerializedName("limit")
+  private Integer limit;
+
+  /**
+   * <pre>
+   * 字段名:分页开始位置
+   * 是否必填:是
+   * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回。
+   * </pre>
+   */
+  @SerializedName("offset")
+  private Integer offset;
+
+  /**
+   * <pre>
+   * 字段名:投诉协商历史总条数
+   * 是否必填:否
+   * 描述:投诉协商历史总条数,当offset=0时返回
+   * </pre>
+   */
+  @SerializedName("total_count")
+  private Integer totalCount;
+
+  /**
+   * 投诉协商历史
+   */
+  @SerializedName("data")
+  private List<NegotiationHistory> data;
+
+  @Data
+  public static class NegotiationHistory implements Serializable {
+    private static final long serialVersionUID = 4240983048700956824L;
+
+    /**
+     * <pre>
+     * 字段名:投诉资料列表
+     * 是否必填:是
+     * 用户上传的投诉相关资料,包括图片凭证等
+     * </pre>
+     */
+    @SerializedName("complaint_media_list")
+    private List<ComplaintDetailResult.ComplaintMedia> complaintMediaList;
+
+    @Data
+    public static class ComplaintMedia implements Serializable {
+      private static final long serialVersionUID = 4240983048700956803L;
+
+      /**
+       * <pre>
+       * 字段名:媒体文件业务类型
+       * 是否必填:是
+       * 描述:
+       * 媒体文件对应的业务类型
+       * USER_COMPLAINT_IMAGE:用户投诉图片,用户提交投诉时上传的图片凭证
+       * OPERATION_IMAGE:操作流水图片,用户、商户、微信支付客服在协商解决投诉时,上传的图片凭证
+       * 注:用户上传的图片凭证会以白名单的形式提供给商户,若希望查看用户图片,联系微信支付客服
+       * 示例值:USER_COMPLAINT_IMAGE
+       * </pre>
+       */
+      @SerializedName("media_type")
+      private String mediaType;
+
+      /**
+       * <pre>
+       * 字段名:媒体文件请求url
+       * 是否必填:是
+       * 描述:
+       * 微信返回的媒体文件请求url
+       * </pre>
+       */
+      @SerializedName("media_url")
+      private String mediaUrl;
+
+    }
+
+    /**
+     * <pre>
+     * 字段名:操作流水号
+     * 是否必填:是
+     * 描述:
+     * 操作流水号
+     * </pre>
+     */
+    @SerializedName("log_id")
+    private String logId;
+
+    /**
+     * <pre>
+     * 字段名:操作人
+     * 是否必填:是
+     * 描述:
+     * 当前投诉协商记录的操作人
+     * </pre>
+     */
+    @SerializedName("operator")
+    private String operator;
+
+    /**
+     * <pre>
+     * 字段名:操作时间
+     * 是否必填:是
+     * 描述:
+     * 当前投诉协商记录的操作时间,遵循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("operate_time")
+    private String operateTime;
+
+    /**
+     * <pre>
+     * 字段名:操作类型
+     * 是否必填:是
+     * 描述:
+     * 当前投诉协商记录的操作类型,对应枚举:
+     * USER_CREATE_COMPLAINT:用户提交投诉
+     * USER_CONTINUE_COMPLAINT:用户继续投诉
+     * USER_RESPONSE:用户留言
+     * PLATFORM_RESPONSE:平台留言
+     * MERCHANT_RESPONSE:商户留言
+     * MERCHANT_CONFIRM_COMPLETE:商户申请结单
+     * COMPLAINT_FULL_REFUNDED:投诉单全额退款
+     * USER_CREATE_COMPLAINT_SYSTEM_MESSAGE:用户提交投诉系统通知
+     * COMPLAINT_FULL_REFUNDED_SYSTEM_MESSAGE:投诉单全额退款系统通知
+     * USER_CONTINUE_COMPLAINT_SYSTEM_MESSAGE:用户继续投诉系统通知
+     * MERCHANT_CONFIRM_COMPLETE_SYSTEM_MESSAGE:商户申请结单系统通知
+     * USER_REVOKE_COMPLAINT:用户主动撤诉(只存在于历史投诉单的协商历史中)
+     * PLATFORM_HELP_APPLICATION:平台问询
+     * USER_APPLY_PLATFORM_HELP:申请协助
+     * </pre>
+     */
+    @SerializedName("operate_type")
+    private String operateType;
+
+    /**
+     * <pre>
+     * 字段名:操作内容
+     * 是否必填:否
+     * 描述:
+     * 当前投诉协商记录的具体内容
+     * </pre>
+     */
+    @SerializedName("operate_details")
+    private String operateDetails;
+
+    /**
+     * <pre>
+     * 字段名:图片凭证
+     * 是否必填:是
+     * 描述:
+     * 当前投诉协商记录提交的图片凭证(url格式),最多返回4张图片,url有效时间为1小时。如未查询到协商历史图片凭证,则返回空数组。
+     * 注:本字段包含商户、微信支付客服在协商解决投诉时上传的图片凭证,若希望查看用户图片,请使用complaint_media_list字段并联系微信支付客服
+     * </pre>
+     */
+    @SerializedName("image_list")
+    private List<String> imageList;
+
+  }
+
+}

+ 96 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ResponseRequest.java

@@ -0,0 +1,96 @@
+package com.github.binarywang.wxpay.bean.complaint;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信消费者投诉2.0
+ * 提交回复请求实体
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ * @date 2022-3-19
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResponseRequest implements Serializable {
+
+  private static final long serialVersionUID = 3244929701614220801L;
+
+  /**
+   * <pre>
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * </pre>
+   */
+  @SerializedName("complaint_id")
+  @Expose
+  private String complaintId;
+
+  /**
+   * <pre>
+   * 字段名:被诉商户号
+   * 是否必填:是
+   * 描述:投诉单对应的被诉商户号
+   * </pre>
+   */
+  @SerializedName("complainted_mchid")
+  private String complaintedMchid;
+
+  /**
+   * <pre>
+   * 字段名:回复内容
+   * 是否必填:是
+   * 描述:具体的投诉处理方案,限制200个字符以内。
+   * </pre>
+   */
+  @SerializedName("response_content")
+  private String responseContent;
+
+  /**
+   * <pre>
+   * 字段名:回复图片
+   * 是否必填:否
+   * 描述:
+   * 传入调用商户上传反馈图片接口返回的media_id,最多上传4张图片凭证
+   * 示例值:file23578_21798531.jpg
+   * </pre>
+   */
+  @SerializedName("response_images")
+  private String responseImages;
+
+  /**
+   * <pre>
+   * 字段名:跳转链接
+   * 是否必填:是
+   * 描述:
+   * 商户可在回复中附加跳转链接,引导用户跳转至商户客诉处理页面,链接需满足https格式
+   * 注:配置文字链属于灰度功能, 若有需要请使用超管邮箱,按照要求发送邮件申请。邮件要求详情见:
+   * 商户申请开通留言链接白名单指南。
+   * 示例值:https://www.xxx.com/notify
+   * </pre>
+   */
+  @SerializedName("jump_url")
+  private String jumpUrl;
+
+  /**
+   * <pre>
+   * 字段名:跳转链接文案
+   * 是否必填:否
+   * 描述:
+   * 实际展示给用户的文案,附在回复内容之后。用户点击文案,即可进行跳转。
+   * 注:若传入跳转链接,则跳转链接文案为必传项,二者缺一不可。
+   * </pre>
+   */
+  @SerializedName("jump_url_text")
+  private String jumpUrlText;
+
+}

+ 64 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/ComplaintNotifyResult.java

@@ -0,0 +1,64 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 投诉通知.
+ * 文档见:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_16.shtml
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ */
+@Data
+@NoArgsConstructor
+public class ComplaintNotifyResult implements Serializable {
+  private static final long serialVersionUID = -1L;
+  /**
+   * 源数据
+   */
+  private OriginNotifyResponse rawData;
+  /**
+   * 解密后的数据
+   */
+  private DecryptNotifyResult result;
+
+  @Data
+  @NoArgsConstructor
+  public static class DecryptNotifyResult implements Serializable {
+    private static final long serialVersionUID = -1L;
+
+    /**
+     * <pre>
+     * 字段名:投诉单号
+     * 是否必填:是
+     * 描述:
+     *  投诉单对应的投诉单号
+     * </pre>
+     */
+    @SerializedName(value = "complaint_id")
+    private String complaintId;
+
+    /**
+     * <pre>
+     * 字段名:动作类型
+     * 是否必填:是
+     * 描述:
+     * 触发本次投诉通知回调的具体动作类型,枚举如下:
+     * CREATE_COMPLAINT:用户提交投诉
+     * CONTINUE_COMPLAINT:用户继续投诉
+     * USER_RESPONSE:用户新留言
+     * RESPONSE_BY_PLATFORM:平台新留言
+     * SELLER_REFUND:收款方全额退款
+     * MERCHANT_RESPONSE:商户新回复
+     * MERCHANT_CONFIRM_COMPLETE:商户反馈处理完成
+     * </pre>
+     */
+    @SerializedName(value = "action_type")
+    private String actionType;
+
+  }
+
+}

+ 132 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/ComplaintService.java

@@ -0,0 +1,132 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.complaint.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+import javax.crypto.BadPaddingException;
+
+/**
+ * <pre>
+ * 微信支付 消费者投诉2.0 API.
+ * Created by jmdhappy on 2022/3/19.
+ * </pre>
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ */
+public interface ComplaintService {
+
+  /**
+   * <pre>
+   * 查询投诉单列表API
+   * 商户可通过调用此接口,查询指定时间段的所有用户投诉信息,以分页输出查询结果。
+   * 对于服务商、渠道商,可通过调用此接口,查询指定子商户号对应子商户的投诉信息,若不指定则查询所有子商户投诉信息。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_11.shtml
+   * </pre>
+   *
+   * @param request {@link ComplaintRequest} 查询投诉单列表请求数据
+   * @return {@link ComplaintResult} 微信返回的投诉单列表
+   * @throws WxPayException the wx pay exception
+   */
+  ComplaintResult queryComplaints(ComplaintRequest request) throws WxPayException, BadPaddingException;
+
+  /**
+   * <pre>
+   * 查询投诉单详情API
+   * 商户可通过调用此接口,查询指定投诉单的用户投诉详情,包含投诉内容、投诉关联订单、投诉人联系方式等信息,方便商户处理投诉。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_13.shtml
+   * </pre>
+   *
+   * @param request {@link ComplaintDetailRequest} 投诉单详情请求数据
+   * @return {@link ComplaintDetailResult} 微信返回的投诉单详情
+   * @throws WxPayException the wx pay exception
+   */
+  ComplaintDetailResult getComplaint(ComplaintDetailRequest request) throws WxPayException, BadPaddingException;
+
+  /**
+   * <pre>
+   * 查询投诉协商历史API
+   * 商户可通过调用此接口,查询指定投诉的用户商户协商历史,以分页输出查询结果,方便商户根据处理历史来制定后续处理方案。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_12.shtml
+   * </pre>
+   *
+   * @param request {@link NegotiationHistoryRequest} 请求数据
+   * @return {@link NegotiationHistoryResult} 微信返回结果
+   * @throws WxPayException the wx pay exception
+   */
+  NegotiationHistoryResult queryNegotiationHistorys(NegotiationHistoryRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 创建投诉通知回调地址API
+   * 商户通过调用此接口创建投诉通知回调URL,当用户产生新投诉且投诉状态已变更时,微信支付会通过回 调URL通知商户。对于服务商、渠道商,会收到所有子商户的投诉信息推送。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_2.shtml
+   * </pre>
+   *
+   * @param request {@link ComplaintDetailRequest} 请求数据
+   * @return {@link ComplaintNotifyUrlResult} 微信返回结果
+   * @throws WxPayException the wx pay exception
+   */
+  ComplaintNotifyUrlResult addComplaintNotifyUrl(ComplaintNotifyUrlRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 查询投诉通知回调地址API
+   * 商户通过调用此接口查询投诉通知的回调URL。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_3.shtml
+   * </pre>
+   *
+   * @return {@link ComplaintNotifyUrlResult} 微信返回结果
+   * @throws WxPayException the wx pay exception
+   */
+  ComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxPayException;
+
+  /**
+   * <pre>
+   * 更新投诉通知回调地址API
+   * 商户通过调用此接口更新投诉通知的回调URL。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_4.shtml
+   * </pre>
+   *
+   * @param request {@link ComplaintDetailRequest} 请求数据
+   * @return {@link ComplaintNotifyUrlResult} 微信返回结果
+   * @throws WxPayException the wx pay exception
+   */
+  ComplaintNotifyUrlResult updateComplaintNotifyUrl(ComplaintNotifyUrlRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 删除投诉通知回调地址API
+   * 当商户不再需要推送通知时,可通过调用此接口删除投诉通知的回调URL,取消通知回调。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_5.shtml
+   * </pre>
+   *
+   * @throws WxPayException the wx pay exception
+   */
+  void deleteComplaintNotifyUrl() throws WxPayException;
+
+  /**
+   * <pre>
+   * 提交回复API
+   * 商户可通过调用此接口,提交回复内容。其中上传图片凭证需首先调用商户上传反馈图片接口,得到图片id,再将id填入请求。
+   * 回复可配置文字链,传入跳转链接文案和跳转链接字段,用户点击即可跳转对应页面
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_14.shtml
+   * </pre>
+   *
+   * @param request {@link ResponseRequest} 请求数据
+   * @throws WxPayException the wx pay exception
+   */
+  void submitResponse(ResponseRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 反馈处理完成API
+   * 商户可通过调用此接口,反馈投诉单已处理完成。
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_15.shtml
+   * </pre>
+   *
+   * @param request {@link CompleteRequest} 请求数据
+   * @throws WxPayException the wx pay exception
+   */
+  void complete(CompleteRequest request) throws WxPayException;
+
+}

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

@@ -174,6 +174,25 @@ public interface WxPayService {
   InputStream downloadV3(String url) throws WxPayException;
 
   /**
+   * 发送put V3请求,得到响应字符串.
+   *
+   * @param url 请求地址
+   * @param url 请求数据
+   * @return 返回请求结果字符串 string
+   * @throws WxPayException the wx pay exception
+   */
+  String putV3(String url, String requestStr) throws WxPayException;
+
+  /**
+   * 发送delete V3请求,得到响应字符串.
+   *
+   * @param url 请求地址
+   * @return 返回请求结果字符串 string
+   * @throws WxPayException the wx pay exception
+   */
+  String deleteV3(String url) throws WxPayException;
+
+  /**
    * 获取微信签约代扣服务类
    * @return entrust service
    */
@@ -1296,4 +1315,22 @@ public interface WxPayService {
    * @throws WxPayException .
    */
   WxPayQueryExchangeRateResult queryExchangeRate(String feeType, String date) throws WxPayException;
+
+  /**
+   * 解析投诉通知
+   * 详见https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_16.shtml
+   *
+   * @param notifyData 通知数据
+   * @param header     通知头部数据,不传则表示不校验头
+   * @return the wx pay refund notify result
+   * @throws WxPayException the wx pay exception
+   */
+  ComplaintNotifyResult parseComplaintNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
+
+  /**
+   * 获取消费者投诉服务类.
+   *
+   * @return the complaints service
+   */
+  ComplaintService getComplaintsService();
 }

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

@@ -77,6 +77,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   private final WxEntrustPapService wxEntrustPapService = new WxEntrustPapServiceImpl(this);
   private final PartnerTransferService partnerTransferService = new PartnerTransferServiceImpl(this);
   private final PayrollService payrollService = new PayrollServiceImpl(this);
+  private final ComplaintService complaintsService = new ComplaintServiceImpl(this);
 
   protected Map<String, WxPayConfig> configMap;
 
@@ -1222,4 +1223,33 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
     result.checkResult(this, request.getSignType(), true);
     return result;
   }
+
+  @Override
+  public ComplaintNotifyResult parseComplaintNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+    if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) {
+      throw new WxPayException("非法请求,头部信息验证失败");
+    }
+    OriginNotifyResponse response = GSON.fromJson(notifyData, OriginNotifyResponse.class);
+    OriginNotifyResponse.Resource resource = response.getResource();
+    String cipherText = resource.getCiphertext();
+    String associatedData = resource.getAssociatedData();
+    String nonce = resource.getNonce();
+    String apiV3Key = this.getConfig().getApiV3Key();
+    try {
+      String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
+      ComplaintNotifyResult.DecryptNotifyResult decryptNotifyResult = GSON.fromJson(result, ComplaintNotifyResult.DecryptNotifyResult.class);
+      ComplaintNotifyResult notifyResult = new ComplaintNotifyResult();
+      notifyResult.setRawData(response);
+      notifyResult.setResult(decryptNotifyResult);
+      return notifyResult;
+    } catch (GeneralSecurityException | IOException e) {
+      throw new WxPayException("解析报文异常!", e);
+    }
+  }
+
+  @Override
+  public ComplaintService getComplaintsService() {
+    return complaintsService;
+  }
+
 }

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

@@ -0,0 +1,106 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.complaint.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.ComplaintService;
+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 javax.crypto.BadPaddingException;
+import java.util.List;
+
+/**
+ * <pre>
+ * 消费者投诉2.0 实现.
+ * Created by jmdhappy on 2022/3/19.
+ * </pre>
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ */
+@RequiredArgsConstructor
+public class ComplaintServiceImpl implements ComplaintService {
+  private static final Gson GSON = new GsonBuilder().create();
+  private final WxPayService payService;
+
+  @Override
+  public ComplaintResult queryComplaints(ComplaintRequest request) throws WxPayException, BadPaddingException {
+    String url = String.format("%s/v3/merchant-service/complaints-v2?limit=%d&offset=%d&begin_date=%s&end_date=%s&complainted_mchid=%s",
+      this.payService.getPayBaseUrl(), request.getLimit(), request.getOffset(), request.getBeginDate(), request.getEndDate(), request.getComplaintedMchid());
+    String response = this.payService.getV3(url);
+    ComplaintResult complaintResult = GSON.fromJson(response, ComplaintResult.class);
+    List<ComplaintDetailResult> data = complaintResult.getData();
+    for (ComplaintDetailResult complaintDetailResult : data) {
+      // 对手机号进行解密操作
+      String payerPhone = RsaCryptoUtil.decryptOAEP(complaintDetailResult.getPayerPhone(), this.payService.getConfig().getPrivateKey());
+      complaintDetailResult.setPayerPhone(payerPhone);
+    }
+    return complaintResult;
+  }
+
+  @Override
+  public ComplaintDetailResult getComplaint(ComplaintDetailRequest request) throws WxPayException, BadPaddingException {
+    String url = String.format("%s/v3/merchant-service/complaints-v2/%s",
+      this.payService.getPayBaseUrl(), request.getComplaintId());
+    String response = this.payService.getV3(url);
+    ComplaintDetailResult result = GSON.fromJson(response, ComplaintDetailResult.class);
+    // 对手机号进行解密操作
+    String payerPhone = RsaCryptoUtil.decryptOAEP(result.getPayerPhone(), this.payService.getConfig().getPrivateKey());
+    result.setPayerPhone(payerPhone);
+    return result;
+  }
+
+  @Override
+  public NegotiationHistoryResult queryNegotiationHistorys(NegotiationHistoryRequest request) throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaints-v2/%s/negotiation-historys?limit=%d&offset=%d",
+      this.payService.getPayBaseUrl(), request.getComplaintId(), request.getLimit(), request.getOffset());
+    String response = this.payService.getV3(url);
+    return GSON.fromJson(response, NegotiationHistoryResult.class);
+  }
+
+  @Override
+  public ComplaintNotifyUrlResult addComplaintNotifyUrl(ComplaintNotifyUrlRequest request) throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaint-notifications", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, ComplaintNotifyUrlResult.class);
+  }
+
+  @Override
+  public ComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaint-notifications", this.payService.getPayBaseUrl());
+    String response = this.payService.getV3(url);
+    return GSON.fromJson(response, ComplaintNotifyUrlResult.class);
+  }
+
+  @Override
+  public ComplaintNotifyUrlResult updateComplaintNotifyUrl(ComplaintNotifyUrlRequest request) throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaint-notifications", this.payService.getPayBaseUrl());
+    String response = this.payService.putV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, ComplaintNotifyUrlResult.class);
+  }
+
+  @Override
+  public void deleteComplaintNotifyUrl() throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaint-notifications", this.payService.getPayBaseUrl());
+    this.payService.deleteV3(url);
+  }
+
+  @Override
+  public void submitResponse(ResponseRequest request) throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaints-v2/%s/response", this.payService.getPayBaseUrl(), request.getComplaintId());
+    // 上面url已经含有complaintId,这里设置为空,避免在body中再次传递,否则微信会报错
+    request.setComplaintId(null);
+    this.payService.postV3(url, GSON.toJson(request));
+  }
+
+  @Override
+  public void complete(CompleteRequest request) throws WxPayException {
+    String url = String.format("%s/v3/merchant-service/complaints-v2/%s/complete", this.payService.getPayBaseUrl(), request.getComplaintId());
+    // 上面url已经含有complaintId,这里设置为空,避免在body中再次传递,否则微信会报错
+    request.setComplaintId(null);
+    this.payService.postV3(url, GSON.toJson(request));
+  }
+
+}

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

@@ -269,6 +269,24 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
     }
   }
 
+  @Override
+  public String putV3(String url, String requestStr) throws WxPayException {
+    HttpPut httpPut = new HttpPut(url);
+    StringEntity entity = this.createEntry(requestStr);
+    httpPut.setEntity(entity);
+    httpPut.addHeader("Accept", "application/json");
+    httpPut.addHeader("Content-Type", "application/json");
+    return requestV3(url, httpPut);
+  }
+
+  @Override
+  public String deleteV3(String url) throws WxPayException {
+    HttpDelete httpDelete = new HttpDelete(url);
+    httpDelete.addHeader("Accept", "application/json");
+    httpDelete.addHeader("Content-Type", "application/json");
+    return requestV3(url, httpDelete);
+  }
+
   private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
     CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient();
     if (null == apiV3HttpClient) {

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

@@ -96,6 +96,16 @@ public class WxPayServiceJoddHttpImpl extends BaseWxPayServiceImpl {
     return null;
   }
 
+  @Override
+  public String putV3(String url, String requestStr) throws WxPayException {
+    return null;
+  }
+
+  @Override
+  public String deleteV3(String url) throws WxPayException {
+    return null;
+  }
+
   private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey) throws WxPayException {
     HttpRequest request = HttpRequest
       .post(url)

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

@@ -0,0 +1,155 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.complaint.*;
+import com.github.binarywang.wxpay.bean.profitsharing.*;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import javax.crypto.BadPaddingException;
+
+/**
+ * <pre>
+ *  消费者投诉2.0 测试类
+ * </pre>
+ *
+ * @author <a href="https://gitee.com/jeequan/jeepay">jmdhappy</a>
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class ComplaintServiceImplTest {
+
+  private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+  @Inject
+  private WxPayService payService;
+
+  private static final String complaintId = "200231020220320120496109901";
+
+  /**
+   * 查询投诉单列表API
+   * @throws WxPayException
+   */
+  @Test
+  public void testQueryComplaints() throws WxPayException, BadPaddingException {
+    ComplaintRequest request = ComplaintRequest
+      .newBuilder()
+      .offset(0)
+      .limit(10)
+      .beginDate("2022-03-01")
+      .endDate("2022-03-20")
+      .complaintedMchid(this.payService.getConfig().getMchId())
+      .build();
+    this.logger.info(this.payService.getComplaintsService().queryComplaints(request).toString());
+  }
+
+  /**
+   * 查询投诉单详情API
+   * @throws WxPayException
+   */
+  @Test
+  public void testGetComplaint() throws WxPayException, BadPaddingException {
+    ComplaintDetailRequest request = ComplaintDetailRequest
+      .newBuilder()
+      .complaintId(complaintId)
+      .build();
+    this.logger.info(this.payService.getComplaintsService().getComplaint(request).toString());
+  }
+
+  /**
+   * 查询投诉协商历史API
+   * @throws WxPayException
+   */
+  @Test
+  public void testQueryNegotiationHistorys() throws WxPayException {
+    NegotiationHistoryRequest request = NegotiationHistoryRequest
+      .newBuilder()
+      .complaintId(complaintId)
+      .offset(0)
+      .limit(20)
+      .build();
+    this.logger.info(this.payService.getComplaintsService().queryNegotiationHistorys(request).toString());
+  }
+
+  /**
+   * 创建投诉通知回调地址API
+   * @throws WxPayException
+   */
+  @Test
+  public void testAddComplaintNotifyUrl() throws WxPayException {
+    ComplaintNotifyUrlRequest request = ComplaintNotifyUrlRequest
+      .newBuilder()
+      .url("https://jeepay.natapp4.cc")
+      .build();
+    this.logger.info(this.payService.getComplaintsService().addComplaintNotifyUrl(request).toString());
+  }
+
+  /**
+   * 查询投诉通知回调地址API
+   * @throws WxPayException
+   */
+  @Test
+  public void testGetComplaintNotifyUrl() throws WxPayException {
+    this.logger.info(this.payService.getComplaintsService().getComplaintNotifyUrl().toString());
+  }
+
+  /**
+   * 更新投诉通知回调地址API
+   * @throws WxPayException
+   */
+  @Test
+  public void testUpdateComplaintNotifyUrl() throws WxPayException {
+    ComplaintNotifyUrlRequest request = ComplaintNotifyUrlRequest
+      .newBuilder()
+      .url("https://jeepay1.natapp4.cc")
+      .build();
+    this.logger.info(this.payService.getComplaintsService().updateComplaintNotifyUrl(request).toString());
+  }
+
+  /**
+   * 删除投诉通知回调地址API
+   * @throws WxPayException
+   */
+  @Test
+  public void testDeleteComplaintNotifyUrl() throws WxPayException {
+    this.payService.getComplaintsService().deleteComplaintNotifyUrl();
+  }
+
+  /**
+   * 提交回复API
+   * @throws WxPayException
+   */
+  @Test
+  public void testSubmitResponse() throws WxPayException {
+    ResponseRequest request = ResponseRequest
+      .newBuilder()
+      .complaintId(complaintId)
+      .complaintedMchid(this.payService.getConfig().getMchId())
+      .responseContent("测试投诉接口1233,正在处理,不要炸鸡")
+      //.jumpUrl("https://www.baidu.com")
+      //.jumpUrlText("问题解决方案")
+      .build();
+    this.payService.getComplaintsService().submitResponse(request);
+  }
+
+  /**
+   * 反馈处理完成API
+   * @throws WxPayException
+   */
+  @Test
+  public void testComplete() throws WxPayException {
+    CompleteRequest request = CompleteRequest
+      .newBuilder()
+      .complaintId(complaintId)
+      .complaintedMchid(this.payService.getConfig().getMchId())
+      .build();
+    this.payService.getComplaintsService().complete(request);
+  }
+
+}