如果使用JDK8运行后端时遇到报错解决:java.security.InvalidKeyException: Illegal key size(微信支付v3遇到的问题)
原因是因为微信支付256位秘钥策略可能会导致某些jdk的版本加密解密出现问题,首先观察你这个目录下的文件
根据文件内容做判断看下目录里面是有一个 policy 文件夹,还是有local_policy.jar
去官方下载JCE无限制权限策略文件,这里贴出jdk 8的国内地址 方便下载https://wwi.lanzoup.com/iXGs404zm1dg
下载解压后,将以上两个文件拷贝覆盖到Java\jdk1.8.0_152\jre\lib\security目录中
SpringBoot整合微信支付(Native最详细):https://blog.csdn.net/yueyue763184/article/details/129624617?spm=1001.2014.3001.5501
微信支付是腾讯集团开发的具有支付功能的一种产品,基于智能手机微信客户端可以迅速、便捷实现在线支付。微信支付通过绑定银行卡完成实名制验证,从而为个人或企业提供安全性高、快捷性强、专业水平高的支付功能,不仅包括基础的收款能力,甚至可以提供运营能力以及资金结算解决方案,当然以上腾讯集团承诺均在保障安全性的前提下提供相关功能。用户首先需要通过实名认证,将一张用户名下的银行卡与微信支付进行绑定注册,只需要简单的几个步骤,一个安装微信软件的手机就会摇身变成一个“钱包”,用户在使用“钱包”时仅需要输入密码或指纹识别即可完成转移支付,支付过程便捷、高效。目前,微信适用于多个应用场景,可以使用微信支付来购物、旅游、就医、生活缴费等。
!!!注意:这里的用户端的"删除订单"和后台管理端的"删除订单"是有区别的,用户是没有彻底删除订单的权限的,只有管理员才有,所以要在数据库的订单表中添加一个字段来判断该订单是否被用户删除。
数据库表已经在sql文件夹中提供。
前端:Vue2+Element-ui+Axios
后端:jdk8+SpringBoot2.7.6+MD5加密+微信支付Native的apiV3+lombok+mybatis-plus+mysql
系统架构图:
pom.xml
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
登录和注册主要使用SecureUtil.md5()来加密
@Resource
private UserMapper userMapper;
/**
* 登录
* 先根据事务查询到用户名,再判断经MD5加密后的密码是否匹配
*/
@Override
public R login(User user) {
// 创建查询包装器来根据用户名查询用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", user.getName());
User user1 = userMapper.selectOne(queryWrapper);
System.out.println("根据name查询到:" + user1);
if(user1 == null){
log.info("用户不存在");
return R.error().setMessage("用户不存在");
}
// 获取查询到的用户的密码
String password1 = user1.getPassword();
// 加密前端传过来的password和上面的password1对比
System.out.println("加密前:" + user.getPassword());
String password = SecureUtil.md5(user.getPassword());
System.out.println("加密后:" + password);
user.setId(user1.getId());
if(password1.equalsIgnoreCase(password)){ //字符串不分大小写比较
log.info(user.getName()+"登录成功");
return R.ok().setMessage("登录成功").data("user",user);
}
log.info("密码错误");
return R.error().setMessage("密码错误").data("user",user);
}
/**
* 用户注册功能
* 将用户的密码进行MD5加密后存入数据库
* */
@Override
public R signIn(User user) {
// 创建查询包装器来根据用户名查询用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", user.getName());
User user1 = userMapper.selectOne(queryWrapper);
System.out.println("根据name查询到:" + user1);
if(user1 != null){
log.info("用户名已存在");
return R.error().setMessage("用户名已存在");
}
String password = user.getPassword();
System.out.println("加密前:" + password);
String md5 = SecureUtil.md5(password);
System.out.println("加密后:" + md5);
user.setPassword(md5);
userMapper.insert(user);
return R.ok().setMessage("用户注册成功").data("user", user);
}
wxpay.properties文件
# 微信支付相关参数
# 商户号
wxpay.mch-id=1639864766
# 商户API证书序列号
wxpay.mch-serial-no=4FFB54B40DEAEF54819ED5CFC0A6C6E12B4A1767
# 商户私钥文件相对路径
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=BuLiangYuWangLuoGongZuoShi123456
# APPID
wxpay.appid=wx1cd564c7469238d3
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置(有公网IP映射也行)
wxpay.notify-domain=https://5d59-112-96-225-60.ngrok-free.app
# APIv2密钥
wxpay.partnerKey: BuLiangYuWangLuoGongZuoShi123456
WxPayConfig读取加载以上信息和对微信支付进行相关配置
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data
@Slf4j
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
// APIv2密钥
private String partnerKey;
/**
* 获取商户的私钥文件
* @param filename
* @return
*/
public PrivateKey getPrivateKey(String filename){
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取签名验证器
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier(){
log.info("获取签名验证器");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
/**
* 获取http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, mchSerialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
}
}
import com.bls.productmall.config.WxPayConfig;
import com.bls.productmall.entity.Order;
import com.bls.productmall.enums.OrderStatus;
import com.bls.productmall.enums.wxpay.WxApiType;
import com.bls.productmall.enums.wxpay.WxNotifyType;
import com.bls.productmall.enums.wxpay.WxTradeState;
import com.bls.productmall.service.OrderService;
import com.bls.productmall.service.PaymentService;
import com.bls.productmall.service.WxPayService;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
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.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
@Resource
private WxPayConfig wxPayConfig;
@Resource
private CloseableHttpClient wxPayClient;
@Resource
private OrderService orderService;
@Resource
private PaymentService paymentService;
@Resource
private CloseableHttpClient wxPayNoSignClient; //无需应答签名
private final ReentrantLock lock = new ReentrantLock();
/**
* 创建订单,调用Native apiV3支付接口
* 根据产品id创建订单
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> nativePay(Long productId, Long userId) throws Exception {
log.info("生成订单");
//生成订单
Order order = orderService.createOrderByProductIdAndUserId(productId, userId);
String codeUrl = order.getCodeUrl();
if(order != null && !StringUtils.isEmpty(codeUrl)){
log.info("订单已存在,二维码已保存");
//返回二维码
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", order.getOrderNo());
return map;
}
log.info("调用统一下单API");
//调用统一下单API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数
Gson gson = new Gson();
Map paramsMap = new HashMap();
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("mchid", wxPayConfig.getMchId());
paramsMap.put("description", order.getTitle());
paramsMap.put("out_trade_no", order.getOrderNo());
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
Map amountMap = new HashMap();
amountMap.put("total", order.getTotalFee());
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);
StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功, 返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功");
} else {
log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
//响应结果
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码
codeUrl = resultMap.get("code_url");
//保存二维码
String orderNo = order.getOrderNo();
orderService.saveCodeUrl(orderNo, codeUrl);
//返回二维码
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", order.getOrderNo());
return map;
} finally {
response.close();
}
}
/**
* 处理订单
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
log.info("处理订单");
//解密报文
String plainText = decryptFromResource(bodyMap);
//将明文转换成map
Gson gson = new Gson();
HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
String orderNo = (String)plainTextMap.get("out_trade_no");
/* 在对业务数据进行状态检查和处理之前, 要采用数据锁进行并发控制, 以避免函数重入造成的数据混乱 */
// 尝试获取锁:成功获取则立即返回true,获取失败则立即返回false。
// 不必一直等待锁的释放
if(lock.tryLock()){
try {
//处理重复的通知
//接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
String orderStatus = orderService.getOrderStatus(orderNo);
if(!OrderStatus.NOTPAY.getType().equals(orderStatus)){
return;
}
//模拟通知并发
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
//记录支付日志
paymentService.createPayment(plainText);
} finally {
//要主动释放锁
lock.unlock();
}
}
}
/**
* 用户取消订单
*/
@Override
public void cancelOrder(String orderNo) throws Exception {
//调用微信支付的关单接口
this.closeOrder(orderNo);
//更新商户端的订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
}
/**
* 查询订单
* */
@Override
public String queryOrder(String orderNo) throws Exception {
log.info("查单接口调用 ===> {}", orderNo);
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功, 返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功");
} else {
log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
return bodyAsString;
} finally {
response.close();
}
}
/**
* 根据订单号查询微信支付查单接口,核实订单状态
* 如果订单已支付,则更新商户端订单状态,并记录支付日志
* 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void checkOrderStatus(String orderNo) throws Exception {
log.warn("根据订单号核实订单状态 ===> {}", orderNo);
//调用微信支付查单接口
String result = this.queryOrder(orderNo);
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
//获取微信支付端的订单状态
String tradeState = resultMap.get("trade_state");
//判断订单状态
if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
log.warn("核实订单已支付 ===> {}", orderNo);
//如果确认订单已支付则更新本地订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
//记录支付日志
paymentService.createPayment(result);
}
if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
log.warn("核实订单未支付 ===> {}", orderNo);
//如果订单未支付,则调用关单接口
this.closeOrder(orderNo);
//更新本地订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
}
}
/**
* 申请账单
*/
@Override
public String queryBill(String billDate, String type) throws Exception {
log.warn("申请账单接口调用 {}", billDate);
String url = "";
if("tradebill".equals(type)){
url = WxApiType.TRADE_BILLS.getType();
}else if("fundflowbill".equals(type)){
url = WxApiType.FUND_FLOW_BILLS.getType();
}else{
throw new RuntimeException("不支持的账单类型");
}
url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
CloseableHttpResponse response = wxPayClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 申请账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);
}
//获取账单下载地址
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap.get("download_url");
} finally {
response.close();
}
}
/**
* 关闭订单
*/
private void closeOrder(String orderNo) throws Exception {
log.info("关单接口的调用,订单号 ===> {}", orderNo);
//创建远程请求对象
String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url);
HttpPost httpPost = new HttpPost(url);
//组装json请求体
Gson gson = new Gson();
Map<String, String> paramsMap = new HashMap<>();
paramsMap.put("mchid", wxPayConfig.getMchId());
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}", jsonParams);
//将请求参数设置到请求对象中
StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功200");
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功204");
} else {
log.info("Native下单失败,响应码 = " + statusCode);
throw new IOException("request failed");
}
} finally {
response.close();
}
}
/**
* 对称解密
*/
private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
log.info("密文解密");
//通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
//数据密文
String ciphertext = resourceMap.get("ciphertext");
//随机串
String nonce = resourceMap.get("nonce");
//附加数据
String associatedData = resourceMap.get("associated_data");
log.info("密文 ===> {}", ciphertext);
AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info("明文 ===> {}", plainText);
return plainText;
}
}
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private WxPayService wxPayService;
@Resource
private Verifier verifier;
/**
* Native下单
*/
@PostMapping("/native/{productId}/{userId}")
public R nativePay(@PathVariable Long productId, @PathVariable Long userId) throws Exception {
log.info("发起支付请求 v3");
//返回支付二维码连接和订单号
Map<String, Object> map = wxPayService.nativePay(productId, userId);
return R.ok().setData(map);
}
/**
* 支付通知
* 微信支付通过支付通知接口将用户支付成功消息通知给商户
*/
@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response){
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();//应答对象
try {
//处理通知参数
String body = HttpUtils.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String)bodyMap.get("id");
log.info("支付通知的id ===> {}", requestId);
//签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if(!wechatPay2ValidatorForRequest.validate(request)){
log.error("通知验签失败");
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
log.info("通知验签成功");
//处理订单
wxPayService.processOrder(bodyMap);
//应答超时
//模拟接收微信端的重复通知
TimeUnit.SECONDS.sleep(5);
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
/**
* 用户取消订单
* @param orderNo
* @return
* @throws Exception
*/
@PostMapping("/cancel/{orderNo}")
public R cancel(@PathVariable String orderNo) throws Exception {
log.info("取消订单");
wxPayService.cancelOrder(orderNo);
return R.ok().setMessage("订单已取消");
}
/**
* 查询订单
* @param orderNo
* @return
* @throws Exception
*/
@GetMapping("/query/{orderNo}")
public R queryOrder(@PathVariable String orderNo) throws Exception {
log.info("查询订单");
String result = wxPayService.queryOrder(orderNo);
return R.ok().setMessage("查询成功").data("result", result);
}
@GetMapping("/querybill/{billDate}/{type}")
public R queryTradeBill(
@PathVariable String billDate,
@PathVariable String type) throws Exception {
log.info("获取账单url");
String downloadUrl = wxPayService.queryBill(billDate, type);
return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
}
}
admin后端也和user后端一样,需要引入支付商户信息。
@Service
@Slf4j
public class RefundServiceImpl extends ServiceImpl<RefundMapper, Refund> implements RefundService {
@Resource
private WxPayConfig wxPayConfig;
@Resource
private OrderService orderService;
@Resource
private CloseableHttpClient wxPayClient;
private final ReentrantLock lock = new ReentrantLock();
/**
* 根据订单号创建退款订单
*/
@Override
public Refund createRefundByOrderNo(String orderNo, String reason) {
//根据订单号获取订单信息
Order order = orderService.getOrderByOrderNo(orderNo);
//根据订单号生成退款订单
Refund refund = new Refund();
refund.setOrderNo(orderNo);//订单编号
refund.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
refund.setTotalFee(order.getTotalFee());//原订单金额(分)
refund.setRefund(order.getTotalFee());//退款金额(分)
refund.setReason(reason);//退款原因
//保存退款订单
baseMapper.insert(refund);
return refund;
}
/**
* 记录退款记录
*/
@Override
public void updateRefund(String content) {
//将json字符串转换成Map
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(content, HashMap.class);
//根据退款单编号修改退款单
QueryWrapper<Refund> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));
//设置要修改的字段
Refund refund = new Refund();
//微信支付退款单号
refund.setRefundId(resultMap.get("refund_id"));
//查询退款和申请退款中的返回参数
if(resultMap.get("status") != null){
refund.setRefundStatus(resultMap.get("status"));//退款状态
refund.setContentReturn(content);//将全部响应结果存入数据库的content字段
}
//退款回调中的回调参数
if(resultMap.get("refund_status") != null){
refund.setRefundStatus(resultMap.get("refund_status"));//退款状态
refund.setContentNotify(content);//将全部响应结果存入数据库的content字段
}
//更新退款单
baseMapper.update(refund, queryWrapper);
}
/**
* 找出申请退款超过minutes分钟并且未成功的退款单
* @param minutes
* @return
*/
@Override
public List<Refund> getNoRefundOrderByDuration(int minutes) {
//minutes分钟之前的时间
Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
QueryWrapper<Refund> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("refund_status", WxRefundStatus.PROCESSING.getType());
queryWrapper.le("create_time", instant);
List<Refund> refundList = baseMapper.selectList(queryWrapper);
return refundList;
}
/**
* 退款(订单号、理由)
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) throws Exception {
log.info("创建退款单记录");
//根据订单编号创建退款单
Refund refundsInfo = this.createRefundByOrderNo(orderNo, reason);
log.info("调用退款API");
//调用统一下单API
String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
HttpPost httpPost = new HttpPost(url);
// 请求body参数
Gson gson = new Gson();
Map paramsMap = new HashMap();
paramsMap.put("out_trade_no", orderNo);//订单编号
paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
paramsMap.put("reason",reason);//退款原因
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
Map amountMap = new HashMap();
amountMap.put("refund", refundsInfo.getRefund());//退款金额
amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
amountMap.put("currency", "CNY");//退款币种
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);
StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");//设置请求报文格式
httpPost.setEntity(entity);//将请求报文放入请求对象
httpPost.setHeader("Accept", "application/json");//设置响应报文格式
//完成签名并执行请求,并完成验签
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
//解析响应结果
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);
}
//更新订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);
//更新退款单
this.updateRefund(bodyAsString);
} finally {
response.close();
}
}
/**
* 查询退款接口调用
*/
@Override
public String queryRefund(String refundNo) throws Exception {
log.info("查询退款接口调用 ===> {}", refundNo);
String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
url = wxPayConfig.getDomain().concat(url);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 查询退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);
}
return bodyAsString;
} finally {
response.close();
}
}
/**
* 根据退款单号核实退款单状态
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void checkRefundStatus(String refundNo) throws Exception {
log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);
//调用查询退款单接口
String result = this.queryRefund(refundNo);
//组装json请求体字符串
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
//获取微信支付端退款状态
String status = resultMap.get("status");
String orderNo = resultMap.get("out_trade_no");
if (WxRefundStatus.SUCCESS.getType().equals(status)) {
log.warn("核实订单已退款成功 ===> {}", refundNo);
//如果确认退款成功,则更新订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
//更新退款单
this.updateRefund(result);
}
if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
log.warn("核实订单退款异常 ===> {}", refundNo);
//如果确认退款成功,则更新订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
//更新退款单
this.updateRefund(result);
}
}
/**
* 处理退款单
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processRefund(Map<String, Object> bodyMap) throws Exception {
log.info("退款单");
//解密报文
String plainText = decryptFromResource(bodyMap);
//将明文转换成map
Gson gson = new Gson();
HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
String orderNo = (String)plainTextMap.get("out_trade_no");
if(lock.tryLock()){
try {
String orderStatus = orderService.getOrderStatus(orderNo);
if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
return;
}
//更新订单状态
orderService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
//更新退款单
this.updateRefund(plainText);
} finally {
//要主动释放锁
lock.unlock();
}
}
}
/**
* 对称解密
*/
private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
log.info("密文解密");
//通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
//数据密文
String ciphertext = resourceMap.get("ciphertext");
//随机串
String nonce = resourceMap.get("nonce");
//附加数据
String associatedData = resourceMap.get("associated_data");
log.info("密文 ===> {}", ciphertext);
AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info("明文 ===> {}", plainText);
return plainText;
}
}
也就是将mysql数据库表导出为excel表,具体参考:https://blog.csdn.net/yueyue763184/article/details/130470124?spm=1001.2014.3001.5501
使用的是Vue+Element-ui+axios
用axios发送订单请求
// axios 发送ajax请求
import request from '@/utils/request'
export default{
//Native下单
nativePay(productId, userId) {
return request({
url: '/api/wx-pay/native/' + productId + "/" + userId,
method: 'post'
})
},
// 取消订单
cancel(orderNo) {
return request({
url: '/api/wx-pay/cancel/' + orderNo,
method: 'post'
})
}
}
首页ui代码
<template>
<div class="bg-fa of">
<!-- 公共头 -->
<AppHeader :inputName="user.name"/>
<!-- /公共头 -->
<section id="index" class="container">
<header class="comm-title">
<h2 class="fl tac">
<span class="c-333">课程列表</span>
</h2>
</header>
<ul>
<li v-for="product in productList" :key="product.id">
<a :class="['orderBtn', {current:payOrder.productId === product.id}]"
@click="selectItem(product.id)"
href="javascript:void(0);">
{{ product.title }}
¥{{ product.price / 100 }}
</a>
</li>
</ul>
<div class="PaymentChannel_payment-channel-panel">
<h3 class="PaymentChannel_title">
选择支付方式
</h3>
<div class="PaymentChannel_channel-options">
<!-- 选择微信 -->
<div :class="['ChannelOption_payment-channel-option', {current:payOrder.payType === 'wxpay'}]"
@click="selectPayType('wxpay')">
<div class="ChannelOption_channel-icon">
<img src="../assets/img/wxpay.png" class="ChannelOption_icon">
</div>
<div class="ChannelOption_channel-info">
<div class="ChannelOption_channel-label">
<div class="ChannelOption_label">微信支付</div>
<div class="ChannelOption_sub-label"></div>
<div class="ChannelOption_check-option"></div>
</div>
</div>
</div>
</div>
</div>
<div class="payButtom">
<el-button
:disabled="payBtnDisabled"
type="warning"
round
style="width: 180px;height: 44px;font-size: 18px;"
@click="toPay()">
确认支付
</el-button>
</div>
</section>
<!-- 微信支付二维码 -->
<el-dialog
:visible.sync="codeDialogVisible"
:show-close="false"
@close="closeDialog"
width="350px"
center>
<qriously :value="codeUrl" :size="300"/>
使用微信扫码支付
</el-dialog>
<!-- 公共底 -->
<AppFooter/>
<!-- /公共底 -->
</div>
</template>
<script>
import productApi from '../api/product'
import wxPayApi from '../api/wxPay'
import orderInfoApi from '../api/orderInfo'
import AppHeader from '../components/AppHeader'
import selectUserByName from "../api/login"
import AppFooter from '../components/AppFooter'
import '../assets/css/reset.css'
import '../assets/css/theme.css'
import '../assets/css/global.css'
export default {
components: {
AppHeader, AppFooter
},
data() {
return {
payBtnDisabled: false, //确认支付按钮是否禁用
codeDialogVisible: false, //微信支付二维码弹窗
productList: [], //商品列表
payOrder: { //订单信息
productId: '', //商品id
payType: 'wxpay' //支付方式
},
codeUrl: '', // 二维码
orderNo: '', //订单号
timer: null, // 定时器
// 存储兄弟组件login传过来的user信息
user: {
id: null,
name: "未登录"
}
}
},
mounted() {
// 获取兄弟组件传的值
this.$bus.$on("name", name => {
this.user.name = name;
// 根据name查询user信息
selectUserByName.selectUserByName(this.user.name).then(r => {
this.user.id = r.data.user.id
// 将整个user对象存入浏览器缓存
localStorage.setItem("user", JSON.stringify(this.user))
})
})
},
created() {
//获取商品列表
productApi.list().then(response => {
this.productList = response.data.productList
this.payOrder.productId = this.productList[0].id
})
// 20毫秒后读取浏览器缓存
if(JSON.parse(localStorage.getItem('user')).id !== null){
setTimeout(() => {
this.user = JSON.parse(localStorage.getItem('user'))
}, 20)
}
},
methods: {
//选择商品
selectItem(productId) {
this.payOrder.productId = productId
},
//选择支付方式
selectPayType(type) {
this.payOrder.payType = type
},
//确认支付
toPay() {
if (this.user.id === null) {
this.$router.push("/login")
} else {
//禁用按钮,防止重复提交
this.payBtnDisabled = true
//微信支付
if (this.payOrder.payType === 'wxpay') {
//调用统一下单接口
wxPayApi.nativePay(this.payOrder.productId, this.user.id).then(response => {
this.codeUrl = response.data.codeUrl
this.orderNo = response.data.orderNo
//打开二维码弹窗
this.codeDialogVisible = true
//启动定时器
this.timer = setInterval(() => {
//查询订单是否支付成功
this.queryOrderStatus()
}, 3000)
})
}
}
},
//关闭微信支付二维码对话框时让“确认支付”按钮可用
closeDialog() {
this.payBtnDisabled = false
clearInterval(this.timer) // 清除定时器
},
// 查询订单状态
queryOrderStatus() {
orderInfoApi.queryOrderStatus(this.orderNo).then(response => {
if (response.code === 0) {
clearInterval(this.timer) // 清除定时器
// 5毫秒后提示支付成功
setTimeout(() => {
this.payBtnDisabled = false
this.codeDialogVisible = false
this.$message.success("支付成功!")
}, 5)
}
})
}
}
}
</script>
用axios发送订单请求
import request from '@/utils/request'
export default{
// 退款请求
refundsByOrderNo(orderNo) {
return request({
url: '/api/wx-pay/refunds/'+ orderNo +'/管理员退款',
method: 'get'
})
},
}
订单管理页面代码
<template>
<div>
<!--条件搜索区域-->
<el-row>
<el-col :span="24">
<el-card header=" ">
<span class="header2">订单列表</span>
<el-form :inline="true" style="margin: 0 0 0 300px">
<el-form-item label="用户名称:">
<el-input
v-model="searchOrder.orderName"
placeholder="用户名"
clearable>
</el-input>
</el-form-item>
<el-form-item>
<el-button icon="el-icon-search" @click="search">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button @click="exportExcel">
<i class="el-icon-download"></i>下载所有订单信息
</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
<!--数据显示区域-->
<el-row>
<el-col :span="24">
<el-card>
<!-- 订单的详细信息 -->
<el-table :data="orderData" style="width: 100%">
<el-table-column
width="40"
prop="id"
label="ID">
</el-table-column>
<el-table-column
width="110"
prop="userName"
label="用户">
</el-table-column>
<el-table-column
width="250"
prop="orderNo"
label="订单号">
</el-table-column>
<el-table-column
width="175"
prop="title"
label="产品名称">
</el-table-column>
<el-table-column
width="100"
prop="totalFee"
label="价格/¥">
</el-table-column>
<el-table-column
width="200"
prop="orderStatus"
label="订单状态">
</el-table-column>
<el-table-column
width="180"
prop="updateTime"
label="更新时间">
</el-table-column>
<el-table-column label="操作" width="210">
<template slot-scope="scope">
<el-popconfirm :title="('确定删除第'+ scope.row.id +'个订单吗?')" @confirm="remove(scope.row.id)">
<!--删除按钮-->
<el-button
icon="el-icon-close"
size="mini"
type="danger"
slot="reference">删除
</el-button>
</el-popconfirm>
<!--退款按钮-->
<el-button
style="margin: 0 0 0 15px;"
v-if="scope.row.orderStatus==='支付成功'||scope.row.orderStatus==='支付成功,用户已删除'"
icon="el-icon-sort"
size="mini"
type="primary"
@click="refundsBtn(scope.row.id,scope.row.orderNo)"
slot="reference">退款
</el-button>
<!-- 退款时弹出的确认对话框 -->
<el-dialog title="提示" :visible.sync="centerDialogVisible" width="30%" center>
<span style="margin-left: 25%">确定退款回第<span
style="color: red">{{ orderRefunds.id }}</span>号订单的用户吗?</span>
<span slot="footer" class="dialog-footer">
<el-button @click="centerDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="refunds">确 定</el-button>
</span>
</el-dialog>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<!--分页-->
<el-row class="page-row">
<el-pagination
class="el-pagination"
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[5, 10, 30, 50]"
@size-change="sizeChange"
@current-change="currentChange"
@prev-click="currentChange"
@next-click="currentChange"
:current-page="current"
:page-size="size"
:total="total">
</el-pagination>
</el-row>
</div>
</template>
<script>
import order from "../api/order"
import refunds from "../api/refunds"
export default {
data() {
return {
adminName: "",
// 是否显示提示框
centerDialogVisible: false,
// 退款时的数据
orderRefunds: {
id: null,
orderNo: '',
},
// 搜索条件对象
searchOrder: {
orderName: '',
},
// 产品数据 (页面初始化时会将数据传进来)
orderData: [],
size: 5, // 每页的数据大小
current: 1, //当前页数
total: 0, // 总数量
}
},
created() {
this.getData()
if(JSON.parse(localStorage.getItem('adminName')) === null){
this.$router.push("/login")
}else{
setTimeout(() => {
// 20毫秒后读取浏览器缓存
this.adminName = JSON.parse(localStorage.getItem('adminName'))
}, 20)
}
},
methods: {
// 下载订单
exportExcel() {
// 这里可以传入一些查询参数,我在这里传入了标题内容
// 将标题内容作为导出的Excel文件名,此处的标题内容后期可动态改变
let url = 'http://127.0.0.1:8051/api/order/exportExcel/订单信息'
window.open(url)
},
// 点击退款按钮时保存值
refundsBtn(id, orderNo) {
this.orderRefunds.id = id
this.orderRefunds.orderNo = orderNo
this.centerDialogVisible = true
},
// 退款
refunds() {
refunds.refundsByOrderNo(this.orderRefunds.orderNo).then(r => {
if (r.code === 0) {
this.$message.success(r.message)
}
if (this.searchOrder.orderName !== '') {
this.search()
} else {
this.getData()
}
})
this.centerDialogVisible = false
},
// 根据搜索条件查询
search() {
if (this.searchOrder.orderName !== '') {
order.getOrderByUserName(this.searchOrder.orderName, this.current, this.size).then(r => {
let orderList = r.data.orderList
this.total = r.total
this.orderData = orderList
// 将分转化为元
for (let i = 0; i < orderList.length; i++) {
this.orderData[i].totalFee = orderList[i].totalFee / 100
}
})
} else {
this.$message.error("搜索的用户名不能为空")
}
},
// 删除
remove(id) {
order.deleteOrderByOrderId(id).then(r => {
this.$message.success(r.message)
if (this.searchOrder.orderName !== '') {
this.search()
} else {
this.getData()
}
})
},
// 每页大小修改
sizeChange(val) {
this.size = val
if (this.searchOrder.orderName !== '') {
this.search()
} else {
this.getData()
}
},
// 第几页
currentChange(val) {
this.current = val
if (this.searchOrder.orderName !== '') {
this.search()
} else {
this.getData()
}
},
// 获取数据,并赋值给wordData
getData() {
order.getOrderPage(this.current, this.size).then(r => {
let orderList = r.data.orderList
this.total = r.total
this.orderData = orderList
// 将分转化为元
for (let i = 0; i < orderList.length; i++) {
this.orderData[i].totalFee = orderList[i].totalFee / 100
}
})
}
}
}
</script>
<style>
@import "~@/assets/css/ddgl.css";
</style>
作者博客:https://blog.csdn.net/yueyue763184?type=lately
!对此作品有任何异议请联系作者邮箱:2013994940@qq.com
!对此作品有任何异议请联系作者邮箱:2013994940@qq.com
!对此作品有任何异议请联系作者邮箱:2013994940@qq.com
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。