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

This commit is contained in:
大森林 2022-03-22 06:32:15 +00:00 committed by binarywang
parent 4036921f21
commit 94e6d6518b
18 changed files with 1430 additions and 0 deletions

View File

@ -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;
}

View File

@ -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+TIMEZONEyyyy-MM-DD表示年月日
* T出现在字符串中表示time元素的开头HH:mm:ss.sss表示时分秒毫秒TIMEZONE表示时区+08:00表示东八区时间领先UTC 8小时即北京时间
* 例如2015-05-20T13:29:35.120+08:00表示北京时间2015年05月20日13点29分35秒
* 示例值2015-05-20T13:29:35.120+08:00
* </pre>
*/
@SerializedName("complaint_time")
private String complaintTime;
/**
* <pre>
* 字段名投诉详情
* 是否必填
* 投诉的具体描述
* </pre>
*/
@SerializedName("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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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+TIMEZONEyyyy-MM-DD表示年月日
* T出现在字符串中表示time元素的开头HH:mm:ss.sss表示时分秒毫秒TIMEZONE表示时区+08:00表示东八区时间领先UTC 8小时即北京时间
* 例如2015-05-20T13:29:35.120+08:00表示北京时间2015年05月20日13点29分35秒
* 示例值2015-05-20T13:29:35.120+08:00
* </pre>
*/
@SerializedName("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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -173,6 +173,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();
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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) {

View File

@ -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)

View File

@ -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);
}
}