--微信支付

This commit is contained in:
yc13649764453
2023-05-24 18:13:36 +08:00
parent 4e7aec5b60
commit 068192327c
25 changed files with 3241 additions and 657 deletions

View File

@@ -0,0 +1,194 @@
package com.peanut.modules.pay.weChatPay.config;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.After;
import org.junit.Before;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
/**
* @Author:
* @Description:
**/
@Configuration
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@PropertySource(value = "classpath:weChatConfig.properties" ) //读取配置文件
//@Component
public class WechatPayConfig implements Serializable {
/**
* APPID
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* 服务商商户号
*/
private String slMchId;
/**
* APIv2密钥
*/
private String apiKey;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知回调地址
*/
private String notifyUrl;
/**
* 退款回调地址
*/
private String refundNotifyUrl;
/**
* API 证书中的 key.pem 商户私钥
*/
private String keyPemPath;
/**
* 商户序列号
*/
private String serialNo;
/**
* 微信支付V3-url前缀
*/
private String baseUrl;
private String domain;
//获取私钥工具
public PrivateKey getPrivateKey(String keyPemPath) {
try {
return PemUtil.loadPrivateKey(new FileInputStream(keyPemPath));
} catch (FileNotFoundException e) {
//抛出异常,并把错误文件继续向上抛出
throw new RuntimeException("私钥文件不存在",e);
}
}
/**
* 获取证书管理器实例 签名验证器
*
* @return
*/
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
//获取商户私钥
// PrivateKey privateKey = getPrivateKey(keyPemPath);
PrivateKey privateKey = getPrivateKey(keyPemPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(
mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(mchId);
}
/**
* 获取支付http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
// @Bean
public CloseableHttpClient getWxPayClient(Verifier verifier) {
log.info("获取HttpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
CloseableHttpClient httpClient =builder.build();
// 通过WechatPayHttpClientBuilder构造的HttpClient会自动的处理签名和验签并进行证书自动更新
return httpClient;
}
/**
* 获取HttpClient无需进行应答签名验证跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, serialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient会自动的处理签名和验签并进行证书自动更新
return builder.build();
}
}

View File

@@ -0,0 +1,335 @@
package com.peanut.modules.pay.weChatPay.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.peanut.common.utils.R;
import com.peanut.modules.book.entity.*;
import com.peanut.modules.book.service.*;
import com.peanut.modules.pay.weChatPay.dto.WechatDto;
import com.peanut.modules.pay.weChatPay.config.WechatPayConfig;
import com.peanut.modules.pay.weChatPay.util.HttpUtils;
import com.peanut.modules.book.entity.BookBuyConfigEntity;
import com.peanut.modules.pay.weChatPay.util.WechatPayValidator;
import com.peanut.modules.pay.weChatPay.util.WxPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@RestController
@CrossOrigin //跨域
@RequestMapping("/pay")
@Configuration
public class WeChatPayController {
@Autowired private MyUserService userService;
@Autowired private BuyOrderService buyOrderService;
@Autowired
@Lazy
private WechatPayConfig wechatPayConfig;
@Autowired private BookBuyConfigService bookBuyConfigService;
@Autowired private PayWechatOrderService payWechatOrderService;
@Autowired private PayPaymentOrderService payPaymentOrderService;
@Autowired private TransactionDetailsService transactionDetailsService;
@Autowired
private CloseableHttpClient wxPayNoSignClient; //无需应答签名
@Autowired
private CloseableHttpClient wxPayClient;
@Autowired
private WxPayUtil wxPayUtil;
private final ReentrantLock lock = new ReentrantLock();
// 由微信生成的应用ID全局唯一。
public static final String appId= "wx47134a8f15083734";
// 直连商户的商户号,由微信支付生成并下发。
public static final String mchId = "1612860909";
// 商户证书序列号 7B5676E3CDF56680D0414A009CE501C844DBE2D6 679AECB2F7AC4183033F713828892BA640E4EEE3
public static final String mchSerialNo = "679AECB2F7AC4183033F713828892BA640E4EEE3";
// 微信下单Url
public static final String payUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
@RequestMapping(value = "/Vxtext", method = RequestMethod.POST)
public String vxPAYtext(@RequestBody Integer user) {
return "=============================test============"+user;
}
/**
* App 微信下单
*
* @param
* @return
* @PathVariable
*/
@RequestMapping(value = "/placeAnOrder/app")
@Transactional(rollbackFor = Exception.class)
public R pay(@RequestBody WechatDto dto ) throws Exception{
System.out.println("==========ordersn================"+dto.getOrderSn());
log.info("生成订单");
List<BuyOrderEntity> one = this.buyOrderService.getBaseMapper().selectList(new QueryWrapper<BuyOrderEntity>().eq("order_sn", dto.getOrderSn()));
BuyOrderEntity order = one.get(0);
// 获取订单
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("appid",appId);
paramMap.put("mchid",mchId);
paramMap.put("description","微信充值");
// 订单编号
paramMap.put("out_trade_no",order.getOrderSn());
// paramMap.put("attach",""); //自定义数据 支付完成后才能显示 在查询API和支付通知中原样返回可作为自定义参数使用
paramMap.put("notify_url",wechatPayConfig.getNotifyUrl());
// paramMap.put("time_expire","");
// 实收金额0.38乘100=38
BigDecimal realsmoney= order.getRealMoney();
BigDecimal hand=new BigDecimal("100");
realsmoney =realsmoney.multiply(hand) ;
Map<String,Object> amountMap = new HashMap<>();
amountMap.put("total",realsmoney);
amountMap.put("currency","CNY");
paramMap.put("amount",amountMap);
JSONObject json = JSONObject.parseObject(JSON.toJSONString(paramMap));
log.info("请求参数"+paramMap);
com.alibaba.fastjson.JSONObject jsonObject1 = wxPayUtil.doPostWexinV3("https://api.mch.weixin.qq.com/v3/pay/transactions/app", json.toJSONString());
String prepayid = jsonObject1.getString("prepay_id");
// 传入参数 payUrl 发送post请求
HttpPost httpPost = new HttpPost(payUrl);
// 将json数据转换成字符串
StringEntity entity = new StringEntity(json.toString(),"utf-8");
// 设置该请求的Content-Type为application/json 都是json格式
entity.setContentType("application/json");
// 将实体对象设置到HttpPost表示要传递该数据到服务器端。
httpPost.setEntity(entity);
// 设置请求头部的Accept属性为"application/json"表示客户端希望接收的为json。
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
// 向微信支付平台发送请求,处理响应结果,并将订单信息保存到数据库中。
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
// 时间戳
Long timestamp = System.currentTimeMillis()/1000;
// 随机串
String nonceStr = UUID.randomUUID().toString().replace("-","");
String sign = wxPayUtil.getSign(WxPayUtil.appId,timestamp,nonceStr,prepayid);
log.info("签名:"+sign);
Map Map = new HashMap();
Map.put("prepayid",prepayid);
Map.put("timestamp",timestamp+"");
Map.put("noncestr",nonceStr);
Map.put("sign",sign);
Map.put("appid",appId);
Map.put("package","Sign=WXPay");
Map.put("extData","sign");
Map.put("partnerid",mchId);
try {
int statusCode = response.getStatusLine().getStatusCode(); //响应状态码
if (statusCode == 200) { //处理成功
System.out.println("成功,返回结果 = " + bodyAsString); //返回响应体 EntityUtils.toString(response.getEntity())
// 添加微信支付订单信息
PayWechatOrderEntity wechat = new PayWechatOrderEntity();
wechat.setCustomerId(order.getUserId()); //用户id
wechat.setCreateTime(new Date()); //创建订单时间
wechat.setOrderSn(order.getOrderSn()); //订单编号
wechat.setPrepayId(prepayid); //预支付回话标识 标识为响应体EntityUtils.toString(response.getEntity())
wechat.setTotalAmount(order.getRealMoney()); //支付实收金额
wechat.setSystemLog(response.toString()); //日志
wechat.setPayType(order.getOrderType()); //交易类型
wechat.setOrderId(order.getOrderSn()); //订单号
wechat.setBuyOrderId(dto.getBuyOrderId()); //购买配置id
this.payWechatOrderService.save(wechat); //微信订单表拿到数据保存数据库
} else if (statusCode == 204) { //处理成功无返回Body
System.out.println("成功");
} else {
System.out.println("下单失败 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
}finally {
response.close();
}
// 返回url和订单号
return R.ok().put("paramMap" ,paramMap).put("Map",Map);
}
/**
* 获取私钥。
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
/**
*
* 微信支付回调
*
* @param request
* @param response
* @return
*/
@PostMapping("/payNotify")
public R payNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("##############################微信支付回调#######################");
PayWechatOrderEntity wechatEntity = new PayWechatOrderEntity();
// 处理通知参数
Map<String,Object> bodyMap = getNotifyBody(request);
if(bodyMap==null){
return null;
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if(lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(),1);
String orderNo = resourceMap.get("out_trade_no").toString();
String transactionId = resourceMap.get("transaction_id").toString();
System.out.println("===================回调解密transactionId================================"+transactionId);
// 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
BuyOrderEntity order = this.buyOrderService.getOne(new QueryWrapper<BuyOrderEntity>().eq("order_sn", orderNo));
if(!ObjectUtils.isEmpty(order)){
wechatEntity = this.payWechatOrderService.getOne(new QueryWrapper<PayWechatOrderEntity>().eq("order_id", order.getOrderId()));
}else{
log.error("无效订单!");
return R.error(500,"无效订单!");
}
// 1.根据订单id获取订单信息
log.warn("=========== 根据订单号,做幂等处理 ===========");
if("order".equals(order.getOrderType())){
BuyOrderEntity orderEntity = buyOrderService.getBaseMapper().selectOne(new QueryWrapper<BuyOrderEntity>().eq("order_sn", wechatEntity.getOrderId()));
BigDecimal realMoney = orderEntity.getRealMoney();
//更新 订单 记录
if (wechatEntity.getTotalAmount().compareTo(realMoney) == 0) {
buyOrderService.updateOrderStatus(Integer.valueOf(order.getUserId()),order.getOrderSn(),"0");
}
}
if("point".equals(order.getOrderType())){
// List<BookBuyConfigEntity> bookBuyConfigEntity = bookBuyConfigService.getBaseMapper().selectList(new QueryWrapper<BookBuyConfigEntity>().eq("price_type_id", ""));
// 插入花生币 变动记录
PayWechatOrderEntity buy_order_id = payWechatOrderService.getBaseMapper().selectOne(new QueryWrapper<PayWechatOrderEntity>().eq("order_sn", order.getOrderSn()));
Integer buyorder= buy_order_id.getBuyOrderId();
BookBuyConfigEntity bookBuyConfigEntity = bookBuyConfigService.getById(buyorder);
String realMoney =bookBuyConfigEntity.getRealMoney();
Integer money = Integer.valueOf(realMoney);
userService.rechargeHSPoint(Integer.valueOf(order.getUserId()),money);
TransactionDetailsEntity transactionDetailsEntity = new TransactionDetailsEntity();
transactionDetailsEntity.setUserId(order.getUserId());
transactionDetailsEntity.setChangeAmount(new BigDecimal(money));
transactionDetailsEntity.setOrderType("充值");
// transactionDetailsEntity.setRelationId(wechatEntity.getId().intValue());
transactionDetailsEntity.setRelationId(buy_order_id.getId().intValue());
transactionDetailsEntity.setRemark("充值");
MyUserEntity user = userService.getById(Integer.valueOf(order.getUserId()));
BigDecimal peanutCoin = user.getPeanutCoin();
transactionDetailsEntity.setUserBalance(peanutCoin);
transactionDetailsEntity.setUserName(user.getNickname());
transactionDetailsEntity.setTel(user.getTel());
transactionDetailsService.save(transactionDetailsEntity);
// 插入 花生币 充值记录
PayPaymentOrderEntity payPaymentOrderEntity = new PayPaymentOrderEntity();
payPaymentOrderEntity.setUserId(Integer.valueOf(order.getUserId()));
payPaymentOrderEntity.setOrderId(String.valueOf(buy_order_id.getId()));
payPaymentOrderEntity.setRealAmount(new BigDecimal(bookBuyConfigEntity.getRealMoney()));
payPaymentOrderEntity.setRechargeAmount(new BigDecimal(bookBuyConfigEntity.getMoney()));
payPaymentOrderEntity.setRechargeChannel(bookBuyConfigEntity.getQudao());
payPaymentOrderEntity.setRechargeStatus("success");
payPaymentOrderEntity.setSuccessTime(new Date());
payPaymentOrderEntity.setUserName(user.getNickname());
payPaymentOrderEntity.setTel(user.getTel());
payPaymentOrderService.save(payPaymentOrderEntity);
buyOrderService.updateOrderStatus(Integer.valueOf(order.getUserId()),order.getOrderSn(),"2");
}
} finally {
// 要主动释放锁
lock.unlock();
}
}
// 成功应答
return R.ok();
}
private Map<String,Object> getNotifyBody(HttpServletRequest request ){
// 处理通知参数
String body = HttpUtils.readData(request);
log.info("zhifu回调参数{}",body);
// 转换为Map
Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>(){});
// 微信的通知ID通知的唯一ID
String notifyId = bodyMap.get("id").toString();
// 验证签名信息
try {
WechatPayValidator wechatPayValidator
= new WechatPayValidator(wechatPayConfig.getVerifier(), notifyId, body);
if(!wechatPayValidator.validate(request)){
log.error("通知验签失败");
return null;
}
log.info("通知验签成功");
}catch (Exception e) {
}
return bodyMap;
}
}

