微信退款、退款回调

This commit is contained in:
songmingsong 2024-12-06 10:13:43 +08:00
parent 569c038072
commit 636f20fb29
11 changed files with 350 additions and 28 deletions

View File

@ -57,9 +57,13 @@ public class WechatConfig {
private String mchSerialNo;
/**
* 回调接口地址
* 支付回调接口地址
*/
private String notifyUrl;
private String payNotifyUrl;
/**
* 退款回调接口地址
*/
private String refundNotifyUrl;
/**
* 商户API私钥路径

View File

@ -0,0 +1,18 @@
package com.ycwl.basic.constant;
/**
* <p>HTTP常量类</p>
*
* @author songmingsong
* @Description HTTP常量类
*/
public interface HttpConstant {
String Authorization = "Authorization";
String Accept = "Accept";
String Content_Type = "Content-Type";
String Application_Json = "application/json";
String SUCCESS = "SUCCESS";
String Message = "message";
String AES = "AES";
String AES_GCM_NoPadding = "AES/GCM/NoPadding";
}

View File

@ -54,6 +54,7 @@ public interface NumberConstant {
int SIXTY = 60;
int HUNDRED = 100;
int ONE_HUNDRED_TWENTY_EIGHT = 128;
int THOUSAND = 1000;

View File

@ -78,5 +78,42 @@ public class WeiXinConstant {
*/
public static final String GENERATE_URL_LINK = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s";
/*-----------------------------*/
/* */
/* 支付相关 */
/* */
/*-----------------------------*/
/**
* 退款链接
*/
public static final String REFUNDS_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
public static final String REFUNDS_URi = "/v3/refund/domestic/refunds/";
/**
* 其他
*/
public static final String WECHATPAY_STATUS = "status";
public static final String WECHATPAY_SUCCESS = "SUCCESS";
public static final String WECHATPAY_PROCESSING = "PROCESSING";
public static final String WECHATPAY_OUT_TRADE_NO = "out_trade_no";
public static final String WECHATPAY_OUT_REFUND_NO = "out_refund_no";
public static final String WECHATPAY_REFUND = "refund";
public static final String WECHATPAY_TOTAL = "total";
public static final String WECHATPAY_CURRENCY = "currency";
public static final String WECHATPAY_CURRENCY_CNY = "CNY";
public static final String WECHATPAY_AMOUNT = "amount";
public static final String WECHATPAY_NOTIFY_URL = "notify_url";
public static final String REFUNDS_RESOURCE = "resource";
public static final String REFUNDS_CIPHERTEXT = "ciphertext";
public static final String REFUNDS_NONCE = "nonce";
public static final String REFUNDS_REFUND_STATUS = "refund_status";
public static final String REFUNDS_ASSOCIATED_DATA = "associated_data";
/**
* 退款的token的SCHEMA
*/
public static final String REFUNDS_SCHEMA = "Wechatpay-Signature-Type "; // 注意有一个空格
/**
* 支付请求头
*/
public static final String WECHATPAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type";
}

View File

@ -10,13 +10,14 @@ import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
/**
* @Author: songmingsong
@ -34,7 +35,8 @@ public class AppWxPayController {
@ApiOperation(value = "微信预支付", notes = "微信预支付")
@PostMapping("/createOrder")
public ApiResponse<WxPayRespVO> createOrder(@RequestBody @Validated WXPayOrderReqVO req) throws Exception {
@IgnoreToken
public ApiResponse<WxPayRespVO> createOrder(@RequestBody WXPayOrderReqVO req) throws Exception {
return ApiResponse.success(wxPayService.createOrder(req));
}
@ -45,4 +47,24 @@ public class AppWxPayController {
wxPayService.payNotify(request);
return ApiResponse.success(BizCodeEnum.REQUEST_OK);
}
@ApiOperation(value = "微信退款", notes = "微信退款")
@PostMapping("/refundOrder")
@IgnoreToken
public ApiResponse<?> refundOrder(@RequestBody String orderId) throws Exception {
return ApiResponse.buildResult(wxPayService.refundOrder(orderId) ?
BizCodeEnum.SUCCESS :
BizCodeEnum.ADVANCE_PAYMENT_REFUND_FAILED);
}
@ApiOperation(value = "微信支付回调", notes = "微信支付回调")
@PostMapping("/refundNotify")
@IgnoreToken
public ApiResponse<?> refundNotify(@RequestBody String refundResult) throws GeneralSecurityException, IOException {
return ApiResponse.buildResult(wxPayService.refundNotify(refundResult) ?
BizCodeEnum.SUCCESS :
BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_REFUND_FAILED);
}
}

View File

@ -423,6 +423,8 @@ public enum BizCodeEnum {
/* -------------------------------------*/
ADVANCE_PAYMENT_FAILED(2001, "预支付失败"),
ADVANCE_PAYMENT_CALLBACK_FAILED(2002, "预支付回调失败"),
ADVANCE_PAYMENT_REFUND_FAILED(2003, "退款失败"),
ADVANCE_PAYMENT_CALLBACK_REFUND_FAILED(2004, "退款回调失败"),
;

View File

@ -11,6 +11,7 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
@ -99,6 +100,7 @@ public class HttpService {
HttpGet get = new HttpGet(requestUrl);
// 请求首部--可选的User-Agent对于一些服务器必选不加可能不会返回正确结果
get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64;rv:39.0) Gecko/20100101 Firefox/39.0");
get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64;rv:39.0) Gecko/20100101 Firefox/39.0");
// Exec Request
CloseableHttpResponse resp = httpClient.execute(get);
// 获得起始行
@ -118,4 +120,49 @@ public class HttpService {
}
return result;
}
public String doPost(String requestUrl, String jsonData, Map<String, String> headers, String encoding) throws Exception {
String result = HttpServiceUtil.REQUEST_NO_RESULT; // 默认返回值
CloseableHttpClient httpClient = null;
try {
// 使用自定义 SSL 工具类创建支持 HTTPS HttpClient
httpClient = SslUtil.sslHttpClientBuild();
// 清理 URL 中的空格字符
requestUrl = requestUrl.replaceAll("\\s*", "");
HttpPost post = new HttpPost(requestUrl);
// 设置请求头
if (headers != null && !headers.isEmpty()) {
for (Map.Entry<String, String> header : headers.entrySet()) {
post.setHeader(header.getKey(), header.getValue());
}
}
// 设置请求体
if (jsonData != null) {
StringEntity entity = new StringEntity(jsonData, encoding);
entity.setContentType("application/json");
post.setEntity(entity);
}
// 执行请求
CloseableHttpResponse response = httpClient.execute(post);
// 获取响应状态码及响应数据
StatusLine status = response.getStatusLine();
if (HttpServiceUtil.success(status)) {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, encoding);
EntityUtils.consume(entity); // 确保释放资源
}
response.close(); // 关闭响应对象
} finally {
if (httpClient != null) {
httpClient.close(); // 关闭 HttpClient
}
}
return result;
}
}

