🆕 #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,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)