View File

@@ -0,0 +1,77 @@
package com.peanut.modules.pay.weChatPay.dto;
import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author
* @version 1.0
* @description 微信支付成功回调返回的数据
* @date
*/
@Data
@Slf4j
public class WxchatCallbackSuccessData {
/**
* 商户订单号
*/
private String orderId;
/**
* 微信支付系统生成的订单号
*/
private String transactionId;
/**
* 交易状态
* SUCCESS支付成功
* REFUND转入退款
* NOTPAY未支付
* CLOSED已关闭
* REVOKED已撤销付款码支付
* USERPAYING用户支付中付款码支付
* PAYERROR支付失败(其他原因,如银行返回失败)
*/
private String tradestate;
/**
* 支付完成时间
*/
private Date successTime;
/**
* 交易类型
* JSAPI公众号支付
* NATIVE扫码支付
* APPAPP支付
* MICROPAY付款码支付
* MWEBH5支付
* FACEPAY刷脸支付
*/
private String tradetype;
/**
* 订单总金额
*/
private BigDecimal totalMoney;
public Date getSuccessTime() {
return successTime;
}
public void setSuccessTime(String successTime) {
// Hutool工具包的方法自动识别一些常用格式的日期字符串
this.successTime = DateUtil.parse(successTime);
}
}

