package com.zsElectric.boot.business.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.google.gson.JsonObject; import com.zsElectric.boot.business.mapper.RechargeLevelMapper; import com.zsElectric.boot.business.model.entity.RechargeLevel; import com.zsElectric.boot.business.model.form.applet.LevelOrderForm; import com.zsElectric.boot.business.model.form.applet.UserPayForm; import com.zsElectric.boot.business.model.vo.UserInfoVO; import com.zsElectric.boot.business.service.RechargeLevelService; import com.zsElectric.boot.business.service.UserInfoService; import com.zsElectric.boot.common.constant.SystemConstants; import com.zsElectric.boot.core.pay.WXPayUtility; import com.zsElectric.boot.core.pay.WechatConstants; import com.zsElectric.boot.core.pay.WechatPayV3Utils; import com.zsElectric.boot.core.pay.WechatUrlConstants; import com.zsElectric.boot.security.util.SecurityUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.zsElectric.boot.business.mapper.UserOrderInfoMapper; import com.zsElectric.boot.business.service.UserOrderInfoService; import com.zsElectric.boot.business.model.entity.UserOrderInfo; import com.zsElectric.boot.business.model.form.UserOrderInfoForm; import com.zsElectric.boot.business.model.query.UserOrderInfoQuery; import com.zsElectric.boot.business.model.vo.UserOrderInfoVO; import com.zsElectric.boot.business.converter.UserOrderInfoConverter; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; /** * 用户支付订单信息服务实现类 * * @author zsElectric * @since 2025-12-16 16:25 */ @Slf4j @Service @RequiredArgsConstructor public class UserOrderInfoServiceImpl extends ServiceImpl implements UserOrderInfoService { private final UserOrderInfoConverter userOrderInfoConverter; private final UserInfoService userInfoService; private final RechargeLevelMapper rechargeLevelMapper; private final WechatPayV3Utils wechatPayV3Utils; // 声明一个可重入锁 private final ReentrantLock lock = new ReentrantLock(); /** * 获取用户支付订单信息分页列表 * * @param queryParams 查询参数 * @return {@link IPage} 用户支付订单信息分页列表 */ @Override public IPage getUserOrderInfoPage(UserOrderInfoQuery queryParams) { Page pageVO = this.baseMapper.getUserOrderInfoPage( new Page<>(queryParams.getPageNum(), queryParams.getPageSize()), queryParams ); return pageVO; } /** * 获取用户支付订单信息表单数据 * * @param id 用户支付订单信息ID * @return 用户支付订单信息表单数据 */ @Override public UserOrderInfoForm getUserOrderInfoFormData(Long id) { UserOrderInfo entity = this.getById(id); return userOrderInfoConverter.toForm(entity); } /** * 新增用户支付订单信息 * * @param formData 用户支付订单信息表单对象 * @return 是否新增成功 */ @Override public boolean saveUserOrderInfo(UserOrderInfoForm formData) { UserOrderInfo entity = userOrderInfoConverter.toEntity(formData); return this.save(entity); } /** * 更新用户支付订单信息 * * @param id 用户支付订单信息ID * @param formData 用户支付订单信息表单对象 * @return 是否修改成功 */ @Override public boolean updateUserOrderInfo(Long id, UserOrderInfoForm formData) { UserOrderInfo entity = userOrderInfoConverter.toEntity(formData); return this.updateById(entity); } /** * 删除用户支付订单信息 * * @param ids 用户支付订单信息ID,多个以英文逗号(,)分割 * @return 是否删除成功 */ @Override public boolean deleteUserOrderInfos(String ids) { Assert.isTrue(StrUtil.isNotBlank(ids), "删除的用户支付订单信息数据为空"); // 逻辑删除 List idList = Arrays.stream(ids.split(",")) .map(Long::parseLong) .toList(); return this.removeByIds(idList); } @Override public UserPayForm createOrder(LevelOrderForm levelOrderForm) { Long userId = SecurityUtils.getUserId(); String userOpenId = userInfoService.getCurrentUserInfo().getOpenid(); String orderNo = createOrderNo("SP",userId); //创建订单 UserOrderInfo orderInfo = new UserOrderInfo(); orderInfo.setUserId(userId); orderInfo.setOrderNo(orderNo); orderInfo.setLevelId(levelOrderForm.getLevelId()); orderInfo.setOpenid(userOpenId); this.save(orderInfo); //构建支付表单返回给前端支撑JsApi支付调用 UserPayForm payForm = new UserPayForm(); payForm.setOrderId(orderInfo.getId()).setOrderNo(orderNo); //查询档位 RechargeLevel level = rechargeLevelMapper.selectById(levelOrderForm.getLevelId()); Map result = payment(userOpenId,orderNo,level.getMoney()); payForm.setParams(result); return payForm; } @Override public UserPayForm payOrder(String orderId) { UserOrderInfo orderInfo = this.getById(orderId); //构建支付表单 UserPayForm payForm = new UserPayForm(); payForm.setOrderId(orderInfo.getId()).setOrderNo(orderInfo.getOrderNo()); //查询档位 RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId()); Map result = payment(orderInfo.getOpenid(),orderInfo.getOrderNo(),level.getMoney()); payForm.setParams(result); return payForm; } @Override public String orderQuery(String orderNo) { //查询订单 UserOrderInfo orderInfo = this.getById(Wrappers.lambdaQuery().eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1")); if (ObjectUtil.isEmpty(orderInfo)) { throw new RuntimeException("当前订单不存在"); } //null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败 JsonObject res = orderQueryByOutTradeNo(orderNo, WechatConstants.WECHAT_MCH_ID); String s = res == null ? null : res.get("trade_state").getAsString(); // String s = "SUCCESS"; if ("SUCCESS".equals(s)) { if (ObjectUtil.isNotEmpty(orderInfo) && Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_ONE)) { orderInfo.setOrderStatus(SystemConstants.STATUS_TWO); orderInfo.setPayTime(LocalDateTime.now()); orderInfo.setOutTradeNo(res.get("transaction_id").getAsString()); this.updateById(orderInfo); } return "100001";//支付成功 } if (s == null) { //查询订单 return "100002";//查询失败 } if ("USERPAYING".equals(s) || "ACCEPT".equals(s)) { //查询订单 return "100003";//查询中 } return "100004";//支付失败 } @Override public Map wechatPayNotify(HttpServletRequest request, HttpServletResponse response) { Map result = new HashMap<>(2); //验签及解析返回数据 JsonObject res = wechatPayV3Utils.getCallbackData(request); if (res == null) { result.put("code", "FAIL"); result.put("message", "失败"); return result; } log.info("最终拿到的微信支付通知数据:" + res); String orderNo = res.get("out_trade_no").getAsString(); if (lock.tryLock()) { // 处理支付成功后的业务 例如 将订单状态修改为已支付 具体参数键值可参考文档 注意!!! 微信可能会多次发送重复的通知 因此要判断业务是否已经处理过了 避免重复处理 try { //查询订单,判断是否已修改为已支付状态 UserOrderInfo orderInfo = this.getOne(Wrappers.lambdaQuery().eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1")); if (ObjectUtil.isNotEmpty(orderInfo)) { if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_TWO)) { result.put("code", "SUCCESS"); result.put("message", "OK"); return result; } if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_ONE)) { orderInfo.setOrderStatus(SystemConstants.STATUS_TWO); orderInfo.setOrderType(SystemConstants.STATUS_ONE); orderInfo.setTransactionId(res.get("transaction_id").getAsString()); orderInfo.setPayTime(LocalDateTime.now()); // orderInfo.setPayTime(dealDateFormat(res.getString("success_time"))); this.updateById(orderInfo); //todo 发送延迟消息 15分钟超时未支付 //todo 异步增加积分 } } result.put("code", "SUCCESS"); result.put("message", "OK"); return result; } catch (Exception e) { log.error("微信支付回调异常:" + e.getMessage()); result.put("code", "FAIL"); result.put("message", "失败"); return result; }finally { lock.unlock(); } }else { result.put("code", "FAIL"); result.put("message", "失败"); return result; } } /** * 通过商户订单号查询订单在微信侧支付状态 * * @param out_trade_no 发起支付时创建的商户订单号 * @return null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败 */ public JsonObject orderQueryByOutTradeNo(String out_trade_no, String subMchId) { String url = WechatUrlConstants.PAY_V3_QUERY_OUT; url = url.replace("{out_trade_no}", WXPayUtility.urlEncode(out_trade_no)); Map args = new HashMap<>(); args.put("sub_mchid", subMchId); url = url + "?" + WXPayUtility.urlEncode(args); return wechatPayV3Utils.sendGet(url); } /** * 构建支付表单返回给前端支撑JsApi支付调用 * @param openId * @param orderNo * @param amount * @return */ private Map payment(String openId, String orderNo,BigDecimal amount) { //15分钟超时限制 Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 15); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); //构建微信支付参数 Map params = new HashMap<>(); params.put("appid", WechatConstants.WECHAT_MP_APPID); //小程序appid params.put("mchid", WechatConstants.WECHAT_MCH_ID); //商户号 params.put("description", "订单业务"); //商品描述 params.put("out_trade_no", orderNo); //商户订单号 params.put("time_expire", sdf.format(calendar.getTime())); //交易结束时间 选填 时间到了之后将不能再支付 遵循rfc3339标准格式 params.put("attach", orderNo); //附加数据 选填 // 在查询API和支付通知中原样返回 可作为自定义参数使用 params.put("notify_url", WechatUrlConstants.PAY_V3_NOTIFY); //支付结果异步通知接口 //订单金额信息 Map amount_json = new HashMap<>(); //支付金额 单位:分 // amount_json.put("total", Integer.parseInt(amount_fee(Double.valueOf("0.1"))));测试用例 amount_json.put("total", Integer.parseInt(amount_fee(amount))); params.put("amount", amount_json); //支付者信息 Map payer = new HashMap<>(); //用户在小程序侧的openid payer.put("openid", openId); params.put("payer", payer); return params; } /** * 创建商户订单号 * 要求 32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一 * 组成 两位前缀 + 17位时间戳 + 9位id补零 + 4位随机数 合计32位 * * @param head 例如 商品-SP 退款-TK 等等 * @param id 用户id * @return */ public String createOrderNo(String head, Long id) { StringBuilder uid = new StringBuilder(id.toString()); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); int length = uid.length(); for (int i = 0; i < 9 - length; i++) { uid.insert(0, "0"); } return head + sdf.format(date) + uid + (int) ((Math.random() * 9 + 1) * 1000); } /** * 金额元转分字符串 * * @param cny 元 * @return */ public String amount_fee(BigDecimal cny) { BigDecimal b2 = new BigDecimal("100"); return cny.multiply(b2).setScale(0, RoundingMode.DOWN).toString(); } public static String amount_fee(double price) { DecimalFormat df = new DecimalFormat("#.00"); price = Double.valueOf(df.format(price)); int money = (int) (price * 100); return money + ""; } /** * 分转元,转换为bigDecimal在转成double * * @return */ public static double changeF2Y3(int price) { return BigDecimal.valueOf(Long.valueOf(price)).divide(new BigDecimal("100")).doubleValue(); } }