UserOrderInfoServiceImpl.java 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. package com.zsElectric.boot.business.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.aliyun.oss.ServiceException;
  5. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  6. import com.google.gson.Gson;
  7. import com.google.gson.JsonObject;
  8. import com.zsElectric.boot.business.model.entity.UserRefundsOrderInfo;
  9. import com.zsElectric.boot.business.mapper.*;
  10. import com.zsElectric.boot.business.model.dto.UserOrderInfoExportDTO;
  11. import com.zsElectric.boot.business.model.entity.*;
  12. import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
  13. import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
  14. import com.zsElectric.boot.business.model.query.applet.AppUserOrderInfoQuery;
  15. import com.zsElectric.boot.business.model.vo.applet.AppUserInfoVO;
  16. import com.zsElectric.boot.business.model.vo.applet.WechatPayParamsVO;
  17. import com.zsElectric.boot.business.service.*;
  18. import com.zsElectric.boot.common.constant.SystemConstants;
  19. import com.zsElectric.boot.core.exception.BusinessException;
  20. import com.zsElectric.boot.core.pay.*;
  21. import com.zsElectric.boot.security.util.SecurityUtils;
  22. import jakarta.servlet.http.HttpServletRequest;
  23. import jakarta.servlet.http.HttpServletResponse;
  24. import lombok.RequiredArgsConstructor;
  25. import lombok.extern.slf4j.Slf4j;
  26. import org.apache.commons.lang3.RandomStringUtils;
  27. import org.apache.commons.lang3.StringUtils;
  28. import org.springframework.stereotype.Service;
  29. import com.baomidou.mybatisplus.core.metadata.IPage;
  30. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  31. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  32. import com.zsElectric.boot.business.model.form.UserOrderInfoForm;
  33. import com.zsElectric.boot.business.model.query.UserOrderInfoQuery;
  34. import com.zsElectric.boot.business.model.vo.UserOrderInfoVO;
  35. import com.zsElectric.boot.business.converter.UserOrderInfoConverter;
  36. import java.math.BigDecimal;
  37. import java.math.RoundingMode;
  38. import java.text.DecimalFormat;
  39. import java.text.SimpleDateFormat;
  40. import java.time.LocalDateTime;
  41. import java.util.*;
  42. import java.util.concurrent.locks.ReentrantLock;
  43. import cn.hutool.core.lang.Assert;
  44. import cn.hutool.core.util.StrUtil;
  45. import org.springframework.transaction.annotation.Transactional;
  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 UserInfoMapper userInfoMapper;
  58. private final RechargeLevelMapper rechargeLevelMapper;
  59. private final UserAccountMapper userAccountMapper;
  60. private final UserAccountService userAccountService;
  61. private final WechatPayV3Utils wechatPayV3Utils;
  62. private final ChargeOrderInfoMapper chargeOrderInfoMapper;
  63. private final UserRefundsOrderInfoMapper userRefundsOrderInfoMapper;
  64. // 声明一个可重入锁
  65. private final ReentrantLock lock = new ReentrantLock();
  66. /**
  67. * 获取用户支付订单信息分页列表
  68. *
  69. * @param queryParams 查询参数
  70. * @return {@link IPage<UserOrderInfoVO>} 用户支付订单信息分页列表
  71. */
  72. @Override
  73. public IPage<UserOrderInfoVO> getUserOrderInfoPage(UserOrderInfoQuery queryParams) {
  74. Page<UserOrderInfoVO> pageVO = this.baseMapper.getUserOrderInfoPage(
  75. new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
  76. queryParams
  77. );
  78. return pageVO;
  79. }
  80. /**
  81. * 获取用户支付订单信息表单数据
  82. *
  83. * @param id 用户支付订单信息ID
  84. * @return 用户支付订单信息表单数据
  85. */
  86. @Override
  87. public UserOrderInfoForm getUserOrderInfoFormData(Long id) {
  88. UserOrderInfo entity = this.getById(id);
  89. return userOrderInfoConverter.toForm(entity);
  90. }
  91. /**
  92. * 新增用户支付订单信息
  93. *
  94. * @param formData 用户支付订单信息表单对象
  95. * @return 是否新增成功
  96. */
  97. @Override
  98. public boolean saveUserOrderInfo(UserOrderInfoForm formData) {
  99. UserOrderInfo entity = userOrderInfoConverter.toEntity(formData);
  100. return this.save(entity);
  101. }
  102. /**
  103. * 更新用户支付订单信息
  104. *
  105. * @param id 用户支付订单信息ID
  106. * @param formData 用户支付订单信息表单对象
  107. * @return 是否修改成功
  108. */
  109. @Override
  110. public boolean updateUserOrderInfo(Long id, UserOrderInfoForm formData) {
  111. UserOrderInfo entity = userOrderInfoConverter.toEntity(formData);
  112. return this.updateById(entity);
  113. }
  114. /**
  115. * 删除用户支付订单信息
  116. *
  117. * @param ids 用户支付订单信息ID,多个以英文逗号(,)分割
  118. * @return 是否删除成功
  119. */
  120. @Override
  121. public boolean deleteUserOrderInfos(String ids) {
  122. Assert.isTrue(StrUtil.isNotBlank(ids), "删除的用户支付订单信息数据为空");
  123. // 逻辑删除
  124. List<Long> idList = Arrays.stream(ids.split(","))
  125. .map(Long::parseLong)
  126. .toList();
  127. return this.removeByIds(idList);
  128. }
  129. @Override
  130. public IPage<UserOrderInfoVO> getTicketRecords(AppUserOrderInfoQuery queryParams) {
  131. UserOrderInfoQuery userOrderInfoQuery = new UserOrderInfoQuery();
  132. userOrderInfoQuery.setUserId(SecurityUtils.getUserId());
  133. Page<UserOrderInfoVO> pageVO = this.baseMapper.getUserOrderInfoPage(
  134. new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
  135. userOrderInfoQuery
  136. );
  137. return pageVO;
  138. }
  139. @Override
  140. public AppUserInfoVO getAppletUserInfo(Long userId){
  141. return userInfoMapper.getAppletUserInfo(userId);
  142. }
  143. /**
  144. * 创建订单
  145. *
  146. * @param appLevelOrderForm
  147. * @return
  148. */
  149. @Override
  150. public AppUserPayForm createOrder(AppLevelOrderForm appLevelOrderForm) {
  151. Long userId = SecurityUtils.getUserId();
  152. String userOpenId = this.getAppletUserInfo(userId).getOpenid();
  153. String orderNo = createOrderNo("SP", userId);
  154. //创建订单
  155. UserOrderInfo orderInfo = new UserOrderInfo();
  156. orderInfo.setUserId(userId);
  157. orderInfo.setOrderNo(orderNo);
  158. orderInfo.setLevelId(appLevelOrderForm.getLevelId());
  159. orderInfo.setOpenid(userOpenId);
  160. this.save(orderInfo);
  161. //构建支付表单返回给前端支撑JsApi支付调用
  162. AppUserPayForm payForm = new AppUserPayForm();
  163. payForm.setOrderId(orderInfo.getId()).setOrderNo(orderNo);
  164. //查询档位
  165. RechargeLevel level = rechargeLevelMapper.selectById(appLevelOrderForm.getLevelId());
  166. Map<String, Object> result = payment(userOpenId, orderNo, level.getMoney());
  167. payForm.setParams(result);
  168. return payForm;
  169. }
  170. /**
  171. * 支付订单
  172. *
  173. * @param orderId
  174. * @return
  175. */
  176. @Override
  177. public AppUserPayForm payOrder(String orderId) {
  178. UserOrderInfo orderInfo = this.getById(orderId);
  179. //构建支付表单
  180. AppUserPayForm payForm = new AppUserPayForm();
  181. payForm.setOrderId(orderInfo.getId()).setOrderNo(orderInfo.getOrderNo());
  182. //查询档位
  183. RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
  184. Map<String,Object> result = payment(orderInfo.getOpenid(), orderInfo.getOrderNo(), level.getMoney());
  185. payForm.setParams(result);
  186. return payForm;
  187. }
  188. /**
  189. * 订单查询
  190. *
  191. * @param orderNo
  192. * @return
  193. */
  194. @Override
  195. @Transactional(rollbackFor = Exception.class)
  196. public String orderQuery(String orderNo) throws Exception {
  197. //查询订单
  198. UserOrderInfo orderInfo = this.getById(Wrappers.<UserOrderInfo>lambdaQuery().eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
  199. if (ObjectUtil.isEmpty(orderInfo)) {
  200. throw new RuntimeException("当前订单不存在");
  201. }
  202. // 如果订单已支付,直接返回成功
  203. if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_TWO)) {
  204. return "100001";//支付成功
  205. }
  206. //null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败
  207. JsonObject res = orderQueryByOutTradeNo(orderNo, WechatConstants.WECHAT_MCH_ID);
  208. String s = res == null ? null : res.get("trade_state").getAsString();
  209. // String s = "SUCCESS";
  210. if ("SUCCESS".equals(s)) {
  211. // 重新查询订单状态,使用数据库行锁保证并发安全,避免与支付回调重复处理
  212. UserOrderInfo latestOrderInfo = this.getOne(Wrappers.<UserOrderInfo>lambdaQuery()
  213. .eq(UserOrderInfo::getOrderNo, orderNo)
  214. .last("FOR UPDATE"));
  215. // 幂等性检查:如果订单已经被支付回调处理,直接返回成功
  216. if (Objects.equals(latestOrderInfo.getOrderStatus(), SystemConstants.STATUS_TWO)) {
  217. log.info("订单已被支付回调处理,跳过重复处理: orderNo={}", orderNo);
  218. return "100001";//支付成功
  219. }
  220. if (Objects.equals(latestOrderInfo.getOrderStatus(), SystemConstants.STATUS_ONE)) {
  221. String transactionId = res.get("transaction_id").getAsString();
  222. LocalDateTime payTime = LocalDateTime.now();
  223. // 微信返回的金额单位是分,需要转换为元
  224. BigDecimal payMoney = new BigDecimal(res.get("amount").getAsJsonObject().get("total").getAsString())
  225. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  226. //执行业务操作
  227. this.successPayOrder(latestOrderInfo, transactionId, payTime, payMoney);
  228. }
  229. return "100001";//支付成功
  230. }
  231. if (s == null) {
  232. //查询订单
  233. return "100002";//查询失败
  234. }
  235. if ("USERPAYING".equals(s) || "ACCEPT".equals(s)) {
  236. //查询订单
  237. return "100003";//查询中
  238. }
  239. return "100004";//支付失败
  240. }
  241. /**
  242. * 微信支付回调
  243. *
  244. * @param request
  245. * @param response
  246. * @return
  247. */
  248. @Override
  249. public Map<String, String> wechatPayNotify(HttpServletRequest request, HttpServletResponse response) {
  250. Map<String, String> result = new HashMap<>(2);
  251. //验签及解析返回数据
  252. JsonObject res = wechatPayV3Utils.getCallbackData(request);
  253. if (res == null) {
  254. result.put("code", "FAIL");
  255. result.put("message", "失败");
  256. return result;
  257. }
  258. log.info("最终拿到的微信支付通知数据:" + res);
  259. String orderNo = res.get("out_trade_no").getAsString();
  260. if (lock.tryLock()) {
  261. // 处理支付成功后的业务 例如 将订单状态修改为已支付 具体参数键值可参考文档 注意!!! 微信可能会多次发送重复的通知 因此要判断业务是否已经处理过了 避免重复处理
  262. try {
  263. //查询订单,判断是否已修改为已支付状态
  264. UserOrderInfo orderInfo = this.getOne(Wrappers.<UserOrderInfo>lambdaQuery().eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
  265. if (ObjectUtil.isNotEmpty(orderInfo)) {
  266. if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_TWO)) {
  267. result.put("code", "SUCCESS");
  268. result.put("message", "OK");
  269. return result;
  270. }
  271. if (Objects.equals(orderInfo.getOrderStatus(), SystemConstants.STATUS_ONE)) {
  272. String transactionId = res.get("transaction_id").getAsString();
  273. LocalDateTime payTime = LocalDateTime.now();
  274. // 微信返回的金额单位是分,需要转换为元
  275. BigDecimal payMoney = new BigDecimal(res.get("amount").getAsJsonObject().get("total").getAsString())
  276. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  277. //执行业务操作
  278. this.successPayOrder(orderInfo, transactionId, payTime, payMoney);
  279. }
  280. }
  281. result.put("code", "SUCCESS");
  282. result.put("message", "OK");
  283. return result;
  284. } catch (Exception e) {
  285. log.error("微信支付回调异常:" + e.getMessage());
  286. result.put("code", "FAIL");
  287. result.put("message", "失败");
  288. return result;
  289. } finally {
  290. lock.unlock();
  291. }
  292. } else {
  293. result.put("code", "FAIL");
  294. result.put("message", "失败");
  295. return result;
  296. }
  297. }
  298. /**
  299. * 支付成功业务处理
  300. *
  301. * @param orderInfo
  302. * @param transactionId
  303. * @param payTime
  304. * @param orderMoney
  305. * @throws Exception
  306. */
  307. @Transactional(rollbackFor = Exception.class)
  308. protected void successPayOrder(UserOrderInfo orderInfo, String transactionId, LocalDateTime payTime, BigDecimal orderMoney) throws Exception {
  309. //修改订单状态为已支付
  310. orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);
  311. orderInfo.setPayTime(payTime);
  312. orderInfo.setTransactionId(transactionId);
  313. orderInfo.setPayMoney(orderMoney);
  314. this.updateById(orderInfo);
  315. //账户变动及日志记录
  316. Long userId = orderInfo.getUserId();
  317. UserAccount userAccount = userAccountService.updateAccountBalanceAndLog(
  318. userId,
  319. orderMoney,
  320. SystemConstants.CHANGE_TYPE_ADD,
  321. SystemConstants.ACCOUNT_LOG_PAY_NOTE,
  322. orderInfo.getId()
  323. );
  324. //平订单超充金额(使用更新后的账户信息)
  325. orderBackTax(userId, userAccount);
  326. }
  327. private void orderBackTax(Long userId, UserAccount userAccount) {
  328. //查询超充订单
  329. List<ChargeOrderInfo> chargeOrderInfoList = chargeOrderInfoMapper.selectList(Wrappers.<ChargeOrderInfo>lambdaQuery()
  330. .eq(ChargeOrderInfo::getUserId, userId)
  331. .eq(ChargeOrderInfo::getMaspStatus, SystemConstants.STATUS_ONE)
  332. .orderByAsc(ChargeOrderInfo::getCreateTime)
  333. );
  334. if (CollUtil.isNotEmpty(chargeOrderInfoList)) {
  335. //余额减去超充金额(使用更新后的余额)
  336. BigDecimal balance = userAccount.getBalance();
  337. for (ChargeOrderInfo chargeOrderInfo : chargeOrderInfoList) {
  338. if (balance.compareTo(BigDecimal.ZERO) > 0) {
  339. BigDecimal deductAmount; // 本次扣除金额
  340. if (balance.compareTo(chargeOrderInfo.getTotalMaspMoney()) < 0) {
  341. //余额不足,部分补缴
  342. deductAmount = balance;
  343. chargeOrderInfo.setAlreadyMaspMoney(deductAmount);
  344. chargeOrderInfo.setMaspDesc("部分补缴");
  345. chargeOrderInfo.setMaspStatus(SystemConstants.STATUS_TWO);
  346. chargeOrderInfoMapper.updateById(chargeOrderInfo);
  347. //账户变动及日志记录
  348. userAccountService.updateAccountBalanceAndLog(
  349. userId,
  350. deductAmount.negate(),
  351. SystemConstants.CHANGE_TYPE_REDUCE,
  352. SystemConstants.ACCOUNT_LOG_BACK_TAX_NOTE,
  353. chargeOrderInfo.getId()
  354. );
  355. balance = BigDecimal.ZERO;
  356. continue;
  357. }
  358. //余额足够,完成补缴
  359. deductAmount = chargeOrderInfo.getTotalMaspMoney();
  360. balance = balance.subtract(deductAmount);
  361. chargeOrderInfo.setAlreadyMaspMoney(deductAmount);
  362. chargeOrderInfo.setMaspDesc("完成补缴");
  363. chargeOrderInfo.setMaspStatus(SystemConstants.STATUS_TWO);
  364. chargeOrderInfoMapper.updateById(chargeOrderInfo);
  365. //账户变动及日志记录
  366. userAccountService.updateAccountBalanceAndLog(
  367. userId,
  368. deductAmount.negate(),
  369. SystemConstants.CHANGE_TYPE_REDUCE,
  370. SystemConstants.ACCOUNT_LOG_BACK_TAX_NOTE,
  371. chargeOrderInfo.getId()
  372. );
  373. }
  374. }
  375. }
  376. }
  377. /**
  378. * 取消订单
  379. *
  380. * @param orderId
  381. * @return
  382. */
  383. @Override
  384. public String cancelOrder(String orderId) {
  385. UserOrderInfo orderInfo = this.getById(orderId);
  386. if (ObjectUtil.isNotEmpty(orderInfo)) {
  387. if (orderInfo.getOrderStatus() == 1) {
  388. log.info("修改订单:{},支付状态为已取消", orderId);
  389. orderInfo.setOrderStatus(SystemConstants.STATUS_THREE);
  390. this.updateById(orderInfo);
  391. }
  392. return "取消成功!";
  393. }
  394. return "取消失败,请刷新后重试!";
  395. }
  396. /**
  397. * 账户退款
  398. *
  399. * @return
  400. */
  401. @Override
  402. @Transactional(rollbackFor = Exception.class)
  403. public String refundOrder() throws Exception {
  404. //查询账户余额
  405. UserAccount userAccount = userAccountMapper.selectOne(Wrappers.<UserAccount>lambdaQuery().eq(UserAccount::getUserId, SecurityUtils.getUserId()).last("limit 1"));
  406. if (userAccount.getBalance().compareTo(BigDecimal.ZERO) == 0) {
  407. return "账户余额为 0";
  408. }
  409. BigDecimal refundMoney = userAccount.getBalance();
  410. //查询一年内已支付的所有券订单
  411. List<UserOrderInfo> userOrderInfoList = baseMapper.selectList(Wrappers.<UserOrderInfo>lambdaQuery()
  412. .eq(UserOrderInfo::getUserId, userAccount.getId())
  413. .eq(UserOrderInfo::getOrderStatus, SystemConstants.STATUS_TWO)
  414. .between(UserOrderInfo::getCreateTime, LocalDateTime.now().minusYears(1), LocalDateTime.now())
  415. );
  416. if (CollUtil.isEmpty(userOrderInfoList)) {
  417. log.info("当前用户一年内未支付任何券订单,无法进行退款操作!");
  418. throw new BusinessException("无法进行退款操作,请联系客服处理!");
  419. }
  420. for (UserOrderInfo userOrderInfo : userOrderInfoList) {
  421. if(refundMoney.compareTo(BigDecimal.ZERO) == 0){
  422. break;
  423. }
  424. if ((userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney())).compareTo(refundMoney) < 0) {
  425. //退款金额大于订单金额,则直接退退款金额
  426. refundOrder(userOrderInfo,refundMoney, "账户退款", SystemConstants.STATUS_ONE);
  427. //账户变动及日志记录
  428. userAccountService.updateAccountBalanceAndLog(
  429. SecurityUtils.getUserId(),
  430. refundMoney,
  431. SystemConstants.CHANGE_TYPE_REDUCE,
  432. SystemConstants.ACCOUNT_LOG_REFUND_NOTE,
  433. userOrderInfo.getId()
  434. );
  435. //修改订单状态
  436. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FOUR);
  437. this.updateById(userOrderInfo);
  438. refundMoney = BigDecimal.ZERO;
  439. break;
  440. }
  441. if ((userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney())).compareTo(refundMoney) > 0) {
  442. //退款金额小于订单金额,则先退订单金额
  443. refundOrder(userOrderInfo,userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney()), "账户退款", SystemConstants.STATUS_ONE);
  444. //账户变动及日志记录
  445. userAccountService.updateAccountBalanceAndLog(
  446. SecurityUtils.getUserId(),
  447. userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney()),
  448. SystemConstants.CHANGE_TYPE_REDUCE,
  449. SystemConstants.ACCOUNT_LOG_REFUND_NOTE,
  450. userOrderInfo.getId()
  451. );
  452. //修改订单状态
  453. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FOUR);
  454. this.updateById(userOrderInfo);
  455. refundMoney = refundMoney.subtract(userOrderInfo.getOrderMoney());
  456. }
  457. }
  458. return "账户退款,预计3个工作日内分一笔或多笔退还!如未收到,请联系客服!";
  459. }
  460. public void refundOrder(UserOrderInfo userOrderInfo, BigDecimal refundAmount, String reason, Integer type) {
  461. log.info("进入退款接口------>");
  462. log.info("执行操作的 原支付交易对应的商户订单号:{}", userOrderInfo.getOrderNo());
  463. //退款单号
  464. String out_refund_no = createOrderNo("TK",userOrderInfo.getId());
  465. // 创建退款订单
  466. UserRefundsOrderInfo userRefundsOrderInfo = new UserRefundsOrderInfo();
  467. userRefundsOrderInfo.setOrderId(userOrderInfo.getId());
  468. userRefundsOrderInfo.setOrderNo(userOrderInfo.getOrderNo());
  469. userRefundsOrderInfo.setOutRefundNo(out_refund_no);
  470. userRefundsOrderInfo.setReason(reason);
  471. userRefundsOrderInfo.setAmount(refundAmount);
  472. userRefundsOrderInfo.setType(type);
  473. userRefundsOrderInfo.setCreateTime(LocalDateTime.now());
  474. Map<String,Object> params = new HashMap<String,Object>();
  475. params.put("transaction_id", userOrderInfo.getTransactionId());
  476. params.put("out_trade_no", userOrderInfo.getOrderNo());//商户订单号
  477. params.put("out_refund_no", out_refund_no);//商户退款单号
  478. params.put("reason", reason);//退款原因
  479. params.put("notify_url", WechatUrlConstants.PAY_V3_REFUND_NOTIFY);//退款通知
  480. Map<String,Object> amount = new HashMap<String,Object>();
  481. amount.put("refund", amount_fee(refundAmount));//退款金额
  482. amount.put("currency", "CNY");
  483. amount.put("total", amount_fee(userOrderInfo.getOrderMoney()));//原订单金额
  484. params.put("amount", amount);
  485. // 执行请求POST 请求发送到微信退款接口
  486. Gson gson = new Gson();
  487. JsonObject res = wechatPayV3Utils.sendPost(WechatUrlConstants.PAY_V3_REFUND, gson.toJsonTree(params).getAsJsonObject());
  488. log.info("最终拿到的微信支付通知数据:" + res);
  489. final String status = res.get("status").getAsString();
  490. switch (status) {
  491. case "SUCCESS":
  492. log.info("订单:{},退款成功!原因:{}", userOrderInfo.getOrderNo(), reason);
  493. //修改订单状态
  494. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FIVE);
  495. userOrderInfo.setRefundMoney(userOrderInfo.getRefundMoney().add(refundAmount));
  496. userOrderInfo.setRefundTime(LocalDateTime.now());
  497. this.updateById(userOrderInfo);
  498. break;
  499. case "CLOSED":
  500. log.info("退款关闭");
  501. break;
  502. case "PROCESSING":
  503. log.info("退款处理中");
  504. //修改订单状态
  505. userOrderInfo.setRefundMoney(userOrderInfo.getRefundMoney().add(refundAmount));
  506. this.updateById(userOrderInfo);
  507. break;
  508. case "ABNORMAL":
  509. log.info("订单:{},退款异常", userOrderInfo.getOrderNo());
  510. break;
  511. }
  512. userRefundsOrderInfo.setRefundId(res.get("refund_id").getAsString());
  513. userRefundsOrderInfo.setNotifyRequest(res.toString());
  514. userRefundsOrderInfo.setTransactionId(res.get("transaction_id").getAsString());
  515. userRefundsOrderInfo.setAcceptedTime(LocalDateTime.now());
  516. userRefundsOrderInfoMapper.insert(userRefundsOrderInfo);
  517. }
  518. @Override
  519. public Map<String, Object> refundCallback(HttpServletRequest request, HttpServletResponse response) {
  520. WechatRefundCallback refundCallback = new WechatRefundCallback() {
  521. @Override
  522. public void success(WechatCallbackRefundData refundData) {
  523. log.info("微信支付退款成功!");
  524. UserOrderInfo userOrderInfo = baseMapper.selectOne(Wrappers.lambdaQuery(UserOrderInfo.class).eq(UserOrderInfo::getOrderNo, refundData.getOrderNo()));
  525. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FIVE);
  526. userOrderInfo.setRefundTime(refundData.getSuccessTime());
  527. baseMapper.updateById(userOrderInfo);
  528. UserRefundsOrderInfo userRefundsOrderInfo = userRefundsOrderInfoMapper.selectOne(Wrappers.<UserRefundsOrderInfo>lambdaQuery()
  529. .eq(UserRefundsOrderInfo::getOrderId, userOrderInfo.getId())
  530. .eq(UserRefundsOrderInfo::getRefundId, refundData.getTransactionRefundId())
  531. .last("limit 1")
  532. );
  533. userRefundsOrderInfo.setStatus(refundData.getStatus());
  534. userRefundsOrderInfo.setSuccessTime(refundData.getSuccessTime());
  535. userRefundsOrderInfoMapper.updateById(userRefundsOrderInfo);
  536. }
  537. @Override
  538. public void fail(WechatCallbackRefundData refundData) {
  539. log.info("微信支付退款失败!");
  540. UserOrderInfo userOrderInfo = baseMapper.selectOne(Wrappers.lambdaQuery(UserOrderInfo.class).eq(UserOrderInfo::getOrderNo, refundData.getOrderNo()));
  541. UserRefundsOrderInfo userRefundsOrderInfo = userRefundsOrderInfoMapper.selectOne(Wrappers.<UserRefundsOrderInfo>lambdaQuery()
  542. .eq(UserRefundsOrderInfo::getOrderId, userOrderInfo.getId())
  543. .eq(UserRefundsOrderInfo::getRefundId, refundData.getTransactionRefundId())
  544. .last("limit 1")
  545. );
  546. userRefundsOrderInfo.setStatus(refundData.getStatus());
  547. userRefundsOrderInfo.setSuccessTime(refundData.getSuccessTime());
  548. userRefundsOrderInfoMapper.updateById(userRefundsOrderInfo);
  549. }
  550. };
  551. Map<String, Object> result = new HashMap<>();
  552. if (lock.tryLock()) {
  553. // 2.签名验证
  554. //验签及解析返回数据
  555. JsonObject res = wechatPayV3Utils.getCallbackData(request);
  556. if (res == null) {
  557. result.put("code", "FAIL");
  558. result.put("message", "失败");
  559. return result;
  560. }
  561. log.info("最终拿到的微信支付通知数据:" + res);
  562. // 4.封装微信返回的数据
  563. WechatCallbackRefundData refundData = getRefundCallbackData(res);
  564. if ("SUCCESS".equals(refundData.getStatus())) {
  565. refundCallback.success(refundData);
  566. } else {
  567. // 特殊情况退款失败业务处理,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款
  568. refundCallback.fail(refundData);
  569. }
  570. // 5.成功应答
  571. response.setStatus(200);
  572. result.put("code", "SUCCESS");
  573. result.put("message", "成功");
  574. } else {
  575. result.put("code", "FAIL");
  576. result.put("message", "失败");
  577. }
  578. return result;
  579. }
  580. private static WechatCallbackRefundData getRefundCallbackData(JsonObject res) {
  581. WechatCallbackRefundData refundData = new WechatCallbackRefundData();
  582. String successTime = res.get("success_time").getAsString();
  583. if (StringUtils.isNoneBlank(successTime)) {
  584. refundData.setSuccessTime(successTime);
  585. }
  586. refundData.setOrderNo(res.get("out_trade_no").getAsString());
  587. refundData.setRefundId(res.get("out_refund_no").getAsString());
  588. refundData.setTransactionId(res.get("transaction_id").getAsString());
  589. refundData.setTransactionRefundId(res.get("refund_id").getAsString());
  590. refundData.setChannel(res.get("channel").getAsString());
  591. final String status = res.get("refund_status").getAsString();
  592. refundData.setStatus(status);
  593. String refundMoney = res.getAsJsonObject("amount").get("refund").getAsString();
  594. refundData.setRefundMoney(new BigDecimal(refundMoney).movePointLeft(2));
  595. log.info("refundData:{}", refundData);
  596. return refundData;
  597. }
  598. /**
  599. * 通过商户订单号查询订单在微信侧支付状态
  600. *
  601. * @param out_trade_no 发起支付时创建的商户订单号
  602. * @return null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败
  603. */
  604. public JsonObject orderQueryByOutTradeNo(String out_trade_no, String subMchId) {
  605. String url = WechatUrlConstants.PAY_V3_QUERY_OUT;
  606. url = url.replace("{out_trade_no}", WXPayUtility.urlEncode(out_trade_no));
  607. Map<String, Object> args = new HashMap<>();
  608. args.put("sub_mchid", subMchId);
  609. url = url + "?" + WXPayUtility.urlEncode(args);
  610. return wechatPayV3Utils.sendGet(url);
  611. }
  612. /**
  613. * 构建支付表单返回给前端支撑JsApi支付调用
  614. *
  615. * @param openId
  616. * @param orderNo
  617. * @param amount
  618. * @return
  619. */
  620. private Map<String, Object> payment(String openId, String orderNo, BigDecimal amount) {
  621. //15分钟超时限制
  622. Calendar calendar = Calendar.getInstance();
  623. calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 15);
  624. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
  625. //构建微信支付参数
  626. Map<String, Object> params = new HashMap<>();
  627. params.put("appid", WechatConstants.WECHAT_APPID); //小程序appid
  628. params.put("mchid", WechatConstants.WECHAT_MCH_ID); //商户号
  629. params.put("description", "订单业务"); //商品描述
  630. params.put("out_trade_no", orderNo); //商户订单号
  631. params.put("time_expire", sdf.format(calendar.getTime())); //交易结束时间 选填 时间到了之后将不能再支付 遵循rfc3339标准格式
  632. params.put("attach", orderNo); //附加数据 选填
  633. // 在查询API和支付通知中原样返回 可作为自定义参数使用
  634. params.put("notify_url", WechatUrlConstants.PAY_V3_NOTIFY); //支付结果异步通知接口
  635. //订单金额信息
  636. Map<String, Object> amount_json = new HashMap<>();
  637. //支付金额 单位:分
  638. // amount_json.put("total", Integer.parseInt(amount_fee(Double.valueOf("0.1"))));测试用例
  639. amount_json.put("total", Integer.parseInt(amount_fee(amount)));
  640. params.put("amount", amount_json);
  641. //支付者信息
  642. Map<String, Object> payer = new HashMap<>();
  643. //用户在小程序侧的openid
  644. payer.put("openid", openId);
  645. params.put("payer", payer);
  646. //小程序支付拉起参数
  647. Gson gson = new Gson();
  648. return wechatPay(gson.toJsonTree(params).getAsJsonObject());
  649. }
  650. /**
  651. * 小程序支付拉起
  652. *
  653. * @param params
  654. * @return
  655. * @throws ServiceException
  656. */
  657. public Map<String, Object> wechatPay(JsonObject params) throws ServiceException {
  658. //发起请求
  659. JsonObject res = wechatPayV3Utils.sendPost(WechatUrlConstants.PAY_V3_JSAPI, params);
  660. log.info("wechatPay res:{}", res.toString());
  661. if (StrUtil.isEmpty(res.get("prepay_id").getAsString())) {
  662. throw new ServiceException("支付发起失败");
  663. }
  664. StringBuilder sb = new StringBuilder();
  665. //返回给小程序拉起微信支付的参数
  666. Map<String, Object> result = new HashMap();
  667. // result.setAppId(WechatConstants.WECHAT_APPID); //小程序appid
  668. // sb.append(result.getAppId()).append("\n");
  669. // result.setTimeStamp((new Date().getTime() / 1000) + ""); //时间戳
  670. // sb.append(result.getTimeStamp()).append("\n");
  671. // result.setNonceStr(RandomStringUtils.randomAlphanumeric(32)); //32位随机字符串
  672. // sb.append(result.getNonceStr()).append("\n");
  673. // result.setPackageValue("prepay_id=" + res.get("prepay_id").getAsString()); //预支付id 格式为 prepay_id=xxx
  674. // sb.append(result.getPackageValue()).append("\n");
  675. // //签名
  676. // result.setPaySign(wechatPayV3Utils.signRSA(sb.toString()));
  677. // result.setSignType("RSA"); //加密方式 固定RSA
  678. // result.setOutTradeNo(params.get("out_trade_no").getAsString()); //商户订单号 此参数不是小程序拉起支付所需的参数 因此不参与签名
  679. return result;
  680. }
  681. /**
  682. * 创建商户订单号
  683. * 要求 32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
  684. * 组成 两位前缀 + 17位时间戳 + 9位id补零 + 4位随机数 合计32位
  685. *
  686. * @param head 例如 商品-SP 退款-TK 等等
  687. * @param id 用户id
  688. * @return
  689. */
  690. public String createOrderNo(String head, Long id) {
  691. StringBuilder uid = new StringBuilder(id.toString());
  692. Date date = new Date();
  693. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
  694. int length = uid.length();
  695. for (int i = 0; i < 9 - length; i++) {
  696. uid.insert(0, "0");
  697. }
  698. return head + sdf.format(date) + uid + (int) ((Math.random() * 9 + 1) * 1000);
  699. }
  700. /**
  701. * 金额元转分字符串
  702. *
  703. * @param cny 元
  704. * @return
  705. */
  706. public String amount_fee(BigDecimal cny) {
  707. BigDecimal b2 = new BigDecimal("100");
  708. return cny.multiply(b2).setScale(0, RoundingMode.DOWN).toString();
  709. }
  710. public static String amount_fee(double price) {
  711. DecimalFormat df = new DecimalFormat("#.00");
  712. price = Double.valueOf(df.format(price));
  713. int money = (int) (price * 100);
  714. return money + "";
  715. }
  716. /**
  717. * 获取用户支付订单信息导出列表
  718. *
  719. * @param queryParams 查询参数
  720. * @return 用户支付订单信息导出列表
  721. */
  722. @Override
  723. public List<UserOrderInfoExportDTO> listExportUserOrderInfo(UserOrderInfoQuery queryParams) {
  724. return baseMapper.listExportUserOrderInfo(queryParams);
  725. }
  726. }