package beilv.wx.pay.service; import beilv.carinformation.mapper.CarInformationMapper; import beilv.common.core.domain.AjaxResult; import beilv.competition.mapper.CompetitionMapper; import beilv.order.domain.StoreOrder; import beilv.order.domain.StoreRefund; import beilv.order.mapper.StoreOrderMapper; import beilv.order.mapper.StoreRefundMapper; import beilv.system.domain.SysMember; import beilv.system.service.ISysMemberService; import beilv.vipCardLog.domain.VipCardLog; import beilv.vipCardLog.service.IVipCardLogService; import beilv.wx.pay.config.WxPayProperties; import beilv.wx.pay.domain.vo.AppPayParam; import beilv.wx.pay.domain.vo.AppRefundParam; import com.github.binarywang.wxpay.bean.notify.SignatureHeader; import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response; import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result; import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result; import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryRequest; import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request; import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; import com.github.binarywang.wxpay.bean.result.BaseWxPayResult; import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult; import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result; import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result.JsapiResult; import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Set; import java.util.UUID; @Slf4j @Service @AllArgsConstructor public class IWxPayService { @Autowired private CarInformationMapper carInformationMapper; @Autowired private StoreOrderMapper storeOrderMapper; @Autowired private CompetitionMapper competitionMapper; @Autowired private WxPayProperties wxPayProperties; @Autowired private ISysMemberService sysMemberService; @Autowired private StoreRefundMapper storeRefundMapper; @Autowired private StringRedisTemplate redisTemplate; @Autowired private IVipCardLogService vipCardLogService; private WxPayService wxService; // Redis中订单延迟队列的key前缀 private static final String ORDER_DELAY_QUEUE_PREFIX = "order:delay:queue:"; // 订单超时时间(5分钟) private static final long ORDER_TIMEOUT_SECONDS = 5 * 60; /** * 统一下单接口 * * @return 预支付交易会话标识 */ public JsapiResult unifiedOrder(AppPayParam param) throws Exception { log.info("开始处理统一下单请求,订单号: {}", param.getOrderId()); // 幂等性校验 - 检查订单是否已存在且已支付 StoreOrder existingOrder = storeOrderMapper.selectStoreOrderByOrderId(param.getOrderId()); if (existingOrder != null && existingOrder.getPaid() != null && existingOrder.getPaid() == 1) { log.warn("订单 {} 已支付,不能重复下单", param.getOrderId()); throw new Exception("订单已支付,不能重复下单"); } SysMember user = getSysMember(param.getUserId()); param.setSysMember(user); //微信支付需要的参数 WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); request.setAppid(wxPayProperties.getAppId()); request.setMchid(wxPayProperties.getMchId()); request.setDescription(param.getDescription()); request.setOutTradeNo(param.getOrderId()); request.setNotifyUrl(wxPayProperties.getNotifyUrl()); WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount(); amount.setTotal(yuanToFen(param.getPayPrice())); amount.setCurrency("CNY"); request.setAmount(amount); request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(user.getOpenId())); log.info("调用微信统一下单接口,订单号: {}", param.getOrderId()); JsapiResult jsapiResult = this.wxService.createOrderV3(TradeTypeEnum.JSAPI, request); if (StringUtils.isNotEmpty(jsapiResult.getPackageValue())) { log.info("微信统一下单成功,订单号: {},开始创建本地订单", param.getOrderId()); //成功之后创建订单 generateOrder(param, jsapiResult.getPackageValue()); //将订单添加到延迟队列中 addOrderToDelayQueue(param.getOrderId()); log.info("订单创建完成并加入延迟队列,订单号: {}", param.getOrderId()); } else { log.warn("微信统一下单失败,订单号: {}", param.getOrderId()); } return jsapiResult; } //获取当前用户信息 private SysMember getSysMember(Long userId) { log.debug("获取用户信息,用户ID: {}", userId); return sysMemberService.selectSysMemberById(userId); } /** * 元转分(不四舍五入,直接截取) * * @param amount 金额(元) * @return 分(整数) */ public static int yuanToFen(BigDecimal amount) { if (amount == null) return 0; return amount.multiply(BigDecimal.valueOf(100)) .setScale(0, RoundingMode.DOWN) // 不四舍五入 .intValueExact(); } /** * 统一下单接口 * * @return 预支付交易会话标识 */ private void generateOrder(AppPayParam param, String payId) { log.info("开始生成订单,订单号: {}", param.getOrderId()); //查看是否已经生成订单,但是未支付 if (!isOrderInDelayQueue(param.getOrderId())) { // 订单不存在,创建新订单 log.info("订单不存在,创建新订单,订单号: {}", param.getOrderId()); StoreOrder storeOrder = new StoreOrder(); storeOrder.setOrderId(param.getOrderId()); storeOrder.setUid(param.getSysMember().getId()); storeOrder.setRealName(param.getSysMember().getUsername()); storeOrder.setUserPhone(param.getSysMember().getMobile()); storeOrder.setUserAddress(param.getSysMember().getAddress()); storeOrder.setTotalNum(1); storeOrder.setTotalPrice(param.getTotalPrice()); storeOrder.setPayPrice(param.getPayPrice()); storeOrder.setPayTime(new Date()); storeOrder.setPayId(payId); storeOrder.setCreateTime(new Date()); storeOrderMapper.insertStoreOrder(storeOrder); log.info("新订单创建成功,订单号: {}", param.getOrderId()); } else { StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(param.getOrderId()); // 订单已存在,更新支付相关信息 log.info("订单已存在,更新支付信息,订单号: {}", param.getOrderId()); order.setPayTime(new Date()); order.setPayId(payId); storeOrderMapper.updateStoreOrder(order); log.info("订单支付信息更新完成,订单号: {}", param.getOrderId()); } } /** * 将订单添加到延迟队列 * * @param orderId 订单ID */ private void addOrderToDelayQueue(String orderId) { log.info("将订单添加到延迟队列,订单号: {}", orderId); // 计算订单超时时间戳(当前时间+5分钟) long expireTime = System.currentTimeMillis() + (ORDER_TIMEOUT_SECONDS * 1000); // 将订单添加到Redis有序集合中,以超时时间戳作为score redisTemplate.opsForZSet().add(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId, expireTime); log.info("订单 {} 已添加到延迟队列,超时时间: {}", orderId, new Date(expireTime)); } /** * 从延迟队列中移除订单(支付成功时调用) * * @param orderId 订单ID */ private void removeOrderFromDelayQueue(String orderId) { log.info("从延迟队列中移除订单,订单号: {}", orderId); // 从Redis有序集合中移除订单 redisTemplate.opsForZSet().remove(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId); log.info("订单 {} 已从延迟队列中移除", orderId); } //生成退款订单号 public String generateOutTradeNo() { String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); String random = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 6位随机 return "REF" + time + random; } /** * 处理支付结果通知 * * @return 处理结果 */ public AjaxResult handleNotify(String notifyData, HttpServletRequest request) { log.info("收到微信支付回调通知"); SignatureHeader header = getRequestHeader(request); try { WxPayNotifyV3Result res = this.wxService.parseOrderNotifyV3Result(notifyData, header); WxPayNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult(); log.info("解析微信支付回调结果,订单号: {}, 交易状态: {}", decryptRes.getOutTradeNo(), decryptRes.getTradeState()); if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(decryptRes.getTradeState())) { log.info("支付成功,开始处理订单,订单号: {}", decryptRes.getOutTradeNo()); // 成功处理订单 processOrderPayment(decryptRes.getOutTradeNo()); //调用充值记录服务,更新充值记录状态 VipCardLog vipCardLog = new VipCardLog(); vipCardLog.setId(decryptRes.getOutTradeNo()); vipCardLog.setPaymentStatus("payment_status_have_paid"); vipCardLogService.updateVipCardLogByOrderId(vipCardLog); // 成功返回200/204,body无需有内容 log.info("订单处理完成,订单号: {}", decryptRes.getOutTradeNo()); return AjaxResult.success(); } else { log.warn("支付失败,订单号: {},状态: {}", decryptRes.getOutTradeNo(), decryptRes.getTradeState()); // 失败返回4xx或5xx,且需要构造body信息 return AjaxResult.error("订单支付失败"); } } catch (WxPayException e) { log.error("处理微信支付回调失败", e); // 失败返回4xx或5xx,且需要构造body信息 return AjaxResult.error("微信支付回调失败"); } } /** * 处理订单支付完成后的业务逻辑 * * @param orderId 订单号 */ private void processOrderPayment(String orderId) { log.info("开始处理订单支付完成逻辑,订单号: {}", orderId); // 使用Redis分布式锁防止并发处理 String lockKey = "payment:lock:" + orderId; Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", java.time.Duration.ofSeconds(30)); if (Boolean.FALSE.equals(lockAcquired)) { log.warn("订单正在被其他线程处理,订单号: {}", orderId); return; } try { StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId); if (order != null) { // 检查订单是否已被取消 if (order.getStatus() != null && order.getStatus() == 3) { log.warn("订单已超时取消,支付回调到达,订单号: {}", orderId); // TODO: 这里应该调用退款接口,将钱退回给用户 return; } // 检查订单是否已经处理过 if (order.getPaid() == null || order.getPaid() != 1) { log.info("更新订单状态为已支付,订单号: {}", orderId); // 更新订单状态 order.setPaid(1); // 设置为已支付 order.setStatus(1); // 设置订单状态为已支付 order.setPayTime(new Date()); // 设置支付时间 storeOrderMapper.updateStoreOrder(order); // 从延迟队列中移除订单 removeOrderFromDelayQueue(orderId); log.info("订单状态更新完成,订单号: {}", orderId); } else { log.info("订单已处理过,无需重复处理,订单号: {}", orderId); } } else { log.warn("订单不存在,订单号: {}", orderId); } } finally { // 释放锁 redisTemplate.delete(lockKey); } } /** * 微信支付-申请退款 * */ public WxPayRefundV3Result refund(AppRefundParam param) throws WxPayException { log.info("开始处理退款请求,订单号: {}", param.getOrderId()); // 检查订单是否存在 StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(param.getOrderId()); if (order == null) { log.warn("退款失败,订单不存在,订单号: {}", param.getOrderId()); throw new WxPayException("订单不存在"); } // 检查订单是否已支付 if (order.getPaid() == null || order.getPaid() != 1) { log.warn("退款失败,订单未支付,订单号: {}", param.getOrderId()); throw new WxPayException("订单未支付,无法退款"); } // 检查订单是否已退款 if (order.getRefundStatus() != null && order.getRefundStatus() == 2) { log.warn("退款失败,订单已退款,订单号: {}", param.getOrderId()); throw new WxPayException("订单已退款"); } // 检查退款金额是否合法 if (param.getRefundAmount().compareTo(order.getPayPrice()) > 0) { log.warn("退款失败,退款金额大于支付金额,订单号: {}", param.getOrderId()); throw new WxPayException("退款金额不能大于支付金额"); } WxPayRefundV3Request request = new WxPayRefundV3Request(); //商户退款单号 request.setOutRefundNo(param.getOrderId()); //原支付商户订单号 request.setOutTradeNo(param.getOrderId()); //退款原因说明 request.setReason(param.getReason()); WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount(); //原订单实际支付金额(单位:分) amount.setTotal(yuanToFen(param.getTotalPrice())); //退款金额(单位:分) amount.setRefund(yuanToFen(param.getRefundAmount())); //货币类型,默认 "CNY" amount.setCurrency("CNY"); request.setAmount(amount); request.setNotifyUrl(wxPayProperties.getRefundNotifyUrl()); param.setSysMember(getSysMember(param.getUserId())); // 检查是否已存在退款记录 StoreRefund existingRefund = storeRefundMapper.selectStoreRefundByOrderId(param.getOrderId()); if (existingRefund != null && "2".equals(existingRefund.getRefundStatus())) { log.warn("退款失败,退款记录已存在且已完成,订单号: {}", param.getOrderId()); throw new WxPayException("该订单已退款"); } // 先调用微信退款接口 log.info("调用微信退款接口,订单号: {}", param.getOrderId()); WxPayRefundV3Result result = this.wxService.refundV3(request); log.info("微信退款接口调用成功,订单号: {}", param.getOrderId()); // 微信退款成功后,再创建本地退款记录 if (existingRefund == null) { log.info("微信退款成功,创建本地退款记录,订单号: {}", param.getOrderId()); refundOrder(param); } return result; } /** * 统一退款接口 * * @return 预支付交易会话标识 */ private void refundOrder(AppRefundParam param) { log.info("生成退款订单记录,订单号: {}", param.getOrderId()); StoreRefund storeRefund = new StoreRefund(); storeRefund.setUid(param.getSysMember().getId()); storeRefund.setRefundNo(generateOutTradeNo()); storeRefund.setOrderId(param.getOrderId()); storeRefund.setRefundAmount(param.getRefundAmount()); storeRefund.setRefundReason(param.getReason()); storeRefund.setCreateTime(new Date()); storeRefundMapper.insertStoreRefund(storeRefund); log.info("退款订单记录创建完成,订单号: {}", param.getOrderId()); } /** * 退款结果通知 * * @return 处理结果 */ public AjaxResult parseRefundNotify(String notifyData, HttpServletRequest request) { log.info("收到微信退款回调通知"); SignatureHeader header = getRequestHeader(request); try { WxPayRefundNotifyV3Result res = this.wxService.parseRefundNotifyV3Result(notifyData, header); WxPayRefundNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult(); log.info("解析微信退款回调结果,订单号: {}, 退款状态: {}", decryptRes.getOutTradeNo(), decryptRes.getRefundStatus()); // TODO 根据自己业务场景需要构造返回对象 if (WxPayConstants.RefundStatus.SUCCESS.equals(decryptRes.getRefundStatus())) { log.info("退款成功,开始处理订单,订单号: {}", decryptRes.getOutTradeNo()); // 成功处理订单 processOrderRefund(decryptRes.getOutTradeNo()); //成功返回200/204,body无需有内容 log.info("退款处理完成,订单号: {}", decryptRes.getOutTradeNo()); return AjaxResult.success(); } else { log.warn("退款失败,订单号: {},状态: {}", decryptRes.getOutTradeNo(), decryptRes.getRefundStatus()); //失败返回4xx或5xx,且需要构造body信息 return AjaxResult.error(); } } catch (WxPayException e) { log.error("处理微信退款回调失败", e); //失败返回4xx或5xx,且需要构造body信息 return AjaxResult.error("微信退款回调失败"); } } /** * 退款完成后的业务逻辑 * * @param orderId 订单号 */ private void processOrderRefund(String orderId) { log.info("开始处理订单退款完成逻辑,订单号: {}", orderId); StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId); if (order != null) { // 检查订单是否已经处理过 if (order.getRefundStatus() != null && order.getPaid() == 1) { log.info("更新订单状态为已退款,订单号: {}", orderId); //更新支付订单状态为退款 order.setStatus(2); // 3表示已取消 order.setUpdateTime(new Date()); storeOrderMapper.updateStoreOrder(order); log.info("订单状态更新完成,订单号: {}", orderId); } else { log.info("订单退款状态无需更新,订单号: {}", orderId); } } else { log.warn("订单不存在,订单号: {}", orderId); } StoreRefund refund = storeRefundMapper.selectStoreRefundByOrderId(orderId); if (refund != null) { // 检查订单是否已经处理过 if (refund.getRefundStatus() != null && refund.getRefundStatus().equals("1")) { log.info("更新退款记录状态为已退款,退款单号: {}", refund.getRefundNo()); // 更新订单状态 refund.setRefundStatus("2"); // 设置订单状态为已退款 refund.setSuccessTime(new Date()); storeRefundMapper.updateStoreRefund(refund); log.info("退款记录状态更新完成,退款单号: {}", refund.getRefundNo()); } else { log.info("退款记录状态无需更新,退款单号: {}", refund.getRefundNo()); } } else { log.warn("退款记录不存在,订单号: {}", orderId); } } /** * 组装请求头重的前面信息 * * @param request * @return */ private SignatureHeader getRequestHeader(HttpServletRequest request) { log.debug("解析微信回调请求头信息"); // 获取通知签名 String signature = request.getHeader("Wechatpay-Signature"); String nonce = request.getHeader("Wechatpay-Nonce"); String serial = request.getHeader("Wechatpay-Serial"); String timestamp = request.getHeader("Wechatpay-Timestamp"); SignatureHeader signatureHeader = new SignatureHeader(); signatureHeader.setSignature(signature); signatureHeader.setNonce(nonce); signatureHeader.setSerial(serial); signatureHeader.setTimeStamp(timestamp); return signatureHeader; } /** * 处理超时订单 * 定时任务调用此方法检查并处理超时订单 */ public void processTimeoutOrders() { // log.info("开始处理超时订单"); // 获取当前时间戳 long currentTime = System.currentTimeMillis(); // 从Redis有序集合中获取所有已超时的订单(score小于当前时间的元素) redisTemplate.opsForZSet().rangeByScore(ORDER_DELAY_QUEUE_PREFIX + "orders", 0, currentTime) .forEach(orderId -> { // 使用分布式锁防止与支付回调冲突 String lockKey = "payment:lock:" + orderId; Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", java.time.Duration.ofSeconds(30)); if (Boolean.FALSE.equals(lockAcquired)) { log.warn("订单正在被处理,跳过超时处理,订单号: {}", orderId); return; } try { log.info("处理超时订单: {}", orderId); // 查询订单信息 StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId); if (order != null && (order.getPaid() == null || order.getPaid() != 1)) { // 订单未支付,执行删除操作或更新状态 // 这里我们更新订单状态为已取消 order.setStatus(3); // 3表示已取消 order.setUpdateTime(new Date()); storeOrderMapper.updateStoreOrder(order); log.info("订单 {} 已超时,状态已更新为已取消", orderId); } else if (order != null) { log.info("订单 {} 已支付,无需处理", orderId); } else { log.warn("订单 {} 不存在", orderId); } // 从延迟队列中移除订单 redisTemplate.opsForZSet().remove(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId); log.info("超时订单 {} 处理完成", orderId); } catch (Exception e) { log.error("处理超时订单 {} 时发生错误", orderId, e); } finally { // 释放锁 redisTemplate.delete(lockKey); } }); // log.info("超时订单处理完成"); } /** * 取消订单 * * @param orderId 订单ID * @return 处理结果 */ public int cancelOrder(String orderId) { StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId); order.setStatus(3); // 3表示已取消 order.setUpdateTime(new Date()); return storeOrderMapper.updateStoreOrder(order); } /** * 检查订单是否在延迟队列中 * * @param orderId 订单ID * @return 如果订单在延迟队列中返回true,否则返回false */ public boolean isOrderInDelayQueue(String orderId) { // 使用rank命令检查订单是否在有序集合中 // 如果返回null,说明订单不在集合中 Long rank = redisTemplate.opsForZSet().rank(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId); return rank != null; } }