WFTOrderService.java 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. package com.zsElectric.boot.business.service;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  5. import com.google.gson.Gson;
  6. import com.google.gson.JsonObject;
  7. import com.zsElectric.boot.business.mapper.*;
  8. import com.zsElectric.boot.business.model.entity.*;
  9. import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
  10. import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
  11. import com.zsElectric.boot.business.model.vo.applet.WFTOrderVO;
  12. import com.zsElectric.boot.business.model.vo.applet.WechatPayParamsVO;
  13. import com.zsElectric.boot.common.constant.SystemConstants;
  14. import com.zsElectric.boot.core.exception.BusinessException;
  15. import com.zsElectric.boot.core.pay.WFT.WFTConstants;
  16. import com.zsElectric.boot.core.pay.WechatUrlConstants;
  17. import com.zsElectric.boot.core.pay.swiftpass.config.SwiftpassConfig;
  18. import com.zsElectric.boot.core.pay.swiftpass.util.PayUtill;
  19. import com.zsElectric.boot.core.pay.swiftpass.util.SignUtil;
  20. import com.zsElectric.boot.core.pay.swiftpass.util.SignUtils;
  21. import com.zsElectric.boot.core.pay.swiftpass.util.XmlUtils;
  22. import com.zsElectric.boot.security.util.SecurityUtils;
  23. import jakarta.annotation.Resource;
  24. import jakarta.servlet.ServletException;
  25. import jakarta.servlet.http.HttpServletRequest;
  26. import jakarta.validation.Valid;
  27. import lombok.extern.slf4j.Slf4j;
  28. import org.springframework.stereotype.Service;
  29. import org.springframework.transaction.annotation.Transactional;
  30. import java.io.IOException;
  31. import java.math.BigDecimal;
  32. import java.math.RoundingMode;
  33. import java.text.SimpleDateFormat;
  34. import java.time.LocalDateTime;
  35. import java.time.format.DateTimeFormatter;
  36. import java.util.*;
  37. /**
  38. * 威富通支付服务
  39. * 用于处理威富通JSAPI支付接口
  40. *
  41. * @author zsElectric
  42. * @since 2025-12-29
  43. */
  44. @Slf4j
  45. @Service
  46. public class WFTOrderService {
  47. @Resource
  48. private UserOrderInfoMapper userOrderInfoMapper;
  49. @Resource
  50. private UserInfoMapper userInfoMapper;
  51. @Resource
  52. private RechargeLevelMapper rechargeLevelMapper;
  53. @Resource
  54. protected SwiftpassConfig swiftpassConfig;
  55. @Resource
  56. private UserAccountService userAccountService;
  57. @Resource
  58. private ChargeOrderInfoMapper chargeOrderInfoMapper;
  59. @Resource
  60. private UserRefundsOrderInfoMapper userRefundsOrderInfoMapper;
  61. /**
  62. * 创建商户订单号
  63. * 要求 32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
  64. * 组成 两位前缀 + 17位时间戳 + 9位id补零 + 4位随机数 合计32位
  65. *
  66. * @param head 例如 商品-SP 退款-TK 等等
  67. * @param id 用户id
  68. * @return
  69. */
  70. public String createOrderNo(String head, Long id) {
  71. StringBuilder uid = new StringBuilder(id.toString());
  72. Date date = new Date();
  73. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
  74. int length = uid.length();
  75. for (int i = 0; i < 9 - length; i++) {
  76. uid.insert(0, "0");
  77. }
  78. return head + sdf.format(date) + uid + (int) ((Math.random() * 9 + 1) * 1000);
  79. }
  80. /**
  81. * 金额元转分字符串
  82. *
  83. * @param cny 元
  84. * @return
  85. */
  86. public String amount_fee(BigDecimal cny) {
  87. BigDecimal b2 = new BigDecimal("100");
  88. return cny.multiply(b2).setScale(0, RoundingMode.DOWN).toString();
  89. }
  90. /**
  91. * 金额分转元
  92. *
  93. * @param fen 分
  94. * @return 元(保留两位小数)
  95. */
  96. public BigDecimal fee_amount(Integer fen) {
  97. if (fen == null) {
  98. return BigDecimal.ZERO;
  99. }
  100. return new BigDecimal(fen).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  101. }
  102. public AppUserPayForm createOrder(@Valid AppLevelOrderForm appLevelOrderForm) {
  103. Long userId = SecurityUtils.getUserId();
  104. String userOpenId = userInfoMapper.getAppletUserInfo(userId).getOpenid();
  105. String orderNo = createOrderNo("SP", userId);
  106. //查询档位
  107. RechargeLevel level = rechargeLevelMapper.selectById(appLevelOrderForm.getLevelId());
  108. //创建订单
  109. UserOrderInfo orderInfo = new UserOrderInfo();
  110. orderInfo.setUserId(userId);
  111. orderInfo.setOrderNo(orderNo);
  112. orderInfo.setLevelId(appLevelOrderForm.getLevelId());
  113. orderInfo.setOpenid(userOpenId);
  114. orderInfo.setOrderMoney(level.getMoney());
  115. userOrderInfoMapper.insert(orderInfo);
  116. //构建支付表单返回给前端支撑JsApi支付调用
  117. AppUserPayForm payForm = new AppUserPayForm();
  118. payForm.setOrderId(orderInfo.getId()).setOrderNo(orderNo);
  119. PayUtill wx = new PayUtill();
  120. /**
  121. * 小程序支付
  122. */
  123. String payWay = "1";
  124. /**
  125. * 请求支付
  126. */
  127. try {
  128. log.debug("通知第三开始支付:");
  129. // 通知第三方支付----------------------------------------------------------
  130. SortedMap<String, String> map = new TreeMap<String, String>();
  131. // 订单编号
  132. map.put("out_trade_no", orderNo);
  133. // 商品描述
  134. map.put("body", "购买充电抵扣券");
  135. // 附加信息
  136. map.put("attach", "支付人" + userId);
  137. // pifList.get(0).setHydOrderPayMoney(new BigDecimal("0.01"));
  138. // 总金额(分)
  139. map.put("total_fee", amount_fee(level.getMoney()));
  140. // 终端ip
  141. map.put("mch_create_ip", appLevelOrderForm.getIp());
  142. // 签名方式
  143. map.put("sign_type", "RSA_1_256");
  144. // 回调地址
  145. map.put("notify_url", swiftpassConfig.getNotify_url());
  146. // 公众账号或小程序ID
  147. map.put("sub_appid", "wx9894a01b9e92c368");
  148. if (payWay.equals("1")) {// 支付渠道(1 微信 2支付宝支付 4建行支付 6微信小程序支付)
  149. // 是否小程序支付--值为1,表示小程序支付;不传或值不为1,表示公众账号内支付
  150. map.put("is_minipg", "1");
  151. map.put("sub_appid", "wx9894a01b9e92c368");
  152. } else if (payWay.equals("2")) {
  153. map.put("is_minipg", "2");
  154. }
  155. // --------微信支付请求
  156. Map<String, Object> wxMap = wx.pay(map, userOpenId, swiftpassConfig);
  157. log.info("威富通支付返回结果: {}", wxMap);
  158. if (wxMap.get("status").toString().equals("200")) {
  159. payForm.setParams(wxMap);
  160. return payForm;
  161. } else {
  162. String errorMsg = wxMap.get("msg") != null ? wxMap.get("msg").toString() : "未知错误";
  163. log.error("请求支付失败,返回结果: {}", wxMap);
  164. throw new RuntimeException("请求支付失败:" + errorMsg);
  165. }
  166. } catch (Exception e) {
  167. log.error("创建订单异常,用户ID: {}, 订单号: {}", userId, orderNo, e);
  168. throw new RuntimeException("请求支付失败:" + e.getMessage(), e);
  169. }
  170. }
  171. public AppUserPayForm payOrder(String orderId, String ip) {
  172. UserOrderInfo orderInfo = userOrderInfoMapper.selectById(orderId);
  173. //构建支付表单
  174. AppUserPayForm payForm = new AppUserPayForm();
  175. payForm.setOrderId(orderInfo.getId()).setOrderNo(orderInfo.getOrderNo());
  176. //查询档位
  177. RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
  178. PayUtill wx = new PayUtill();
  179. /**
  180. * 小程序支付
  181. */
  182. String payWay = "1";
  183. try {
  184. log.debug("通知第三开始支付:");
  185. // 通知第三方支付----------------------------------------------------------
  186. SortedMap<String, String> map = new TreeMap<String, String>();
  187. // 订单编号
  188. map.put("out_trade_no", orderInfo.getOrderNo());
  189. // 商品描述
  190. map.put("body", "购买充电抵扣券");
  191. // 附加信息
  192. map.put("attach", "支付人" + orderInfo.getUserId());
  193. // pifList.get(0).setHydOrderPayMoney(new BigDecimal("0.01"));
  194. // 总金额(分)
  195. map.put("total_fee", amount_fee(level.getMoney()));
  196. // 终端ip
  197. map.put("mch_create_ip", ip);
  198. // 签名方式
  199. map.put("sign_type", "RSA_1_256");
  200. // 回调地址
  201. map.put("notify_url", swiftpassConfig.getNotify_url());
  202. // 公众账号或小程序ID
  203. map.put("sub_appid", "wx9894a01b9e92c368");
  204. if (payWay.equals("1")) {// 支付渠道(1 微信 2支付宝支付 4建行支付 6微信小程序支付)
  205. // 是否小程序支付--值为1,表示小程序支付;不传或值不为1,表示公众账号内支付
  206. map.put("is_minipg", "1");
  207. map.put("sub_appid", "wx9894a01b9e92c368");
  208. } else if (payWay.equals("2")) {
  209. map.put("is_minipg", "2");
  210. }
  211. // --------微信支付请求
  212. Map<String, Object> wxMap = wx.pay(map, orderInfo.getOpenid(), swiftpassConfig);
  213. log.info("威富通支付返回结果: {}", wxMap);
  214. if (wxMap.get("status").toString().equals("200")) {
  215. payForm.setParams(wxMap);
  216. return payForm;
  217. } else {
  218. String errorMsg = wxMap.get("msg") != null ? wxMap.get("msg").toString() : "未知错误";
  219. log.error("支付订单失败,返回结果: {}", wxMap);
  220. throw new RuntimeException("支付失败:" + errorMsg);
  221. }
  222. } catch (Exception e) {
  223. log.error("支付订单异常,订单ID: {}", orderId, e);
  224. throw new RuntimeException("支付失败:" + e.getMessage(), e);
  225. }
  226. }
  227. public Boolean closeOrder(String orderNo) {
  228. UserOrderInfo userOrderInfo = userOrderInfoMapper.selectOne(Wrappers.lambdaQuery(UserOrderInfo.class).eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
  229. if(ObjectUtil.isNull(userOrderInfo)){
  230. throw new BusinessException("订单不存在");
  231. }
  232. userOrderInfo.setOrderStatus(SystemConstants.STATUS_THREE);
  233. int i = userOrderInfoMapper.updateById(userOrderInfo);
  234. if(i > 0){
  235. return Boolean.TRUE;
  236. }
  237. return Boolean.FALSE;
  238. }
  239. /**
  240. * 处理威富通支付通知回调
  241. *
  242. * 重要说明:
  243. * 1. 接收威富通POST的XML数据流
  244. * 2. 验证签名(支持RSA_1_256、RSA_1_1、MD5)
  245. * 3. 校验订单号和金额
  246. * 4. 幂等性处理,防止重复通知
  247. * 5. 使用数据库锁避免并发问题
  248. * 6. 更新订单状态
  249. * 7. 必须返回纯字符串"success"或"fail"
  250. *
  251. * 通知重试机制:0/15/15/30/180/1800/1800/1800/1800/3600秒
  252. *
  253. * @param request HTTP请求对象
  254. * @return "success" 表示处理成功,"fail" 表示处理失败
  255. */
  256. @Transactional(rollbackFor = Exception.class)
  257. public String handlePayNotify(HttpServletRequest request) {
  258. log.info("========== 开始处理威富通支付通知 ==========");
  259. try {
  260. // 1. 读取POST的XML数据
  261. String xmlData = XmlUtils.parseRequst(request);
  262. log.info("接收到的XML通知数据: {}", xmlData);
  263. if (xmlData == null || xmlData.trim().isEmpty()) {
  264. log.error("通知数据为空");
  265. return "fail";
  266. }
  267. // 2. 解析XML为Map
  268. Map<String, String> notifyData = XmlUtils.toMap(xmlData.getBytes("UTF-8"), "UTF-8");
  269. log.info("解析后的通知参数: {}", notifyData);
  270. // 3. 验证基本参数
  271. String status = notifyData.get("status");
  272. String resultCode = notifyData.get("result_code");
  273. String signType = notifyData.get("sign_type");
  274. String sign = notifyData.get("sign");
  275. if (status == null || resultCode == null || signType == null || sign == null) {
  276. log.error("通知参数缺失: status={}, result_code={}, sign_type={}, sign={}",
  277. status, resultCode, signType, sign);
  278. return "fail";
  279. }
  280. // 4. 验证签名
  281. boolean signValid = SignUtil.verifySign(sign, signType, notifyData, swiftpassConfig);
  282. if (!signValid) {
  283. log.error("签名验证失败");
  284. return "fail";
  285. }
  286. log.info("签名验证成功");
  287. // 5. 检查通信状态和业务结果
  288. if (!"0".equals(status)) {
  289. log.error("通信状态失败: status={}, message={}", status, notifyData.get("message"));
  290. return "fail";
  291. }
  292. if (!"0".equals(resultCode)) {
  293. log.error("业务结果失败: result_code={}, err_code={}, err_msg={}",
  294. resultCode, notifyData.get("err_code"), notifyData.get("err_msg"));
  295. return "fail";
  296. }
  297. // 6. 提取支付结果参数
  298. String payResult = notifyData.get("pay_result");
  299. if (!"0".equals(payResult)) {
  300. log.error("支付结果失败: pay_result={}, pay_info={}",
  301. payResult, notifyData.get("pay_info"));
  302. return "fail";
  303. }
  304. // 7. 提取订单相关信息
  305. String orderNo = notifyData.get("out_trade_no"); //商户订单号
  306. String outTradeNo = notifyData.get("out_transaction_id"); // 第三方订单号
  307. String transactionId = notifyData.get("transaction_id"); // 平台订单号
  308. String totalFeeStr = notifyData.get("total_fee"); // 总金额(分)
  309. BigDecimal payMoney = fee_amount(Integer.parseInt(totalFeeStr));
  310. String timeEnd = notifyData.get("time_end"); // 支付完成时间
  311. //转localDateTime
  312. LocalDateTime payTime = LocalDateTime.parse(timeEnd, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
  313. String openid = notifyData.get("openid"); // 用户标识
  314. String attach = notifyData.get("attach"); // 附加信息
  315. if (outTradeNo == null || transactionId == null || totalFeeStr == null) {
  316. log.error("订单关键参数缺失: out_trade_no={}, transaction_id={}, total_fee={}",
  317. outTradeNo, transactionId, totalFeeStr);
  318. return "fail";
  319. }
  320. log.info("订单信息: out_trade_no={}, transaction_id={}, total_fee={}, time_end={}",
  321. outTradeNo, transactionId, totalFeeStr, timeEnd);
  322. // 8. 查询订单并加锁(防止并发)
  323. UserOrderInfo orderInfo = userOrderInfoMapper.selectOne(
  324. Wrappers.lambdaQuery(UserOrderInfo.class)
  325. .eq(UserOrderInfo::getOrderNo, orderNo)
  326. .last("FOR UPDATE") // 数据库行锁
  327. );
  328. if (orderInfo == null) {
  329. log.error("订单不存在: out_trade_no={}", orderNo);
  330. return "fail";
  331. }
  332. // 9. 幂等性检查 - 如果订单已支付,直接返回成功
  333. if (SystemConstants.STATUS_TWO.equals(orderInfo.getOrderStatus())) {
  334. log.info("订单已支付,幂等性处理: out_trade_no={}, transaction_id={}",
  335. outTradeNo, orderInfo.getTransactionId());
  336. return "success";
  337. }
  338. // 10. 验证订单金额
  339. String expectedFee = amount_fee(orderInfo.getOrderMoney());
  340. if (!expectedFee.equals(totalFeeStr)) {
  341. log.error("订单金额不匹配: expected={}, actual={}", expectedFee, totalFeeStr);
  342. return "fail";
  343. }
  344. // 11. 更新订单状态
  345. orderInfo.setOrderStatus(SystemConstants.STATUS_TWO); // 已支付
  346. orderInfo.setTransactionId(transactionId); // 平台订单号
  347. orderInfo.setPayMoney(payMoney); // 支付金额
  348. orderInfo.setOutTradeNo(outTradeNo); // 第三方订单号
  349. orderInfo.setPayTime(payTime);
  350. // 解析支付时间
  351. if (timeEnd != null && timeEnd.length() == 14) {
  352. try {
  353. java.time.format.DateTimeFormatter formatter =
  354. java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
  355. orderInfo.setPayTime(java.time.LocalDateTime.parse(timeEnd, formatter));
  356. } catch (Exception e) {
  357. log.warn("解析支付时间失败: time_end={}", timeEnd, e);
  358. }
  359. }
  360. int updateCount = userOrderInfoMapper.updateById(orderInfo);
  361. if (updateCount <= 0) {
  362. log.error("更新订单状态失败: out_trade_no={}", outTradeNo);
  363. return "fail";
  364. }
  365. log.info("订单支付成功: out_trade_no={}, transaction_id={}, total_fee={}",
  366. outTradeNo, transactionId, totalFeeStr);
  367. //支付成功业务处理
  368. successPayOrder(orderInfo, transactionId, payTime, payMoney);
  369. return "success";
  370. } catch (Exception e) {
  371. log.error("处理支付通知异常", e);
  372. return "fail";
  373. } finally {
  374. log.info("========== 威富通支付通知处理完成 ==========");
  375. }
  376. }
  377. /**
  378. * 支付成功业务处理
  379. *
  380. * @param orderInfo
  381. * @param transactionId
  382. * @param payTime
  383. * @param orderMoney
  384. * @throws Exception
  385. */
  386. @Transactional(rollbackFor = Exception.class)
  387. protected void successPayOrder(UserOrderInfo orderInfo, String transactionId, LocalDateTime payTime, BigDecimal orderMoney) throws Exception {
  388. //修改订单状态为已支付
  389. orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);
  390. orderInfo.setPayTime(payTime);
  391. orderInfo.setTransactionId(transactionId);
  392. orderInfo.setPayMoney(orderMoney);
  393. userOrderInfoMapper.updateById(orderInfo);
  394. //账户变动及日志记录
  395. Long userId = orderInfo.getUserId();
  396. UserAccount userAccount = userAccountService.updateAccountBalanceAndLog(
  397. userId,
  398. orderMoney,
  399. SystemConstants.CHANGE_TYPE_ADD,
  400. SystemConstants.ACCOUNT_LOG_PAY_NOTE,
  401. orderInfo.getId()
  402. );
  403. //平订单超充金额(使用更新后的账户信息)
  404. orderBackTax(userId, userAccount);
  405. }
  406. private void orderBackTax(Long userId, UserAccount userAccount) {
  407. //查询超充订单
  408. List<ChargeOrderInfo> chargeOrderInfoList = chargeOrderInfoMapper.selectList(Wrappers.<ChargeOrderInfo>lambdaQuery()
  409. .eq(ChargeOrderInfo::getUserId, userId)
  410. .eq(ChargeOrderInfo::getMaspStatus, SystemConstants.STATUS_ONE)
  411. .orderByAsc(ChargeOrderInfo::getCreateTime)
  412. );
  413. if (CollUtil.isNotEmpty(chargeOrderInfoList)) {
  414. //余额减去超充金额(使用更新后的余额)
  415. BigDecimal balance = userAccount.getBalance();
  416. for (ChargeOrderInfo chargeOrderInfo : chargeOrderInfoList) {
  417. if (balance.compareTo(BigDecimal.ZERO) > 0) {
  418. BigDecimal deductAmount; // 本次扣除金额
  419. if (balance.compareTo(chargeOrderInfo.getTotalMaspMoney()) < 0) {
  420. //余额不足,部分补缴
  421. deductAmount = balance;
  422. chargeOrderInfo.setAlreadyMaspMoney(deductAmount);
  423. chargeOrderInfo.setMaspDesc("部分补缴");
  424. chargeOrderInfo.setMaspStatus(SystemConstants.STATUS_TWO);
  425. chargeOrderInfoMapper.updateById(chargeOrderInfo);
  426. //账户变动及日志记录
  427. userAccountService.updateAccountBalanceAndLog(
  428. userId,
  429. deductAmount.negate(),
  430. SystemConstants.CHANGE_TYPE_REDUCE,
  431. SystemConstants.ACCOUNT_LOG_BACK_TAX_NOTE,
  432. chargeOrderInfo.getId()
  433. );
  434. balance = BigDecimal.ZERO;
  435. continue;
  436. }
  437. //余额足够,完成补缴
  438. deductAmount = chargeOrderInfo.getTotalMaspMoney();
  439. balance = balance.subtract(deductAmount);
  440. chargeOrderInfo.setAlreadyMaspMoney(deductAmount);
  441. chargeOrderInfo.setMaspDesc("完成补缴");
  442. chargeOrderInfo.setMaspStatus(SystemConstants.STATUS_TWO);
  443. chargeOrderInfoMapper.updateById(chargeOrderInfo);
  444. //账户变动及日志记录
  445. userAccountService.updateAccountBalanceAndLog(
  446. userId,
  447. deductAmount.negate(),
  448. SystemConstants.CHANGE_TYPE_REDUCE,
  449. SystemConstants.ACCOUNT_LOG_BACK_TAX_NOTE,
  450. chargeOrderInfo.getId()
  451. );
  452. }
  453. }
  454. }
  455. }
  456. /**
  457. * 订单是否支付成功查询
  458. *
  459. * 应用场景:
  460. * 1. 用户支付后前端轮询查询订单状态
  461. * 2. 后台定时任务查询未支付订单
  462. * 3. 支付通知未收到时的补偿查询
  463. *
  464. * 查询策略建议:
  465. * - 方案一:以订单创建时间为基准,间隔5秒/30秒/1分钟/3分钟/5分钟/10分钟/30分钟查询
  466. * - 方案二:定时任务每30秒查询最近10分钟内未支付订单,最多查询10次
  467. *
  468. * @param orderNo 商户订单号
  469. * @return true-已支付成功,false-未支付或查询失败
  470. */
  471. public Boolean queryOrder(String orderNo) {
  472. log.info("========== 开始查询订单支付状态 ==========");
  473. log.info("查询订单号: {}", orderNo);
  474. try {
  475. // 1. 查询本地订单信息
  476. UserOrderInfo orderInfo = userOrderInfoMapper.selectOne(
  477. Wrappers.lambdaQuery(UserOrderInfo.class)
  478. .eq(UserOrderInfo::getOrderNo, orderNo)
  479. );
  480. if (orderInfo == null) {
  481. log.error("订单不存在: orderNo={}", orderNo);
  482. return false;
  483. }
  484. // 2. 如果订单已经是已支付状态,直接返回true
  485. if (SystemConstants.STATUS_TWO.equals(orderInfo.getOrderStatus())) {
  486. log.info("订单已支付: orderNo={}, transactionId={}", orderNo, orderInfo.getTransactionId());
  487. return true;
  488. }
  489. // 3. 如果订单已取消,返回false
  490. if (SystemConstants.STATUS_THREE.equals(orderInfo.getOrderStatus())) {
  491. log.info("订单已取消: orderNo={}", orderNo);
  492. return false;
  493. }
  494. // 4. 构建查询请求参数
  495. SortedMap<String, String> queryParams = new TreeMap<>();
  496. queryParams.put("out_trade_no", orderNo); // 商户订单号
  497. queryParams.put("sign_type", "RSA_1_256"); // 签名方式
  498. // 5. 调用威富通订单查询API
  499. Map<String, String> queryResult = queryOrderFromWFT(queryParams);
  500. if (!"200".equals(queryResult.get("status"))) {
  501. log.error("查询订单失败: orderNo={}, result={}", orderNo, queryResult);
  502. return false;
  503. }
  504. // 6. 解析查询结果
  505. String tradeState = queryResult.get("trade_state");
  506. String payResult = queryResult.get("pay_result");
  507. log.info("订单查询结果: orderNo={}, trade_state={}, pay_result={}",
  508. orderNo, tradeState, payResult);
  509. // 7. 判断支付状态
  510. // trade_state: SUCCESS-支付成功, NOTPAY-未支付, CLOSED-已关闭, REFUND-转入退款
  511. // pay_result: 0-成功
  512. boolean isPaid = "SUCCESS".equals(tradeState) || "0".equals(payResult);
  513. // 8. 如果支付成功但本地订单状态未更新,同步更新订单状态
  514. if (isPaid && !SystemConstants.STATUS_TWO.equals(orderInfo.getOrderStatus())) {
  515. log.info("同步更新订单支付状态: orderNo={}", orderNo);
  516. String transactionId = queryResult.get("transaction_id"); // 平台订单号
  517. String totalFeeStr = queryResult.get("total_fee"); // 总金额(分)
  518. BigDecimal payMoney = fee_amount(Integer.parseInt(totalFeeStr));
  519. String timeEnd = queryResult.get("time_end"); // 支付完成时间
  520. //转localDateTime
  521. LocalDateTime payTime = LocalDateTime.parse(timeEnd, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
  522. //支付成功业务处理
  523. successPayOrder(orderInfo, transactionId, payTime, payMoney);
  524. }
  525. return isPaid;
  526. } catch (Exception e) {
  527. log.error("查询订单支付状态异常: orderNo={}", orderNo, e);
  528. return false;
  529. } finally {
  530. log.info("========== 订单支付状态查询完成 ==========");
  531. }
  532. }
  533. /**
  534. * 调用威富通订单查询API
  535. *
  536. * @param queryParams 查询参数
  537. * @return 查询结果
  538. */
  539. private Map<String, String> queryOrderFromWFT(SortedMap<String, String> queryParams) {
  540. log.info("调用威富通订单查询API: params={}", queryParams);
  541. try {
  542. // 设置公共参数
  543. queryParams.put("service", "unified.trade.query");
  544. queryParams.put("version", "2.0");
  545. queryParams.put("charset", "UTF-8");
  546. queryParams.put("mch_id", swiftpassConfig.getMch_id());
  547. queryParams.put("nonce_str", String.valueOf(new Date().getTime()));
  548. // 生成签名
  549. Map<String, String> params = SignUtils.paraFilter(queryParams);
  550. StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
  551. SignUtils.buildPayParams(buf, params, false);
  552. String preStr = buf.toString();
  553. String signType = queryParams.get("sign_type");
  554. String sign = SignUtil.getSign(signType, preStr, swiftpassConfig);
  555. queryParams.put("sign", sign);
  556. log.info("查询请求XML: {}", XmlUtils.toXml(queryParams));
  557. // 发送HTTP请求
  558. String reqUrl = swiftpassConfig.getReq_url();
  559. org.apache.http.client.methods.HttpPost httpPost =
  560. new org.apache.http.client.methods.HttpPost(reqUrl);
  561. org.apache.http.entity.StringEntity entityParams =
  562. new org.apache.http.entity.StringEntity(XmlUtils.parseXML(queryParams), "utf-8");
  563. httpPost.setEntity(entityParams);
  564. httpPost.setHeader("Content-Type", "text/xml;utf-8");
  565. org.apache.http.impl.client.CloseableHttpClient client =
  566. org.apache.http.impl.client.HttpClients.createDefault();
  567. org.apache.http.client.methods.CloseableHttpResponse response = client.execute(httpPost);
  568. try {
  569. if (response != null && response.getEntity() != null) {
  570. // 解析响应
  571. Map<String, String> resultMap = XmlUtils.toMap(
  572. org.apache.http.util.EntityUtils.toByteArray(response.getEntity()),
  573. "utf-8");
  574. log.info("查询响应结果: {}", resultMap);
  575. // 验证签名
  576. String reSign = resultMap.get("sign");
  577. String reSignType = resultMap.get("sign_type");
  578. if (resultMap.containsKey("sign")) {
  579. boolean signValid = SignUtil.verifySign(reSign, reSignType, resultMap, swiftpassConfig);
  580. if (!signValid) {
  581. log.error("查询结果签名验证失败");
  582. resultMap.put("status", "500");
  583. resultMap.put("msg", "签名验证失败");
  584. return resultMap;
  585. }
  586. }
  587. // 检查通信状态和业务结果
  588. if ("0".equals(resultMap.get("status")) &&
  589. "0".equals(resultMap.get("result_code"))) {
  590. resultMap.put("status", "200");
  591. return resultMap;
  592. } else {
  593. log.error("查询失败: status={}, result_code={}, err_code={}, err_msg={}",
  594. resultMap.get("status"), resultMap.get("result_code"),
  595. resultMap.get("err_code"), resultMap.get("err_msg"));
  596. resultMap.put("status", "500");
  597. return resultMap;
  598. }
  599. } else {
  600. log.error("查询响应为空");
  601. Map<String, String> errorResult = new HashMap<>();
  602. errorResult.put("status", "500");
  603. errorResult.put("msg", "查询响应为空");
  604. return errorResult;
  605. }
  606. } finally {
  607. response.close();
  608. client.close();
  609. }
  610. } catch (Exception e) {
  611. log.error("调用威富通查询API异常", e);
  612. Map<String, String> errorResult = new HashMap<>();
  613. errorResult.put("status", "500");
  614. errorResult.put("msg", "系统异常: " + e.getMessage());
  615. return errorResult;
  616. }
  617. }
  618. /**
  619. * 同步订单支付状态
  620. * 当查询到订单已支付但本地状态未更新时,同步更新本地订单状态
  621. *
  622. * @param orderInfo 订单信息
  623. * @param queryResult 查询结果
  624. */
  625. @Transactional(rollbackFor = Exception.class)
  626. public void syncOrderStatus(UserOrderInfo orderInfo, Map<String, String> queryResult) {
  627. log.info("同步订单状态: orderNo={}", orderInfo.getOrderNo());
  628. try {
  629. // 提取支付信息
  630. String transactionId = queryResult.get("transaction_id");
  631. String totalFeeStr = queryResult.get("total_fee");
  632. String timeEnd = queryResult.get("time_end");
  633. // 查询档位信息验证金额
  634. RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
  635. if (level != null) {
  636. String expectedFee = amount_fee(level.getMoney());
  637. if (expectedFee.equals(totalFeeStr)) {
  638. // 更新订单状态
  639. orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);
  640. orderInfo.setTransactionId(transactionId);
  641. orderInfo.setPayMoney(level.getMoney());
  642. orderInfo.setOutTradeNo(transactionId);
  643. // 解析支付时间
  644. if (timeEnd != null && timeEnd.length() == 14) {
  645. try {
  646. java.time.format.DateTimeFormatter formatter =
  647. java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
  648. orderInfo.setPayTime(java.time.LocalDateTime.parse(timeEnd, formatter));
  649. } catch (Exception e) {
  650. log.warn("解析支付时间失败: time_end={}", timeEnd, e);
  651. }
  652. }
  653. int updateCount = userOrderInfoMapper.updateById(orderInfo);
  654. if (updateCount > 0) {
  655. log.info("订单状态同步成功: orderNo={}, transactionId={}",
  656. orderInfo.getOrderNo(), transactionId);
  657. } else {
  658. log.error("订单状态同步失败: orderNo={}", orderInfo.getOrderNo());
  659. }
  660. } else {
  661. log.error("订单金额不匹配,不同步状态: expected={}, actual={}",
  662. expectedFee, totalFeeStr);
  663. }
  664. }
  665. } catch (Exception e) {
  666. log.error("同步订单状态异常: orderNo={}", orderInfo.getOrderNo(), e);
  667. throw e;
  668. }
  669. }
  670. /**
  671. * 账户退款
  672. *
  673. * @return
  674. */
  675. @Transactional(rollbackFor = Exception.class)
  676. public String refundOrder() throws Exception {
  677. //查询账户余额
  678. UserAccount userAccount =
  679. userAccountService.getOne(Wrappers.<UserAccount>lambdaQuery().eq(UserAccount::getUserId, SecurityUtils.getUserId()).last("limit 1"));
  680. if (userAccount.getBalance().compareTo(BigDecimal.ZERO) == 0) {
  681. return "账户余额为 0";
  682. }
  683. BigDecimal refundMoney = userAccount.getBalance();
  684. //查询一年内已支付的所有券订单
  685. List<UserOrderInfo> userOrderInfoList = userOrderInfoMapper.selectList(Wrappers.<UserOrderInfo>lambdaQuery()
  686. .eq(UserOrderInfo::getOrderStatus, SystemConstants.STATUS_TWO)
  687. .between(UserOrderInfo::getCreateTime, LocalDateTime.now().minusYears(1), LocalDateTime.now())
  688. );
  689. if (CollUtil.isEmpty(userOrderInfoList)) {
  690. log.info("当前用户一年内未支付任何券订单,无法进行退款操作!");
  691. throw new BusinessException("无法进行退款操作,请联系客服处理!");
  692. }
  693. for (UserOrderInfo userOrderInfo : userOrderInfoList) {
  694. if(refundMoney.compareTo(BigDecimal.ZERO) == 0){
  695. break;
  696. }
  697. if ((userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney())).compareTo(refundMoney) > 0) {
  698. //退款金额大于订单金额,则直接退退款金额
  699. refundOrder(userOrderInfo,refundMoney, "账户退款", SystemConstants.STATUS_ONE);
  700. //账户变动及日志记录
  701. userAccountService.updateAccountBalanceAndLog(
  702. SecurityUtils.getUserId(),
  703. refundMoney,
  704. SystemConstants.CHANGE_TYPE_REDUCE,
  705. SystemConstants.ACCOUNT_LOG_REFUND_NOTE,
  706. userOrderInfo.getId()
  707. );
  708. //修改订单状态
  709. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FOUR);
  710. userOrderInfoMapper.updateById(userOrderInfo);
  711. refundMoney = BigDecimal.ZERO;
  712. break;
  713. }
  714. if ((userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney())).compareTo(refundMoney) < 0) {
  715. //退款金额小于订单金额,则先退订单金额
  716. refundOrder(userOrderInfo,userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney()), "账户退款", SystemConstants.STATUS_ONE);
  717. //账户变动及日志记录
  718. userAccountService.updateAccountBalanceAndLog(
  719. SecurityUtils.getUserId(),
  720. userOrderInfo.getOrderMoney().subtract(userOrderInfo.getRefundMoney()),
  721. SystemConstants.CHANGE_TYPE_REDUCE,
  722. SystemConstants.ACCOUNT_LOG_REFUND_NOTE,
  723. userOrderInfo.getId()
  724. );
  725. //修改订单状态
  726. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FOUR);
  727. userOrderInfoMapper.updateById(userOrderInfo);
  728. refundMoney = refundMoney.subtract(userOrderInfo.getOrderMoney());
  729. }
  730. }
  731. return "账户退款,预计3个工作日内分一笔或多笔退还!如未收到,请联系客服!";
  732. }
  733. public void refundOrder(UserOrderInfo userOrderInfo, BigDecimal refundAmount, String reason, Integer type) throws Exception {
  734. log.info("进入退款接口------>");
  735. log.info("执行操作的 原支付交易对应的商户订单号:{}", userOrderInfo.getOrderNo());
  736. //退款单号
  737. String out_refund_no = createOrderNo("TK",userOrderInfo.getId());
  738. // 创建退款订单
  739. UserRefundsOrderInfo userRefundsOrderInfo = new UserRefundsOrderInfo();
  740. userRefundsOrderInfo.setOrderId(userOrderInfo.getId());
  741. userRefundsOrderInfo.setOrderNo(userOrderInfo.getOrderNo());
  742. userRefundsOrderInfo.setOutRefundNo(out_refund_no);
  743. userRefundsOrderInfo.setReason(reason);
  744. userRefundsOrderInfo.setAmount(refundAmount);
  745. userRefundsOrderInfo.setType(type);
  746. userRefundsOrderInfo.setCreateTime(LocalDateTime.now());
  747. SortedMap<String,String> params = new TreeMap<>();
  748. params.put("out_trade_no", userOrderInfo.getOrderNo());//商户订单号
  749. params.put("out_refund_no", out_refund_no);//商户退款单号
  750. params.put("attach", reason);//退款原因
  751. params.put("total_fee", amount_fee(userOrderInfo.getOrderMoney()));//原订单金额
  752. params.put("refund_fee", amount_fee(refundAmount));//退款金额
  753. params.put("sign_type", "RSA_1_256");
  754. PayUtill payUtill = new PayUtill();
  755. Map<String, String> refund = payUtill.refund(params, swiftpassConfig);
  756. log.info("最终拿到的微信支付通知数据:" + refund);
  757. if (Objects.equals(refund.get("status"), "0")){
  758. log.info("退款调用成功!");
  759. if(Objects.equals(refund.get("result_code"), "0")){
  760. log.info("退款成功!");
  761. log.info("订单:{},退款成功!原因:{}", userOrderInfo.getOrderNo(), reason);
  762. //修改订单状态
  763. userOrderInfo.setOrderStatus(SystemConstants.STATUS_FIVE);
  764. userOrderInfo.setRefundMoney(userOrderInfo.getRefundMoney().add(refundAmount));
  765. userOrderInfo.setRefundTime(LocalDateTime.now());
  766. userOrderInfoMapper.updateById(userOrderInfo);
  767. } else{
  768. log.info("退款处理中");
  769. //修改订单状态
  770. userOrderInfo.setRefundMoney(userOrderInfo.getRefundMoney().add(refundAmount));
  771. userOrderInfoMapper.updateById(userOrderInfo);
  772. }
  773. //退款信息补充
  774. userRefundsOrderInfo.setRefundId(refund.get("refund_id"));
  775. userRefundsOrderInfo.setNotifyRequest(refund.toString());
  776. userRefundsOrderInfo.setTransactionId(userOrderInfo.getTransactionId());
  777. userRefundsOrderInfo.setAcceptedTime(LocalDateTime.now());
  778. userRefundsOrderInfoMapper.insert(userRefundsOrderInfo);
  779. }
  780. }
  781. }