-- 新版提交

This commit is contained in:
yc13649764453
2023-09-09 13:51:35 +08:00
parent 763e24b4e0
commit 0b193caa03
92 changed files with 3451 additions and 1120 deletions

View File

@@ -1,5 +1,7 @@
package com.peanut.modules.pay.weChatPay.config;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.Resource;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
@@ -7,9 +9,11 @@ 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 io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.After;
import org.junit.Before;
@@ -18,11 +22,18 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.Enumeration;
/**
@@ -94,15 +105,47 @@ public class WechatPayConfig implements Serializable {
private String domain;
//获取私钥工具
public PrivateKey getPrivateKey(String keyPemPath) {
try {
return PemUtil.loadPrivateKey(new FileInputStream(keyPemPath));
/*
解决方案
1. 将私钥文件放在应用的外部,然后通过文件路径来读取。例如:
privateKeyPath = "/etc/myapp/apiclient_key.pem";
File file = new File(privateKeyPath);
return PemUtil.loadPrivateKey(new FileInputStream(file));
这样你就可以将 privateKeyPath 设置为任意位置的文件路径。
2.如果你确实需要将私钥文件打包到应用中,你可以使用 ResourceUtils.getURL(filename).openStream() 来获取文件内容,例如:
privateKeyPath = "classpath:cert/apiclient_key.pem";
return PemUtil.loadPrivateKey(ResourceUtils.getURL(privateKeyPath).openStream());
这样你就可以从应用的 classpath 中读取文件内容。
ResourceUtils.getURL(filename).openStream() 来获取资源这是与平台无关的所以在Windows、Linux以及其他任何支持Java的平台上都能正常使用。只要你的资源文件在这个例子中是私钥文件被正确的包含在了你的应用的classpath中那么你就可以在任何平台上使用这个方法来读取资源文件的内容。
这里需要注意的是classpath: 是一个特殊的协议前缀它表示资源是从classpath中获取的。当你的资源文件被打包进jar或war时它们就位于应用的classpath中因此你可以使用 classpath: 前缀来获取这些文件。
因此无论你的应用运行在Windows还是Linux或者是其他任何操作系统上使用方法2都不会有问题。只要你的资源文件被正确地打包进了应用那么就可以使用这种方法来读取文件内容。
*/
// 修改后的方法
return PemUtil.loadPrivateKey(ResourceUtils.getURL(keyPemPath).openStream());
} catch (FileNotFoundException e) {
//抛出异常,并把错误文件继续向上抛出
throw new RuntimeException("私钥文件不存在",e);
throw new RuntimeException("私钥文件不存在", e);
} catch (IOException e) {
throw new RuntimeException("私钥文件流失败", e);
}
}
@@ -119,7 +162,8 @@ public class WechatPayConfig implements Serializable {
log.info("获取证书管理器实例");
//获取商户私钥
// PrivateKey privateKey = getPrivateKey(keyPemPath);
PrivateKey privateKey = getPrivateKey(keyPemPath);
//私钥签名对象
@@ -146,12 +190,13 @@ public class WechatPayConfig implements Serializable {
* @return
*/
@Bean(name = "wxPayClient")
// @Bean
public CloseableHttpClient getWxPayClient(Verifier verifier) {
log.info("获取HttpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
@@ -168,12 +213,11 @@ public class WechatPayConfig implements Serializable {
/**
* 获取HttpClient无需进行应答签名验证跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息

View File

@@ -88,11 +88,6 @@ public class WeChatPayController {
@RequestMapping(value = "/Vxtext", method = RequestMethod.POST)
public String vxPAYtext(@RequestBody Integer user) {
return "=============================test============"+user;
}
/**
@@ -106,39 +101,7 @@ public class WeChatPayController {
@RequestMapping(value = "/placeAnOrder/app")
@Transactional(rollbackFor = Exception.class)
public R pay(@RequestBody WechatDto dto ) throws Exception{
// Date afterDate = new Date(nowDate.getTime()+300000*3);
//
// Date nowDate = new Date();
// Date afterDate = new Date(nowDate.getTime() + 60000);
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ssXXX");
// sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
//
// String dateString = sdf.format(afterDate); // 将修改后的时间转换为字符串
//
// ZoneId zoneId = ZoneId.of("Asia/Shanghai");
// Instant now = Instant.now();
// Instant after = now.plus(60, ChronoUnit.SECONDS);
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ssXXX");
//// .withZone(zoneId);
// sdf.setTimeZone(TimeZone.getTimeZone(zoneId));
//
// String afterString = sdf.format(after);
//
//
//
//
//
//
// System.out.println("修改后时间:" + afterString);
System.out.println("==========ordersn================"+dto.getOrderSn());
log.info("生成订单");
log.info("微信生成订单");
List<BuyOrderEntity> one = this.buyOrderService.getBaseMapper().selectList(new QueryWrapper<BuyOrderEntity>().eq("order_sn", dto.getOrderSn()));
BuyOrderEntity order = one.get(0);
// 获取订单
@@ -154,12 +117,27 @@ public class WeChatPayController {
// paramMap.put("time_expire",afterString);
// 实收金额0.38乘100=38
BigDecimal realsmoney= order.getRealMoney();
BigDecimal hand=new BigDecimal("100");
realsmoney =realsmoney.multiply(hand) ;
BigDecimal hand=new BigDecimal("100");
realsmoney =realsmoney.multiply(hand) ;
Map<String,Object> amountMap = new HashMap<>();
amountMap.put("total",realsmoney);
amountMap.put("currency","CNY");
BigDecimal money= order.getRealMoney();
BigDecimal han=new BigDecimal("100");
realsmoney =realsmoney.multiply(han) ;
Map<String,Object> amount = new HashMap<>();
amount.put("total",realsmoney);
amount.put("currency","CNY");
if (dto.getTotalAmount()!=order.getRealMoney()) {
}
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());
@@ -204,7 +182,7 @@ public class WeChatPayController {
wechat.setCreateTime(new Date()); //创建订单时间
wechat.setOrderSn(order.getOrderSn()); //订单编号
wechat.setPrepayId(prepayid); //预支付回话标识 标识为响应体EntityUtils.toString(response.getEntity())
wechat.setTotalAmount(order.getRealMoney()); //支付实收金额
wechat.setTotalAmount(dto.getTotalAmount()); //支付实收金额
wechat.setSystemLog(response.toString()); //日志
wechat.setPayType(order.getOrderType()); //交易类型
wechat.setOrderId(order.getOrderSn()); //订单号
@@ -258,13 +236,13 @@ public class WeChatPayController {
@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中的通知数据
@@ -272,19 +250,21 @@ public class WeChatPayController {
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));
PayWechatOrderEntity wechatEntity = new PayWechatOrderEntity();
if(!ObjectUtils.isEmpty(order)){
wechatEntity = this.payWechatOrderService.getOne(new QueryWrapper<PayWechatOrderEntity>().eq("order_id", order.getOrderId()));
wechatEntity = this.payWechatOrderService.getOne(new QueryWrapper<PayWechatOrderEntity>().eq("order_sn", order.getOrderSn()));
}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()));
BuyOrderEntity orderEntity = buyOrderService.getBaseMapper().selectOne(new QueryWrapper<BuyOrderEntity>().eq("order_sn", wechatEntity.getOrderSn()));
BigDecimal realMoney = orderEntity.getRealMoney();
//更新 订单 记录
@@ -373,4 +353,99 @@ public class WeChatPayController {
}
@RequestMapping(value = "/placeAnOrder/shoppingpay")
@Transactional(rollbackFor = Exception.class)
public R shoppingpay(@RequestBody WechatDto dto ) throws Exception{
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());
BigDecimal realsmoney= dto.getTotalAmount();
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
// wechat.setEndtime(after);
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);
}
}

View File

@@ -2,18 +2,28 @@ package com.peanut.modules.pay.weChatPay.dto;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class WechatDto implements Serializable {
//totalAmount: payItem.realMoney,
// customerId:
private String orderSn;
private Integer buyOrderId;
private BigDecimal totalAmount;
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public String getOrderSn() {
return orderSn;

View File

@@ -1,9 +1,15 @@
package com.peanut.modules.pay.weChatPay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.peanut.common.utils.R;
import com.peanut.modules.book.entity.PayWechatOrderEntity;
import com.peanut.modules.pay.weChatPay.dto.WechatDto;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
@Service
public interface WxpayService extends IService<PayWechatOrderEntity> {
}

View File

@@ -1,42 +0,0 @@
package com.peanut.modules.pay.weChatPay.util;
import com.peanut.modules.pay.weChatPay.dto.WxchatCallbackRefundData;
import java.lang.reflect.Type;
/**
* 退款处理接口,为了防止项目开发人员,不手动判断退款失败的情况
* 退款失败:退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款
*/
public interface WechatRefundCallback {
/*
* WxchatCallbackRefundData是微信支付退款回调数据的格式在退款请求完成后微信支付会将退款结果发送给商户的服务器
* 以此通知商户退款结果。refundData表示退款回调的数据包含退款结果的各种信息如退款金额、退款状态、退款时间等等。
* */
/**
* 退款成功处理情况
*/
void success(WxchatCallbackRefundData refundData);
/**
* 退款失败处理情况
*/
void find(WxchatCallbackRefundData refundData);
}

View File

@@ -32,17 +32,18 @@ import java.util.Base64;
public static final String apiV3Key = "4aYFklzaULeGlr7oJPZ6rHWKcxjihZUF"; // apiV3秘钥
//商户私钥路径
public static final String privateKeyUrl = "C:\\Users\\Administrator\\IdeaProjects\\peanut_book\\src\\main\\resources\\cent\\apiclient_key.pem";
public static final String privateKeyUrl = "/usr/local/hs/peanut_book/target/classes/cent/apiclient_key.pem";
// public static final String privateKeyUrl = "C:/Users/Administrator/IdeaProjects/peanut_book/src/main/resources/cent/apiclient_key.pem";
//平台证书路径
public static final String wechatPayCertificateUrl = "C:\\Users\\Administrator\\IdeaProjects\\peanut_book\\src\\main\\resources\\cent\\wechatpay_7B5676E3CDF56680D0414A009CE501C844DBE2D6.pem";
public static final String wechatPayCertificateUrl = "/usr/local/hs/peanut_book/target/classes/cent/wechatpay_7B5676E3CDF56680D0414A009CE501C844DBE2D6.pem";
// public static final String wechatPayCertificateUrl = "C:/Users/Administrator/IdeaProjects/peanut_book/src/main/resources/cent/wechatpay_7B5676E3CDF56680D0414A009CE501C844DBE2D6.pem";
//第一步申请完证书后在API证书哪里点击管理证书就能看到
public static final String mchSerialNo = "679AECB2F7AC4183033F713828892BA640E4EEE3"; // 商户证书序列号
private CloseableHttpClient httpClient;
public void setup() {
// PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
PrivateKey merchantPrivateKey = null;
X509Certificate wechatPayCertificate = null;
@@ -137,7 +138,7 @@ import java.util.Base64;
e.printStackTrace();
}
Signature sign = Signature.getInstance("SHA256withRSA");
//这里需要一个PrivateKey类型的参数就是商户的私钥。
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());