View File

@@ -0,0 +1,45 @@
package com.peanut.modules.pay.weChatPay.util;
import org.junit.rules.Verifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
public class HttpUtils {
/**
* 将通知参数转化为字符串
* @param request
* @return
*
* WechatPayValidatorForRequest
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}}}}

View File

@@ -0,0 +1,150 @@
package com.peanut.modules.pay.weChatPay.util;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
/**
* @Author:
* @Description:
**/
@Slf4j
public class WechatPayValidator {
/**
* 应答超时时间,单位为分钟
*/
private static final long RESPONSE_EXPIRED_MINUTES = 5;
private final Verifier verifier;
private final String requestId;
private final String body;
public WechatPayValidator(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
private void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
private String buildMessage(HttpServletRequest request) {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
/**
* 对称解密,异步通知的加密数据
* @param resource 加密数据
* @param apiV3Key apiV3密钥
* @param type 1-支付2-退款
* @return
*/
public static Map<String, Object> decryptFromResource(String resource,String apiV3Key,Integer type) {
String msg = type==1?"支付成功":"退款成功";
log.info(msg+",回调通知,密文解密");
try {
//通知数据
Map<String, Object> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, Object>>() {
});
//数据密文
String ciphertext = resourceMap.get("ciphertext").toString();
//随机串
String nonce = resourceMap.get("nonce").toString();
//附加数据
String associatedData = resourceMap.get("associated_data").toString();
log.info("密文: {}", ciphertext);
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info(msg+",回调通知,解密结果 明文: {}", resourceStr);
return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>(){});
}catch (Exception e){
throw new RuntimeException("回调参数,解密失败!");
}
}
}