View File

@ -2,43 +2,58 @@ package com.ycwl.basic.service.impl.mobile;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.utils.StringUtils;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.http.HttpMethod;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.HttpConstant;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.pc.OrderMapper;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
import com.ycwl.basic.model.wxPay.WXPayOrderReqVO;
import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData;
import com.ycwl.basic.service.HttpService;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.Objects;
import java.util.UUID;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.*;
import static com.ycwl.basic.constant.WeiXinConstant.*;
/**
* @Author: songmingsong
@ -53,7 +68,7 @@ public class WxPayServiceImpl implements WxPayService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private OrderMapper orderMapper;
private HttpService httpService;
@Autowired
private OrderService orderService;
@ -80,7 +95,7 @@ public class WxPayServiceImpl implements WxPayService {
request.setAppid(wechatConfig.getAppId());
request.setMchid(wechatConfig.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wechatConfig.getNotifyUrl());
request.setNotifyUrl(wechatConfig.getPayNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
Payer payer = new Payer();
@ -90,7 +105,7 @@ public class WxPayServiceImpl implements WxPayService {
// 调用下单方法得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / 1000;
Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
@ -102,7 +117,7 @@ public class WxPayServiceImpl implements WxPayService {
return vo;
} catch (ServiceException e) {
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString("message"));
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString(HttpConstant.Message));
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
}
@ -183,7 +198,6 @@ public class WxPayServiceImpl implements WxPayService {
com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
String successTime = transaction.getSuccessTime();
wxchatCallbackSuccessData.setOrderId(orderId.toString());
wxchatCallbackSuccessData.setSuccessTime(successTime);
wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
@ -193,9 +207,165 @@ public class WxPayServiceImpl implements WxPayService {
wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("reponse body={}", e.getResponseBody());
log.error("[微信退款] code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("[微信退款] Response_body={}", e.getResponseBody());
}
return wxchatCallbackSuccessData;
}
@Override
public Boolean refundOrder(String orderId) throws Exception {
OrderRespVO orderDetail = orderService.detail(Long.parseLong(orderId)).getData();
BigDecimal payPrice = orderDetail.getPayPrice();
int priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).intValue(); // 转换为分int
// 构建退款请求参数
Map<String, Object> data = new HashMap<>();
data.put(WECHATPAY_OUT_TRADE_NO, orderId);// 你要退款的订单号
data.put(WECHATPAY_OUT_REFUND_NO, SnowFlakeUtil.getId()); // 退款订单号随机生成一个就行保证不重复
Map<String, Object> amount = new HashMap<>();
amount.put(WECHATPAY_REFUND, priceInCents); // 退款金额以分为单位
amount.put(WECHATPAY_TOTAL, priceInCents); // 订单总金额以分为单位
amount.put(WECHATPAY_CURRENCY, WECHATPAY_CURRENCY_CNY);
data.put(WECHATPAY_AMOUNT, amount);
// 回调通知 URL
data.put(WECHATPAY_NOTIFY_URL, wechatConfig.getRefundNotifyUrl());
Map<String, Object> stringObjectMap = this.processRefund(data);
if (stringObjectMap.get(WECHATPAY_STATUS).equals(WECHATPAY_SUCCESS)
|| stringObjectMap.get(WECHATPAY_STATUS).equals(WECHATPAY_PROCESSING)) {
orderService.updateOrderState(Long.parseLong(orderId), OrderStateEnum.REFUNDED, null);
return true;
} else {
return false;
}
}
@Override
public boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException {
// 转为map格式
Map<String, String> jsonMap = JSONObject.parseObject(refundResult, Map.class);
/*
* 退款成功后返回一个加密字段resource,以下为解密
* 解密需要从resource参数中获取到ciphertextnonceassociated_data这三个参数进行解密
*/
String resource = JSONObject.toJSONString(jsonMap.get(REFUNDS_RESOURCE));
JSONObject object = JSONObject.parseObject(resource);
String ciphertext = String.valueOf(object.get(REFUNDS_CIPHERTEXT));
String nonce = String.valueOf(object.get(REFUNDS_NONCE));
String associated_data = String.valueOf(object.get(REFUNDS_ASSOCIATED_DATA));
String resultStr = decryptToString(associated_data.getBytes(String.valueOf(StandardCharsets.UTF_8)),
nonce.getBytes(String.valueOf(StandardCharsets.UTF_8)),
ciphertext);
Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);
String refund_status = reqInfo.get(REFUNDS_REFUND_STATUS);// 退款状态
String out_trade_no = reqInfo.get(WECHATPAY_OUT_TRADE_NO); // 订单号
if (!StringUtils.isEmpty(refund_status) && WECHATPAY_SUCCESS.equals(refund_status)) {
orderService.updateOrderState(Long.parseLong(out_trade_no), OrderStateEnum.REFUNDED, null);
log.info("[微信退款回调]退款成功");
return true;
} else {
log.error("[微信退款回调]退款失败");
return false;
}
}
/**
* 退款回调 解密数据
*
* @param associatedData
* @param nonce
* @param ciphertext
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance(HttpConstant.AES_GCM_NoPadding);
SecretKeySpec key = new SecretKeySpec(wechatConfig.getKeyPath().getBytes(), HttpConstant.AES);
GCMParameterSpec spec = new GCMParameterSpec(NumberConstant.ONE_HUNDRED_TWENTY_EIGHT, nonce);// 规定为128
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), String.valueOf(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 发起退款
*
* @param data 退款请求参数
* @return
* @throws Exception
*/
public Map<String, Object> processRefund(Map<String, Object> data) throws Exception {
// 请求头需要根据业务生成 Authorization Token
Map<String, String> headers = new HashMap<>();
headers.put(HttpConstant.Authorization, getToken(String.valueOf(HttpMethod.POST), REFUNDS_URL, JSONObject.toJSONString(data)));
headers.put(HttpConstant.Accept, HttpConstant.Application_Json);
headers.put(HttpConstant.Content_Type, HttpConstant.Application_Json);
// 请求体数据
String jsonData = JSONObject.toJSONString(data);
// 执行 POST 请求
String response = httpService.doPost(REFUNDS_URL, jsonData, headers, String.valueOf(StandardCharsets.UTF_8));
// 将响应字符串解析为 Map
Map<String, Object> responseMap = JSONObject.parseObject(response, Map.class);
return responseMap;
}
/**
* 生成退款请求token
*
* @param method
* @param orderId
* @param body
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
* @throws SignatureException
*/
public String getToken(String method, String orderId, String body) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, SignatureException {
String nonceStr = WXPayUtil.generateNonceStr();
long timestamp = System.currentTimeMillis() / NumberConstant.THOUSAND; // 生成时间戳
String parameter = method + "\n"
+ REFUNDS_URi + orderId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
// 对参数进行加密
byte[] bytes = parameter.getBytes(String.valueOf(StandardCharsets.UTF_8));
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath(wechatConfig.getKeyPath()); // privateKeyPath是商户证书密钥的位置apiclient_key.pem
sign.initSign(privateKey); // 商户密钥文件路径
sign.update(bytes);
String signature = Base64.getEncoder().encodeToString(sign.sign());
// 获取token
String token = "mchid=\"" + wechatConfig.getMchId() + "\"," // 商户号
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + wechatConfig.getMchSerialNo() + "\"," // merchantSerialNumber是微信支付中申请的证书序列号
+ "signature=\"" + signature + "\"";
return REFUNDS_SCHEMA + token;
}
}

