UserOrderInfoServiceImpl.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package com.zsElectric.boot.business.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.util.ObjectUtil;
  5. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  6. import com.google.gson.JsonObject;
  7. import com.zsElectric.boot.business.mapper.RechargeLevelMapper;
  8. import com.zsElectric.boot.business.model.entity.RechargeLevel;
  9. import com.zsElectric.boot.business.model.form.applet.LevelOrderForm;
  10. import com.zsElectric.boot.business.model.form.applet.UserPayForm;
  11. import com.zsElectric.boot.business.model.vo.UserInfoVO;
  12. import com.zsElectric.boot.business.service.RechargeLevelService;
  13. import com.zsElectric.boot.business.service.UserInfoService;
  14. import com.zsElectric.boot.common.constant.SystemConstants;
  15. import com.zsElectric.boot.core.pay.WXPayUtility;
  16. import com.zsElectric.boot.core.pay.WechatConstants;
  17. import com.zsElectric.boot.core.pay.WechatPayV3Utils;
  18. import com.zsElectric.boot.core.pay.WechatUrlConstants;
  19. import com.zsElectric.boot.security.util.SecurityUtils;
  20. import jakarta.servlet.http.HttpServletRequest;
  21. import jakarta.servlet.http.HttpServletResponse;
  22. import lombok.RequiredArgsConstructor;
  23. import lombok.extern.slf4j.Slf4j;
  24. import org.springframework.stereotype.Service;
  25. import com.baomidou.mybatisplus.core.metadata.IPage;
  26. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  27. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  28. import com.zsElectric.boot.business.mapper.UserOrderInfoMapper;
  29. import com.zsElectric.boot.business.service.UserOrderInfoService;
  30. import com.zsElectric.boot.business.model.entity.UserOrderInfo;
  31. import com.zsElectric.boot.business.model.form.UserOrderInfoForm;
  32. import com.zsElectric.boot.business.model.query.UserOrderInfoQuery;
  33. import com.zsElectric.boot.business.model.vo.UserOrderInfoVO;
  34. import com.zsElectric.boot.business.converter.UserOrderInfoConverter;
  35. import java.math.BigDecimal;
  36. import java.math.RoundingMode;
  37. import java.text.DecimalFormat;
  38. import java.text.SimpleDateFormat;
  39. import java.time.LocalDateTime;
  40. import java.util.*;
  41. import java.util.concurrent.ThreadLocalRandom;
  42. import java.util.concurrent.locks.ReentrantLock;
  43. import java.util.stream.Collectors;
  44. import cn.hutool.core.lang.Assert;
  45. import cn.hutool.core.util.StrUtil;
  46. /**
  47. * 用户支付订单信息服务实现类
  48. *
  49. * @author zsElectric
  50. * @since 2025-12-16 16:25
  51. */
  52. @Slf4j
  53. @Service
  54. @RequiredArgsConstructor
  55. public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, UserOrderInfo> implements UserOrderInfoService {
  56. private final UserOrderInfoConverter userOrderInfoConverter;
  57. private final UserInfoService userInfoService;
  58. private final RechargeLevelMapper rechargeLevelMapper;
  59. private final WechatPayV3Utils wechatPayV3Utils;
  60. // 声明一个可重入锁
  61. private final ReentrantLock lock = new ReentrantLock();
  62. /**
  63. * 获取用户支付订单信息分页列表
  64. *
  65. * @param queryParams 查询参数
  66. * @return {@link IPage<UserOrderInfoVO>} 用户支付订单信息分页列表
  67. */
  68. @Override
  69. public IPage<UserOrderInfoVO> getUserOrderInfoPage(UserOrderInfoQuery queryParams) {
  70. Page<UserOrderInfoVO> pageVO = this.baseMapper.getUserOrderInfoPage(
  71. new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
  72. queryParams
  73. );
  74. return pageVO;
  75. }
  76. /**
  77. * 获取用户支付订单信息表单数据
  78. *
  79. * @param id 用户支付订单信息ID
  80. * @return 用户支付订单信息表单数据
  81. */
  82. @Override
  83. public UserOrderInfoForm getUserOrderInfoFormData(Long id) {
  84. UserOrderInfo entity = this.getById(id);
  85. return userOrderInfoConverter.toForm(entity);
  86. }
  87. /**
  88. * 新增用户支付订单信息
  89. *
  90. * @param formData 用户支付订单信息表单对象
  91. * @return 是否新增成功
  92. */
  93. @Override
  94. public boolean saveUserOrderInfo(UserOrderInfoForm formData) {
  95. UserOrderInfo entity = userOrderInfoConverter.toEntity(formData);
  96. return this.save(entity);
  97. }
  98. /**
  99. * 更新用户支付订单信息
  100. *
  101. * @param id 用户支付订单信息ID
  102. * @param formData 用户支付订单信息表单对象
  103. * @return 是否修改成功
  104. */
  105. @Override
  106. public boolean updateUserOrderInfo(Long id, UserOrderInfoForm formData) {
  107. UserOrderInfo entity = userOrderInfoConverter.toEntity(formData);
  108. return this.updateById(entity);
  109. }
  110. /**
  111. * 删除用户支付订单信息
  112. *
  113. * @param ids 用户支付订单信息ID,多个以英文逗号(,)分割
  114. * @return 是否删除成功
  115. */
  116. @Override
  117. public boolean deleteUserOrderInfos(String ids) {
  118. Assert.isTrue(StrUtil.isNotBlank(ids), "删除的用户支付订单信息数据为空");
  119. // 逻辑删除
  120. List<Long> idList = Arrays.stream(ids.split(","))
  121. .map(Long::parseLong)
  122. .toList();
  123. return this.removeByIds(idList);
  124. }
  125. @Override
  126. public UserPayForm createOrder(LevelOrderForm levelOrderForm) {
  127. Long userId = SecurityUtils.getUserId();
  128. String userOpenId = userInfoService.getCurrentUserInfo().getOpenid();
  129. String orderNo = createOrderNo("SP",userId);
  130. //创建订单
  131. UserOrderInfo orderInfo = new UserOrderInfo();
  132. orderInfo.setUserId(userId);
  133. orderInfo.setOrderNo(orderNo);
  134. orderInfo.setLevelId(levelOrderForm.getLevelId());
  135. orderInfo.setOpenid(userOpenId);
  136. this.save(orderInfo);
  137. //构建支付表单返回给前端支撑JsApi支付调用
  138. UserPayForm payForm = new UserPayForm();
  139. payForm.setOrderId(orderInfo.getId()).setOrderNo(orderNo);
  140. //查询档位
  141. RechargeLevel level = rechargeLevelMapper.selectById(levelOrderForm.getLevelId());
  142. Map<String, Object> result = payment(userOpenId,orderNo,level.getMoney());
  143. payForm.setParams(result);
  144. return payForm;
  145. }
  146. @Override
  147. public UserPayForm payOrder(String orderId) {
  148. UserOrderInfo orderInfo = this.getById(orderId);
  149. //构建支付表单
  150. UserPayForm payForm = new UserPayForm();
  151. payForm.setOrderId(orderInfo.getId()).setOrderNo(orderInfo.getOrderNo());
  152. //查询档位
  153. RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
  154. Map<String, Object> result = payment(orderInfo.getOpenid(),orderInfo.getOrderNo(),level.getMoney());
  155. payForm.setParams(result);
  156. return payForm;
  157. }
  158. @Override
  159. public String orderQuery(String orderNo) {
  160. //查询订单
  161. UserOrderInfo orderInfo = this.getById(Wrappers.<UserOrderInfo>lambdaQuery().eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
  162. if (ObjectUtil.isEmpty(orderInfo)) {
  163. throw new RuntimeException("当前订单不存在");
  164. }
  165. //null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败
  166. JsonObject res = orderQueryByOutTradeNo(orderNo, WechatConstants.WECHAT_MCH_ID);
  167. String s = res == null ? null : res.get("trade_state").getAsString();
  168. // String s = "SUCCESS";
  169. if ("SUCCESS".equals(s)) {
  170. if (ObjectUtil.isNotEmpty(orderInfo) && Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_ONE)) {
  171. orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);
  172. orderInfo.setPayTime(LocalDateTime.now());
  173. orderInfo.setOutTradeNo(res.get("transaction_id").getAsString());
  174. this.updateById(orderInfo);
  175. }
  176. return "100001";//支付成功
  177. }
  178. if (s == null) {
  179. //查询订单
  180. return "100002";//查询失败
  181. }
  182. if ("USERPAYING".equals(s) || "ACCEPT".equals(s)) {
  183. //查询订单
  184. return "100003";//查询中
  185. }
  186. return "100004";//支付失败
  187. }
  188. @Override
  189. public Map<String, String> wechatPayNotify(HttpServletRequest request, HttpServletResponse response) {
  190. Map<String, String> result = new HashMap<>(2);
  191. //验签及解析返回数据
  192. JsonObject res = wechatPayV3Utils.getCallbackData(request);
  193. if (res == null) {
  194. result.put("code", "FAIL");
  195. result.put("message", "失败");
  196. return result;
  197. }
  198. log.info("最终拿到的微信支付通知数据:" + res);
  199. String orderNo = res.get("out_trade_no").getAsString();
  200. if (lock.tryLock()) {
  201. // 处理支付成功后的业务 例如 将订单状态修改为已支付 具体参数键值可参考文档 注意!!! 微信可能会多次发送重复的通知 因此要判断业务是否已经处理过了 避免重复处理
  202. try {
  203. //查询订单,判断是否已修改为已支付状态
  204. UserOrderInfo orderInfo = this.getOne(Wrappers.<UserOrderInfo>lambdaQuery().eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
  205. if (ObjectUtil.isNotEmpty(orderInfo)) {
  206. if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_TWO)) {
  207. result.put("code", "SUCCESS");
  208. result.put("message", "OK");
  209. return result;
  210. }
  211. if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_ONE)) {
  212. orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);
  213. orderInfo.setOrderType(SystemConstants.STATUS_ONE);
  214. orderInfo.setTransactionId(res.get("transaction_id").getAsString());
  215. orderInfo.setPayTime(LocalDateTime.now());
  216. // orderInfo.setPayTime(dealDateFormat(res.getString("success_time")));
  217. this.updateById(orderInfo);
  218. //todo 发送延迟消息 15分钟超时未支付
  219. //todo 异步增加积分
  220. }
  221. }
  222. result.put("code", "SUCCESS");
  223. result.put("message", "OK");
  224. return result;
  225. } catch (Exception e) {
  226. log.error("微信支付回调异常:" + e.getMessage());
  227. result.put("code", "FAIL");
  228. result.put("message", "失败");
  229. return result;
  230. }finally {
  231. lock.unlock();
  232. }
  233. }else {
  234. result.put("code", "FAIL");
  235. result.put("message", "失败");
  236. return result;
  237. }
  238. }
  239. /**
  240. * 通过商户订单号查询订单在微信侧支付状态
  241. *
  242. * @param out_trade_no 发起支付时创建的商户订单号
  243. * @return null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败
  244. */
  245. public JsonObject orderQueryByOutTradeNo(String out_trade_no, String subMchId) {
  246. String url = WechatUrlConstants.PAY_V3_QUERY_OUT;
  247. url = url.replace("{out_trade_no}", WXPayUtility.urlEncode(out_trade_no));
  248. Map<String, Object> args = new HashMap<>();
  249. args.put("sub_mchid", subMchId);
  250. url = url + "?" + WXPayUtility.urlEncode(args);
  251. return wechatPayV3Utils.sendGet(url);
  252. }
  253. /**
  254. * 构建支付表单返回给前端支撑JsApi支付调用
  255. * @param openId
  256. * @param orderNo
  257. * @param amount
  258. * @return
  259. */
  260. private Map<String, Object> payment(String openId, String orderNo,BigDecimal amount) {
  261. //15分钟超时限制
  262. Calendar calendar = Calendar.getInstance();
  263. calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 15);
  264. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
  265. //构建微信支付参数
  266. Map<String, Object> params = new HashMap<>();
  267. params.put("appid", WechatConstants.WECHAT_MP_APPID); //小程序appid
  268. params.put("mchid", WechatConstants.WECHAT_MCH_ID); //商户号
  269. params.put("description", "订单业务"); //商品描述
  270. params.put("out_trade_no", orderNo); //商户订单号
  271. params.put("time_expire", sdf.format(calendar.getTime())); //交易结束时间 选填 时间到了之后将不能再支付 遵循rfc3339标准格式
  272. params.put("attach", orderNo); //附加数据 选填
  273. // 在查询API和支付通知中原样返回 可作为自定义参数使用
  274. params.put("notify_url", WechatUrlConstants.PAY_V3_NOTIFY); //支付结果异步通知接口
  275. //订单金额信息
  276. Map<String, Object> amount_json = new HashMap<>();
  277. //支付金额 单位:分
  278. // amount_json.put("total", Integer.parseInt(amount_fee(Double.valueOf("0.1"))));测试用例
  279. amount_json.put("total", Integer.parseInt(amount_fee(amount)));
  280. params.put("amount", amount_json);
  281. //支付者信息
  282. Map<String, Object> payer = new HashMap<>();
  283. //用户在小程序侧的openid
  284. payer.put("openid", openId);
  285. params.put("payer", payer);
  286. return params;
  287. }
  288. /**
  289. * 创建商户订单号
  290. * 要求 32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
  291. * 组成 两位前缀 + 17位时间戳 + 9位id补零 + 4位随机数 合计32位
  292. *
  293. * @param head 例如 商品-SP 退款-TK 等等
  294. * @param id 用户id
  295. * @return
  296. */
  297. public String createOrderNo(String head, Long id) {
  298. StringBuilder uid = new StringBuilder(id.toString());
  299. Date date = new Date();
  300. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
  301. int length = uid.length();
  302. for (int i = 0; i < 9 - length; i++) {
  303. uid.insert(0, "0");
  304. }
  305. return head + sdf.format(date) + uid + (int) ((Math.random() * 9 + 1) * 1000);
  306. }
  307. /**
  308. * 金额元转分字符串
  309. *
  310. * @param cny 元
  311. * @return
  312. */
  313. public String amount_fee(BigDecimal cny) {
  314. BigDecimal b2 = new BigDecimal("100");
  315. return cny.multiply(b2).setScale(0, RoundingMode.DOWN).toString();
  316. }
  317. public static String amount_fee(double price) {
  318. DecimalFormat df = new DecimalFormat("#.00");
  319. price = Double.valueOf(df.format(price));
  320. int money = (int) (price * 100);
  321. return money + "";
  322. }
  323. /**
  324. * 分转元,转换为bigDecimal在转成double
  325. *
  326. * @return
  327. */
  328. public static double changeF2Y3(int price) {
  329. return BigDecimal.valueOf(Long.valueOf(price)).divide(new BigDecimal("100")).doubleValue();
  330. }
  331. }