OrderSettlementServiceImpl.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package com.kym.service.impl;
  2. import com.kym.entity.*;
  3. import com.kym.entity.awoara.OrderInfo;
  4. import com.kym.service.*;
  5. import com.kym.service.cache.KymCache;
  6. import com.kym.service.factory.DiscountStrategyFactory;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.beans.factory.annotation.Value;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.transaction.annotation.Transactional;
  11. import java.math.BigDecimal;
  12. import java.math.RoundingMode;
  13. import java.time.Duration;
  14. import java.time.LocalDateTime;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.UUID;
  18. import java.util.stream.Collectors;
  19. /**
  20. * 订单结算服务实现
  21. *
  22. * @author skyline
  23. */
  24. @Slf4j
  25. @Service
  26. public class OrderSettlementServiceImpl implements OrderSettlementService {
  27. @Value("${kym.domain}")
  28. private String DOMAIN;
  29. private final WashOrderService washOrderService;
  30. private final WalletDetailService walletDetailService;
  31. private final AccountService accountService;
  32. private final SplitRecordService splitRecordService;
  33. private final PayLogService payLogService;
  34. private final UserService userService;
  35. private final MpMsgTemplateService mpMsgTemplateService;
  36. public OrderSettlementServiceImpl(WashOrderService washOrderService, WalletDetailService walletDetailService,
  37. AccountService accountService, SplitRecordService splitRecordService,
  38. PayLogService payLogService, UserService userService,
  39. MpMsgTemplateService mpMsgTemplateService) {
  40. this.washOrderService = washOrderService;
  41. this.walletDetailService = walletDetailService;
  42. this.accountService = accountService;
  43. this.splitRecordService = splitRecordService;
  44. this.payLogService = payLogService;
  45. this.userService = userService;
  46. this.mpMsgTemplateService = mpMsgTemplateService;
  47. }
  48. @Override
  49. @Transactional
  50. public void settleOrder(WashOrder washOrder, OrderInfo orderInfo) {
  51. log.info("执行订单结算,订单:{},结算信息:{}", orderInfo.getOrder_id(), orderInfo);
  52. // 幂等保护:重新加载订单并检查是否已结算
  53. var freshOrder = washOrderService.lambdaQuery().eq(WashOrder::getId, washOrder.getId()).one();
  54. if (freshOrder != null && Integer.valueOf(WashOrder.PAY_STATUS_已支付).equals(freshOrder.getPayStatus())) {
  55. log.warn("订单:{},已结算,跳过重复结算", orderInfo.getOrder_id());
  56. return;
  57. }
  58. int rechargePayment;
  59. int grantsPayment = 0;
  60. var account = accountService.lambdaQuery().eq(Account::getUserId, washOrder.getUserId()).last("FOR UPDATE").one();
  61. int amountReceived = orderInfo.getAmount_received();
  62. if (account.getRechargeBalance() >= amountReceived) {
  63. rechargePayment = amountReceived;
  64. accountService.lambdaUpdate()
  65. .setSql("balance = balance - {0}, recharge_balance = recharge_balance - {1}",
  66. amountReceived, rechargePayment)
  67. .eq(Account::getUserId, washOrder.getUserId())
  68. .update();
  69. } else {
  70. rechargePayment = account.getRechargeBalance();
  71. grantsPayment = amountReceived - account.getRechargeBalance();
  72. accountService.lambdaUpdate()
  73. .setSql("balance = balance - {0}, recharge_balance = 0, grants_balance = grants_balance - {1}",
  74. amountReceived, grantsPayment)
  75. .eq(Account::getUserId, washOrder.getUserId())
  76. .update();
  77. }
  78. washOrder
  79. .setCloseType(orderInfo.getClose_type())
  80. .setAmount(orderInfo.getAmount())
  81. .setAmountReceivable(orderInfo.getAmount_receivable())
  82. .setAmountReceived(amountReceived)
  83. .setDiscountMoney(orderInfo.getDiscount_money())
  84. .setDetail(orderInfo.getDetail())
  85. .setEndTime(LocalDateTime.now())
  86. .setTotalSeconds(Duration.between(washOrder.getStartTime(), LocalDateTime.now()).toSeconds())
  87. .setRechargePayment(rechargePayment)
  88. .setGrantsPayment(grantsPayment)
  89. .setPayStatus(WashOrder.PAY_STATUS_已支付)
  90. .setOrderStatus(WashOrder.ORDER_STATUS_成功);
  91. washOrderService.updateById(washOrder);
  92. ensureUserStationBelonging(washOrder.getUserId(), washOrder.getStationId());
  93. backfillUnattributedRecharges(washOrder.getUserId());
  94. if (orderInfo.getAmount() == 0) {
  95. return;
  96. }
  97. if (washOrder.getIsCross()) {
  98. doCrossSplit(washOrder, KymCache.INSTANCE.getUserStationId(washOrder.getUserId()));
  99. } else {
  100. doLocalSplit(washOrder);
  101. }
  102. var walletDetail = new WalletDetail();
  103. walletDetail.setUserId(washOrder.getUserId());
  104. walletDetail.setType(WalletDetail.TYPE_消费);
  105. walletDetail.setOrderNo(washOrder.getOrderId());
  106. walletDetail.setAmount(amountReceived);
  107. walletDetail.setGrantsAmount(grantsPayment);
  108. walletDetail.setBeforeBalance(account.getBalance());
  109. walletDetail.setAfterBalance(account.getBalance() - amountReceived);
  110. walletDetail.setBeforeGrantsBalance(account.getGrantsBalance());
  111. walletDetail.setAfterGrantsBalance(account.getGrantsBalance() - grantsPayment);
  112. walletDetail.setTransactionId(washOrder.getId().toString());
  113. walletDetail.setTransactionTime(LocalDateTime.now());
  114. walletDetail.setStatus(WalletDetail.STATUS_已确认);
  115. walletDetailService.save(walletDetail);
  116. DiscountStrategyFactory.getDiscountStrategy(washOrder.getDiscountType()).computeDiscount(washOrder, account);
  117. washOrderService.updateById(washOrder);
  118. if (orderInfo.getAmount() >= KymCache.INSTANCE.getParkingCouponMinAmountByStationId(washOrder.getStationId())) {
  119. var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(washOrder.getStationId());
  120. var code = UUID.randomUUID().toString();
  121. KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L);
  122. var url = DOMAIN + "/api/parking-coupon?code=" + code;
  123. mpMsgTemplateService.sendParkingCouponMsg(washOrder, url);
  124. } else {
  125. mpMsgTemplateService.sendOrderCompletedMsg(washOrder, account.getBalance());
  126. }
  127. }
  128. @Transactional
  129. protected void doLocalSplit(WashOrder washOrder) {
  130. log.info("订单:{},本店消费,V2方案无需分账", washOrder.getOrderId());
  131. }
  132. @Transactional
  133. protected void doCrossSplit(WashOrder washOrder, String userStationId) {
  134. int rechargePayment = washOrder.getRechargePayment();
  135. int grantsPayment = washOrder.getGrantsPayment();
  136. int totalPayment = rechargePayment + grantsPayment;
  137. if (totalPayment == 0) {
  138. log.info("订单:{},支付金额为0,不产生跨店转账", washOrder.getOrderId());
  139. return;
  140. }
  141. BigDecimal crossRate = new BigDecimal("0.7");
  142. int crossAmount = crossRate.multiply(BigDecimal.valueOf(totalPayment)).setScale(0, RoundingMode.DOWN).intValue();
  143. log.info("订单:{},执行跨店分账,归属站:{},消费站:{},充值款实收:{},赠款实收:{},转账金额:{}",
  144. washOrder.getOrderId(), userStationId, washOrder.getStationId(), rechargePayment, grantsPayment, crossAmount);
  145. var crossExpend = new SplitRecord()
  146. .setFromStationId(userStationId)
  147. .setToStationId(washOrder.getStationId())
  148. .setTradeNo(washOrder.getOrderId())
  149. .setAmount(crossAmount)
  150. .setType(SplitRecord.TYPE_CROSS_EXPEND);
  151. var crossIncome = new SplitRecord()
  152. .setFromStationId(userStationId)
  153. .setToStationId(washOrder.getStationId())
  154. .setTradeNo(washOrder.getOrderId())
  155. .setAmount(crossAmount)
  156. .setType(SplitRecord.TYPE_CROSS_INCOME);
  157. splitRecordService.saveBatch(List.of(crossExpend, crossIncome));
  158. log.info("订单:{},跨店分账完成", washOrder.getOrderId());
  159. }
  160. /**
  161. * 兜底保障:如果 createOrder 的自动归属没有生效,在结算时补齐。
  162. * IoT 回调路径没有用户 Session,同步写 Redis 缓存供后续使用。
  163. */
  164. private void ensureUserStationBelonging(Long userId, String orderStationId) {
  165. var user = userService.getById(userId);
  166. if (user == null || user.getStationId() != null) {
  167. return;
  168. }
  169. userService.lambdaUpdate()
  170. .set(User::getStationId, orderStationId)
  171. .eq(User::getId, userId)
  172. .update();
  173. KymCache.INSTANCE.putUserId2StationId(Map.of(userId, orderStationId));
  174. log.info("兜底归属:用户={} 归属站点={}", userId, orderStationId);
  175. }
  176. private void backfillUnattributedRecharges(Long userId) {
  177. var userStationId = KymCache.INSTANCE.getUserStationId(userId);
  178. if (userStationId == null) {
  179. var user = userService.getById(userId);
  180. userStationId = user != null ? user.getStationId() : null;
  181. }
  182. if (userStationId == null) {
  183. return;
  184. }
  185. var walletDetails = walletDetailService.lambdaQuery()
  186. .eq(WalletDetail::getUserId, userId)
  187. .eq(WalletDetail::getType, WalletDetail.TYPE_充值)
  188. .eq(WalletDetail::getStatus, WalletDetail.STATUS_已确认)
  189. .list();
  190. if (walletDetails.isEmpty()) {
  191. return;
  192. }
  193. var outTradeNos = walletDetails.stream().map(WalletDetail::getOrderNo).toList();
  194. var payLogs = payLogService.lambdaQuery().in(PayLog::getOutTradeNo, outTradeNos).list();
  195. if (payLogs.isEmpty()) {
  196. return;
  197. }
  198. var allTxIds = payLogs.stream().map(PayLog::getTransactionId).collect(Collectors.toSet());
  199. var existingTxIds = splitRecordService.lambdaQuery()
  200. .in(SplitRecord::getTradeNo, allTxIds)
  201. .eq(SplitRecord::getType, SplitRecord.TYPE_RECHARGE)
  202. .list()
  203. .stream()
  204. .map(SplitRecord::getTradeNo)
  205. .collect(Collectors.toSet());
  206. for (var payLog : payLogs) {
  207. if (!existingTxIds.contains(payLog.getTransactionId())) {
  208. var splitRecord = new SplitRecord()
  209. .setFromStationId(userStationId)
  210. .setToStationId(userStationId)
  211. .setTradeNo(payLog.getTransactionId())
  212. .setAmount(payLog.getTotal())
  213. .setType(SplitRecord.TYPE_RECHARGE);
  214. splitRecordService.save(splitRecord);
  215. log.info("追溯补建充值分账:用户={}, 站点={}, 交易号={}, 金额={}",
  216. userId, userStationId, payLog.getTransactionId(), payLog.getTotal());
  217. }
  218. }
  219. }
  220. }