View File

@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* @Authorlongbinbin
@ -60,9 +61,9 @@ public class OrderServiceImpl implements OrderService {
Long orderId = SnowFlakeUtil.getLongId();
order.setId(orderId);
int add = orderMapper.add(order);
if (add == 0) {
if (add == NumberConstant.ZERO) {
return ApiResponse.fail("订单添加失败");
}else {
} else {
List<OrderItemDTO> orderItemList = order.getOrderItemList();
List<OrderItemEntity> orderItems = new ArrayList<>();
orderItemList.forEach(orderItemDTO -> {
@ -71,7 +72,7 @@ public class OrderServiceImpl implements OrderService {
orderItemEntity.setOrderId(orderId);
});
int addOrderItems = orderMapper.addOrderItems(orderItems);
if (addOrderItems == 0) {
if (addOrderItems == NumberConstant.ZERO) {
log.error("订单明细添加失败");
return ApiResponse.fail("订单添加失败");
}
@ -82,7 +83,7 @@ public class OrderServiceImpl implements OrderService {
@Override
public ApiResponse<Integer> update(OrderAddOrUpdateReq query) {
int update = orderMapper.update(query);
if (update == 0) {
if (update == NumberConstant.ZERO) {
return ApiResponse.fail("订单更新失败");
}
return ApiResponse.success(update);
@ -100,7 +101,9 @@ public class OrderServiceImpl implements OrderService {
if (orderStateEnum.getType() == NumberConstant.ONE) {
orderAddOrUpdateReq.setRefundStatus(orderStateEnum.getState());
orderAddOrUpdateReq.setRefundAt(new Date());
if (Objects.nonNull(refundReason)) {
orderAddOrUpdateReq.setRefundReason(refundReason);
}
} else if (orderStateEnum.getType() == NumberConstant.TWO) {
int state = orderStateEnum.getState();
orderAddOrUpdateReq.setPayAt(new Date());
@ -113,7 +116,7 @@ public class OrderServiceImpl implements OrderService {
}
@Override
public ApiResponse getOrderCountByUserId(Long userId) {
public ApiResponse<?> getOrderCountByUserId(Long userId) {
OrderReqQuery query = new OrderReqQuery();
query.setMemberId(userId);
return orderMapper.getOrderCount(query);
@ -125,8 +128,8 @@ public class OrderServiceImpl implements OrderService {
List<OrderAppRespVO> list = orderMapper.appList(orderReqQuery);
for (OrderAppRespVO appRespVO : list) {
List<OrderItemVO> orderItemList = appRespVO.getOrderItemList();
if(orderItemList!= null && orderItemList.size() > 0){
OrderItemVO itemVO = orderItemList.get(0);
if (orderItemList != null && !orderItemList.isEmpty()) {
OrderItemVO itemVO = orderItemList.get(NumberConstant.ZERO);
appRespVO.setScenicName(itemVO.getScenicName());
appRespVO.setGoodsName(itemVO.getGoodsName());
}
@ -137,10 +140,10 @@ public class OrderServiceImpl implements OrderService {
@Override
public ApiResponse<OrderAppRespVO> appDetail(Long id) {
OrderAppRespVO orderAppRespVO=orderMapper.appDetail(id);
OrderAppRespVO orderAppRespVO = orderMapper.appDetail(id);
List<OrderItemVO> orderItemList = orderAppRespVO.getOrderItemList();
if(orderItemList!= null && orderItemList.size() > 0){
OrderItemVO itemVO = orderItemList.get(0);
if (orderItemList != null && !orderItemList.isEmpty()) {
OrderItemVO itemVO = orderItemList.get(NumberConstant.ZERO);
orderAppRespVO.setScenicName(itemVO.getScenicName());
orderAppRespVO.setGoodsName(itemVO.getGoodsName());
}

View File

@ -5,6 +5,8 @@ import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
public interface WxPayService {
@ -22,4 +24,18 @@ public interface WxPayService {
* 微信支付结果查询
*/
WxchatCallbackSuccessData queryPay(Long orderId);
/**
* 订单退款
*
* @param orderId 订单id订单编号
* @return
* @throws Exception
*/
Boolean refundOrder(String orderId) throws Exception;
/**
* 微信退款回调
*/
boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException;
}

View File

@ -79,8 +79,10 @@ wx:
mchId: xxxx
# 商户证书序列号
mchSerialNo: xxxxx
# 回调接口地址
notifyUrl: https://xxxx/a/biz/wxpay/payNotify
# 支付回调接口地址
payNotifyUrl: https://xxxx/a/biz/wxpay/payNotify
# 退款回调接口地址
refundNotifyUrl: https://xxxx/a/biz/wxpay/payNotify
# 商户API私钥路径
keyPath: module-app/src/main/resources/cert/apiclient_key.pem
# 商户APIV3密钥