微信退款、退款回调
This commit is contained in:
parent
569c038072
commit
636f20fb29
@ -57,9 +57,13 @@ public class WechatConfig {
|
||||
private String mchSerialNo;
|
||||
|
||||
/**
|
||||
* 回调接口地址
|
||||
* 支付回调接口地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
private String payNotifyUrl;
|
||||
/**
|
||||
* 退款回调接口地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
|
||||
/**
|
||||
* 商户API私钥路径
|
||||
|
18
src/main/java/com/ycwl/basic/constant/HttpConstant.java
Normal file
18
src/main/java/com/ycwl/basic/constant/HttpConstant.java
Normal 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";
|
||||
}
|
@ -54,6 +54,7 @@ public interface NumberConstant {
|
||||
int SIXTY = 60;
|
||||
|
||||
int HUNDRED = 100;
|
||||
int ONE_HUNDRED_TWENTY_EIGHT = 128;
|
||||
|
||||
int THOUSAND = 1000;
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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, "退款回调失败"),
|
||||
;
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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参数中,获取到ciphertext,nonce,associated_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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:longbinbin
|
||||
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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密钥
|
||||
|
Loading…
x
Reference in New Issue
Block a user