ChargeOrderInfoServiceImpl.java 86 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797
  1. package com.zsElectric.boot.business.service.impl;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import cn.hutool.core.util.StrUtil;
  4. import com.baomidou.mybatisplus.core.metadata.IPage;
  5. import com.baomidou.mybatisplus.core.toolkit.Assert;
  6. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  7. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  9. import com.fasterxml.jackson.core.JsonProcessingException;
  10. import com.zsElectric.boot.business.converter.ChargeOrderInfoConverter;
  11. import com.zsElectric.boot.business.mapper.*;
  12. import com.zsElectric.boot.business.mapper.ThirdPartyStationInfoMapper;
  13. import com.zsElectric.boot.business.model.dto.ChargeOrderInfoExportDTO;
  14. import com.zsElectric.boot.business.model.entity.*;
  15. import com.zsElectric.boot.business.model.form.ChargeOrderInfoForm;
  16. import com.zsElectric.boot.business.model.form.applet.AppInvokeChargeForm;
  17. import com.zsElectric.boot.business.model.form.applet.AppStopChargeForm;
  18. import com.zsElectric.boot.business.model.query.ChargeOrderInfoQuery;
  19. import com.zsElectric.boot.business.model.query.applet.AppChargeOrderInfoQuery;
  20. import com.zsElectric.boot.business.model.vo.ChargeOrderInfoVO;
  21. import com.zsElectric.boot.business.model.vo.UserVehicleVO;
  22. import com.zsElectric.boot.business.model.vo.applet.AppChargeVO;
  23. import com.zsElectric.boot.business.model.vo.applet.AppUserInfoVO;
  24. import com.zsElectric.boot.business.service.AppletHomeService;
  25. import com.zsElectric.boot.business.service.ChargeOrderInfoService;
  26. import com.zsElectric.boot.business.service.UserAccountService;
  27. import com.zsElectric.boot.business.service.UserInfoService;
  28. import com.zsElectric.boot.charging.dto.StartChargingRequestDTO;
  29. import com.zsElectric.boot.charging.dto.StartChargingResponseVO;
  30. import com.zsElectric.boot.charging.service.ChargingBusinessService;
  31. import com.zsElectric.boot.charging.vo.EquipmentAuthResponseVO;
  32. import com.zsElectric.boot.charging.vo.StopChargingOperationResponseVO;
  33. import com.zsElectric.boot.common.constant.ConnectivityConstants;
  34. import com.zsElectric.boot.common.constant.SystemConstants;
  35. import com.zsElectric.boot.core.exception.BusinessException;
  36. import com.zsElectric.boot.security.util.SecurityUtils;
  37. import com.zsElectric.boot.system.mapper.UserMapper;
  38. import com.zsElectric.boot.system.model.entity.DictItem;
  39. import com.zsElectric.boot.system.model.entity.User;
  40. import com.zsElectric.boot.system.service.DictItemService;
  41. import lombok.RequiredArgsConstructor;
  42. import lombok.extern.slf4j.Slf4j;
  43. import org.springframework.stereotype.Service;
  44. import java.math.BigDecimal;
  45. import java.math.RoundingMode;
  46. import java.text.SimpleDateFormat;
  47. import java.time.LocalDateTime;
  48. import java.time.format.DateTimeFormatter;
  49. import java.util.*;
  50. import com.zsElectric.boot.charging.entity.ThirdPartyChargeStatus;
  51. import com.zsElectric.boot.charging.entity.ThirdPartyConnectorInfo;
  52. import com.zsElectric.boot.charging.entity.ThirdPartyStationInfo;
  53. import com.zsElectric.boot.charging.entity.ThirdPartyEquipmentPricePolicy;
  54. import com.zsElectric.boot.charging.entity.ThirdPartyPolicyInfo;
  55. import com.zsElectric.boot.charging.mapper.ThirdPartyChargeStatusMapper;
  56. import com.zsElectric.boot.charging.mapper.ThirdPartyConnectorInfoMapper;
  57. import com.zsElectric.boot.charging.mapper.ThirdPartyEquipmentPricePolicyMapper;
  58. import com.zsElectric.boot.charging.mapper.ThirdPartyPolicyInfoMapper;
  59. import com.zsElectric.boot.charging.mapper.ThirdPartyApiLogMapper;
  60. import com.zsElectric.boot.charging.entity.ThirdPartyApiLog;
  61. import com.zsElectric.boot.common.util.DateUtils;
  62. import com.fasterxml.jackson.databind.ObjectMapper;
  63. import com.fasterxml.jackson.databind.JsonNode;
  64. import org.springframework.transaction.annotation.Transactional;
  65. import org.redisson.api.RLock;
  66. import org.redisson.api.RedissonClient;
  67. import java.util.concurrent.TimeUnit;
  68. import com.zsElectric.boot.business.model.entity.FirmAccountLog;
  69. import com.zsElectric.boot.common.util.OkHttpUtil;
  70. import static com.zsElectric.boot.business.service.WFTOrderService.USER_FUND_LOCK_KEY;
  71. import static com.zsElectric.boot.business.service.WFTOrderService.USER_FUND_LOCK_EXPIRE;
  72. /**
  73. * 充电订单信息服务实现类
  74. *
  75. * @author zsElectric
  76. * @since 2025-12-17 19:13
  77. */
  78. @Slf4j
  79. @Service
  80. @RequiredArgsConstructor
  81. public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMapper, ChargeOrderInfo> implements ChargeOrderInfoService {
  82. private final ChargeOrderInfoConverter chargeOrderInfoConverter;
  83. private final ChargingBusinessService chargingBusinessService;
  84. private final ThirdPartyInfoMapper thirdPartyInfoMapper;
  85. private final UserInfoMapper userInfoMapper;
  86. private final UserInfoService userInfoService;
  87. private final FirmInfoMapper firmInfoMapper;
  88. private final CouponMapper couponMapper;
  89. private final CouponTemplateMapper couponTemplateMapper;
  90. private final UserAccountService userAccountService;
  91. private final AppletHomeService appletHomeService;
  92. private final UserMapper userMapper;
  93. private final UserVehicleMapper userVehicleMapper;
  94. private final ThirdPartyChargeStatusMapper chargeStatusMapper;
  95. private final ThirdPartyConnectorInfoMapper connectorInfoMapper;
  96. private final ThirdPartyStationInfoMapper thirdPartyStationInfoMapper;
  97. private final PolicyFeeMapper policyFeeMapper;
  98. private final ThirdPartyEquipmentPricePolicyMapper thirdPartyEquipmentPricePolicyMapper;
  99. private final ThirdPartyPolicyInfoMapper thirdPartyPolicyInfoMapper;
  100. private final DiscountsActivityMapper discountsActivityMapper;
  101. private final ThirdPartyApiLogMapper thirdPartyApiLogMapper;
  102. private final ObjectMapper objectMapper;
  103. private final DictItemService dictItemService;
  104. private final RedissonClient redissonClient;
  105. private final OkHttpUtil okHttpUtil;
  106. private final FirmAccountLogMapper firmAccountLogMapper;
  107. //充电订单号前缀
  108. private final String ORDER_NO_PREFIX = "CD";
  109. //设备流水号前缀
  110. private final String EQUIPMENT_NO_PREFIX = "SB";
  111. /**
  112. * 获取充电订单信息分页列表
  113. *
  114. * @param queryParams 查询参数
  115. * @return {@link IPage <ChargeOrderInfoVO>} 充电订单信息分页列表
  116. */
  117. @Override
  118. public IPage<ChargeOrderInfoVO> getChargeOrderInfoPage(ChargeOrderInfoQuery queryParams) {
  119. Page<ChargeOrderInfoVO> pageVO = this.baseMapper.getChargeOrderInfoPage(
  120. new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
  121. queryParams
  122. );
  123. return pageVO;
  124. }
  125. /**
  126. * 获取充电订单信息表单数据
  127. *
  128. * @param id 充电订单信息ID
  129. * @return 充电订单信息表单数据
  130. */
  131. @Override
  132. public ChargeOrderInfoForm getChargeOrderInfoFormData(Long id) {
  133. ChargeOrderInfo entity = this.getById(id);
  134. return chargeOrderInfoConverter.toForm(entity);
  135. }
  136. /**
  137. * 新增充电订单信息
  138. *
  139. * @param formData 充电订单信息表单对象
  140. * @return 是否新增成功
  141. */
  142. @Override
  143. public boolean saveChargeOrderInfo(ChargeOrderInfoForm formData) {
  144. ChargeOrderInfo entity = chargeOrderInfoConverter.toEntity(formData);
  145. return this.save(entity);
  146. }
  147. /**
  148. * 更新充电订单信息
  149. *
  150. * @param id 充电订单信息ID
  151. * @param formData 充电订单信息表单对象
  152. * @return 是否修改成功
  153. */
  154. @Override
  155. public boolean updateChargeOrderInfo(Long id,ChargeOrderInfoForm formData) {
  156. ChargeOrderInfo entity = chargeOrderInfoConverter.toEntity(formData);
  157. return this.updateById(entity);
  158. }
  159. /**
  160. * 删除充电订单信息
  161. *
  162. * @param ids 充电订单信息ID,多个以英文逗号(,)分割
  163. * @return 是否删除成功
  164. */
  165. @Override
  166. public boolean deleteChargeOrderInfos(String ids) {
  167. Assert.isTrue(StrUtil.isNotBlank(ids), "删除的充电订单信息数据为空");
  168. // 逻辑删除
  169. List<Long> idList = Arrays.stream(ids.split(","))
  170. .map(Long::parseLong)
  171. .toList();
  172. return this.removeByIds(idList);
  173. }
  174. @Override
  175. public IPage<ChargeOrderInfoVO> getPage(AppChargeOrderInfoQuery queryParams) {
  176. queryParams.setUserId(SecurityUtils.getUserId());
  177. Page<ChargeOrderInfoVO> pageVO = this.baseMapper.getPage(
  178. new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
  179. queryParams
  180. );
  181. return pageVO;
  182. }
  183. @Override
  184. public AppChargeVO invokeCharge(AppInvokeChargeForm formData) {
  185. log.info("启动充电开始,用户ID:{},设备认证流水号:{},充电桩编号:{}", SecurityUtils.getUserId(), formData.getEquipAuthSeq(), formData.getEquipmentId());
  186. //校验设备占用状态
  187. chargingBusinessService.checkEquipmentOccupancyStatus(formData.getConnectorId());
  188. // 渠道方启动充电不需要用户资金锁
  189. if (Objects.equals(formData.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_CHANNEL)) {
  190. log.info("渠道方启动充电开始,用户ID:{},设备认证流水号:{},充电桩编号:{}", SecurityUtils.getUserId(), formData.getEquipAuthSeq(), formData.getEquipmentId());
  191. try {
  192. return channelInvokeCharge(formData);
  193. } catch (Exception e) {
  194. log.error("渠道方启动充电失败", e);
  195. throw new BusinessException("启动充电失败 !" + e.getMessage());
  196. }
  197. }
  198. // 必要校验
  199. Long userId = SecurityUtils.getUserId();
  200. Assert.isTrue(userId != null, "用户ID不能为空");
  201. // 获取用户资金操作统一锁,防止充电启动与退款并发
  202. String lockKey = USER_FUND_LOCK_KEY + userId;
  203. RLock lock = redissonClient.getLock(lockKey);
  204. boolean locked = false;
  205. try {
  206. // 尝试获取锁,等待3秒,锁过期时间60秒
  207. locked = lock.tryLock(3, USER_FUND_LOCK_EXPIRE, TimeUnit.SECONDS);
  208. if (!locked) {
  209. log.warn("用户:{}资金操作正在进行中,无法启动充电", userId);
  210. throw new BusinessException("操作正在进行中,请稍后重试");
  211. }
  212. return doInvokeCharge(formData, userId);
  213. } catch (InterruptedException e) {
  214. Thread.currentThread().interrupt();
  215. throw new BusinessException("启动充电失败,请稍后重试");
  216. } finally {
  217. // 释放锁
  218. if (locked && lock.isHeldByCurrentThread()) {
  219. lock.unlock();
  220. }
  221. }
  222. }
  223. /**
  224. * 执行启动充电逻辑(内部方法)
  225. */
  226. private AppChargeVO doInvokeCharge(AppInvokeChargeForm formData, Long userId) {
  227. try {
  228. AppChargeVO appInvokeChargeVO = new AppChargeVO();
  229. AppUserInfoVO userInfo = userInfoMapper.getAppletUserInfo(userId);
  230. Assert.isTrue(userInfo != null, "用户信息不存在");
  231. //判断有没有正在进行中的订单
  232. Long count = this.baseMapper.selectCount(Wrappers.lambdaQuery(ChargeOrderInfo.class).eq(ChargeOrderInfo::getUserId, userId).in(ChargeOrderInfo::getStatus, SystemConstants.STATUS_ZERO, SystemConstants.STATUS_ONE, SystemConstants.STATUS_TWO));
  233. if (count > 0){
  234. throw new BusinessException("您有正在进行中的订单,请先停止充电");
  235. }
  236. //校验用户余额是否满足起充价(防止充值后立即退款绕过前端校验)
  237. DictItem upRecharge = dictItemService.getOne(Wrappers.<DictItem>lambdaQuery()
  238. .eq(DictItem::getDictCode, "up_recharge")
  239. .last("limit 1")
  240. );
  241. if (ObjectUtil.isNotEmpty(upRecharge)) {
  242. BigDecimal chargeFee = new BigDecimal(upRecharge.getValue());
  243. UserAccount userAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class)
  244. .eq(UserAccount::getUserId, userId)
  245. .last("limit 1"));
  246. if (userAccount == null || userAccount.getBalance().compareTo(chargeFee) < 0) {
  247. throw new BusinessException("用户余额低于起充值 " + chargeFee + " 元,请前往充值后再充电!");
  248. }
  249. }
  250. //生成系统充电订单号及互联互通充电订单号 startChargeSeq equipAuthSeq (格式"运营商ID+唯一编号")
  251. String chargeOrderNo = generateNo(ORDER_NO_PREFIX, userId);
  252. String seq = ConnectivityConstants.OPERATOR_ID + chargeOrderNo;
  253. //请求设备认证
  254. EquipmentAuthResponseVO equipmentAuthResponseVO = chargingBusinessService.queryEquipAuth(seq,
  255. formData.getConnectorId());
  256. if (Objects.equals(equipmentAuthResponseVO.getSuccStat(), SystemConstants.STATUS_ONE)) {
  257. throw new BusinessException("设备认证失败,请检查枪是否正确接入充电槽!");
  258. }else {
  259. log.info("设备认证成功,设备认证流水号:{}", equipmentAuthResponseVO.getEquipAuthSeq());
  260. }
  261. //创建订单
  262. ChargeOrderInfo chargeOrderInfo = new ChargeOrderInfo();
  263. chargeOrderInfo.setUserId(userId);
  264. chargeOrderInfo.setConnectorId(formData.getConnectorId());
  265. chargeOrderInfo.setEquipmentId(formData.getEquipmentId());
  266. chargeOrderInfo.setEquipAuthSeq(seq);
  267. chargeOrderInfo.setChargeOrderNo(chargeOrderNo);
  268. chargeOrderInfo.setStartChargeSeq(seq);
  269. chargeOrderInfo.setPhoneNum(userInfo.getPhone());
  270. chargeOrderInfo.setThirdPartyStationId(formData.getStationId());
  271. chargeOrderInfo.setOrderType(formData.getOrderType());
  272. //预支付金额
  273. BigDecimal preAmt = appletHomeService.calculateAvailableChargingAmount(formData.getConnectorId());
  274. chargeOrderInfo.setPreAmt(preAmt);
  275. //渠道方订单设置运营商ID
  276. if (Objects.equals(formData.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_CHANNEL)){
  277. chargeOrderInfo.setOperatorId(formData.getOperatorId());
  278. chargeOrderInfo.setPreAmt(formData.getChannelPreAmt());
  279. }
  280. //判断用户是否绑定企业
  281. if (ObjectUtil.isNotEmpty(userInfo.getFirmId())) {
  282. FirmInfo firmInfo = firmInfoMapper.selectById(userInfo.getFirmId());
  283. if(firmInfo != null && Objects.equals(firmInfo.getStatus(), SystemConstants.STATUS_ONE)) {
  284. chargeOrderInfo.setFirmId(firmInfo.getId());
  285. chargeOrderInfo.setOrderType(SystemConstants.STATUS_ONE);
  286. }
  287. }
  288. //优惠券
  289. if(ObjectUtil.isNotEmpty(formData.getCouponId())) {
  290. Coupon coupon = couponMapper.selectById(formData.getCouponId());
  291. if(coupon != null && Objects.equals(coupon.getStatus(), SystemConstants.STATUS_ONE)) {
  292. CouponTemplate couponTemplate = couponTemplateMapper.selectById(coupon.getTemplateId());
  293. chargeOrderInfo.setCouponId(coupon.getId());
  294. chargeOrderInfo.setCouponPrice(couponTemplate.getDiscountPrice());
  295. }
  296. }
  297. //启动充电
  298. StartChargingRequestDTO requestDTO = new StartChargingRequestDTO();
  299. requestDTO
  300. .setStartChargeSeq(seq)
  301. .setConnectorID(formData.getConnectorId())
  302. .setPhoneNum(userInfo.getPhone())
  303. //预支付金额
  304. .setChargingAmt(preAmt.toString())
  305. ;
  306. //车牌号
  307. if(ObjectUtil.isNotEmpty(formData.getPlateNum())) {
  308. chargeOrderInfo.setPlateNum(formData.getPlateNum());
  309. requestDTO.setPlateNum(formData.getPlateNum());
  310. }else {
  311. //获取当前用户的默认车牌号
  312. UserVehicle userVehicle =
  313. userVehicleMapper.selectOne(Wrappers.lambdaQuery(UserVehicle.class).eq(UserVehicle::getUserId, userId).eq(UserVehicle::getIsDefault, SystemConstants.STATUS_ONE).last("limit 1"));
  314. if (ObjectUtil.isNotEmpty(userVehicle)) {
  315. requestDTO.setPlateNum(userVehicle.getLicensePlate());
  316. }
  317. }
  318. StartChargingResponseVO startChargingResponseVO = chargingBusinessService.startCharging(requestDTO);
  319. if (!Objects.equals(startChargingResponseVO.getSuccStat(), SystemConstants.STATUS_ZERO)) {
  320. throw new BusinessException(startChargingResponseVO.getFailReasonMsg());
  321. }
  322. //保存订单
  323. this.save(chargeOrderInfo);
  324. appInvokeChargeVO.setChargeOrderNo(chargeOrderNo);
  325. appInvokeChargeVO.setChargeOrderId(chargeOrderInfo.getId());
  326. return appInvokeChargeVO;
  327. } catch (Exception e) {
  328. log.error("启动充电失败,系统错误", e);
  329. throw new BusinessException("启动充电失败 !" + e.getMessage());
  330. }
  331. }
  332. /**
  333. * 渠道方启动充电
  334. *
  335. * @param formData
  336. * @return
  337. */
  338. public AppChargeVO channelInvokeCharge(AppInvokeChargeForm formData) throws JsonProcessingException {
  339. if (StrUtil.isBlank(formData.getOperatorId())) {
  340. throw new BusinessException("运营商ID不能为空");
  341. }
  342. if (StrUtil.isBlank(formData.getChannelOrderNo())) {
  343. throw new BusinessException("渠道订单号不能为空");
  344. }
  345. if (StrUtil.isBlank(formData.getChannelUserPhone())) {
  346. throw new BusinessException("渠道用户手机号不能为空");
  347. }
  348. if (formData.getChannelPreAmt() == null || formData.getChannelPreAmt().compareTo(BigDecimal.ZERO) <= 0) {
  349. throw new BusinessException("渠道预支付金额必须大于0");
  350. }
  351. ThirdPartyInfo thirdPartyInfo = thirdPartyInfoMapper.selectOne(
  352. Wrappers.lambdaQuery(ThirdPartyInfo.class)
  353. .eq(ThirdPartyInfo::getOperatorId, formData.getOperatorId())
  354. .last("limit 1"));
  355. if (thirdPartyInfo == null) {
  356. throw new BusinessException("渠道运营商不存在");
  357. }
  358. UserInfo channelUser = userInfoService.getUserInfoByPhoneAndOperatorId(
  359. formData.getChannelUserPhone(),
  360. thirdPartyInfo.getId()
  361. );
  362. if (channelUser == null) {
  363. channelUser = userInfoService.registerThirdPartyUserByPhone(
  364. formData.getChannelUserPhone(),
  365. thirdPartyInfo.getId()
  366. );
  367. }
  368. if (channelUser == null) {
  369. throw new BusinessException("渠道用户不存在且自动注册失败");
  370. }
  371. String seq = ConnectivityConstants.OPERATOR_ID + formData.getChannelOrderNo();
  372. //请求设备认证
  373. EquipmentAuthResponseVO equipmentAuthResponseVO = chargingBusinessService.queryEquipAuth(seq,
  374. formData.getConnectorId());
  375. if (Objects.equals(equipmentAuthResponseVO.getSuccStat(), SystemConstants.STATUS_ONE)) {
  376. throw new BusinessException("设备认证失败,请检查枪是否正确接入充电槽!");
  377. } else {
  378. log.info("设备认证成功,设备认证流水号:{}", equipmentAuthResponseVO.getEquipAuthSeq());
  379. }
  380. //创建订单
  381. ChargeOrderInfo chargeOrderInfo = new ChargeOrderInfo();
  382. Long currentUserId = SecurityUtils.getUserId();
  383. chargeOrderInfo.setUserId(channelUser.getId());
  384. User user = currentUserId == null ? null : userMapper.selectById(currentUserId);
  385. if(ObjectUtil.isNotEmpty(user)){
  386. FirmInfo firmInfo = firmInfoMapper.selectOne(Wrappers.lambdaQuery(FirmInfo.class).eq(FirmInfo::getDeptId, user.getDeptId()).last("limit 1"));
  387. if(firmInfo != null) {
  388. chargeOrderInfo.setFirmId(firmInfo.getId());
  389. }
  390. }
  391. chargeOrderInfo.setOrderType(SystemConstants.CHARGE_ORDER_TYPE_CHANNEL);
  392. chargeOrderInfo.setConnectorId(formData.getConnectorId());
  393. chargeOrderInfo.setEquipmentId(formData.getEquipmentId());
  394. chargeOrderInfo.setEquipAuthSeq(seq);
  395. chargeOrderInfo.setChargeOrderNo(formData.getChannelOrderNo());
  396. chargeOrderInfo.setStartChargeSeq(seq);
  397. chargeOrderInfo.setPhoneNum(formData.getChannelUserPhone());
  398. chargeOrderInfo.setThirdPartyStationId(formData.getStationId());
  399. //渠道手机号
  400. if(ObjectUtil.isNotEmpty(formData.getChannelUserPhone())){
  401. chargeOrderInfo.setPhoneNum(formData.getChannelUserPhone());
  402. }
  403. //预支付金额
  404. chargeOrderInfo.setPreAmt(formData.getChannelPreAmt());
  405. //渠道方订单设置运营商ID
  406. chargeOrderInfo.setOperatorId(formData.getOperatorId());
  407. //启动充电
  408. StartChargingRequestDTO requestDTO = new StartChargingRequestDTO();
  409. requestDTO
  410. .setStartChargeSeq(seq)
  411. .setConnectorID(formData.getConnectorId())
  412. .setPhoneNum(formData.getChannelUserPhone())
  413. //预支付金额
  414. .setChargingAmt(formData.getChannelPreAmt().toString());
  415. //车牌号
  416. if(ObjectUtil.isNotEmpty(formData.getPlateNum())) {
  417. chargeOrderInfo.setPlateNum(formData.getPlateNum());
  418. requestDTO.setPlateNum(formData.getPlateNum());
  419. }
  420. StartChargingResponseVO startChargingResponseVO = chargingBusinessService.startCharging(requestDTO);
  421. if (!Objects.equals(startChargingResponseVO.getSuccStat(), SystemConstants.STATUS_ZERO)) {
  422. throw new BusinessException(startChargingResponseVO.getFailReasonMsg());
  423. }
  424. //保存订单
  425. this.save(chargeOrderInfo);
  426. AppChargeVO appInvokeChargeVO = new AppChargeVO();
  427. appInvokeChargeVO.setChargeOrderId(chargeOrderInfo.getId());
  428. appInvokeChargeVO.setChargeOrderNo(chargeOrderInfo.getChargeOrderNo());
  429. appInvokeChargeVO.setStatus(SystemConstants.STATUS_ZERO);
  430. return appInvokeChargeVO;
  431. }
  432. @Override
  433. public AppChargeVO stopCharge(AppStopChargeForm formData) {
  434. try {
  435. AppChargeVO appInvokeChargeVO = new AppChargeVO();
  436. //订单
  437. ChargeOrderInfo chargeOrderInfo = this.getOne(Wrappers.lambdaQuery(ChargeOrderInfo.class)
  438. .eq(ChargeOrderInfo::getChargeOrderNo, formData.getChargeOrderNo())
  439. .eq(StrUtil.isNotBlank(formData.getOperatorId()), ChargeOrderInfo::getOperatorId, formData.getOperatorId())
  440. .last("limit 1"));
  441. if(ObjectUtil.isEmpty(chargeOrderInfo)){
  442. throw new BusinessException("订单不存在");
  443. }
  444. StopChargingOperationResponseVO stopChargingOperationResponseVO = chargingBusinessService.stopCharging(chargeOrderInfo.getStartChargeSeq(), chargeOrderInfo.getConnectorId());
  445. if (!Objects.equals(stopChargingOperationResponseVO.getSuccStat(), SystemConstants.STATUS_ZERO)) {
  446. throw new BusinessException("停止充电失败,请稍后再重试!");
  447. }
  448. chargeOrderInfo.setStopType(1); // 1-主动停止
  449. this.updateById(chargeOrderInfo);
  450. appInvokeChargeVO.setChargeOrderId(chargeOrderInfo.getId());
  451. appInvokeChargeVO.setChargeOrderNo(chargeOrderInfo.getChargeOrderNo());
  452. return appInvokeChargeVO;
  453. } catch (Exception e) {
  454. log.error("停止充电失败,系统错误", e);
  455. throw new BusinessException("停止充电失败,请稍后再重试!");
  456. }
  457. }
  458. @Override
  459. public void orderSettlement(Long chargeOrderId) {
  460. ChargeOrderInfo chargeOrderInfo = this.getById(chargeOrderId);
  461. Long userId = chargeOrderInfo.getUserId();
  462. //平台收费总金额
  463. BigDecimal totalCharge = chargeOrderInfo.getRealCost();
  464. //加积分
  465. userAccountService.update(Wrappers.lambdaUpdate(UserAccount.class)
  466. .eq(UserAccount::getUserId, userId)
  467. .setSql("`integral` = `integral` +" + totalCharge)
  468. );
  469. //账户变动及日志记录
  470. userAccountService.updateAccountBalanceAndLog(
  471. userId,
  472. totalCharge,
  473. SystemConstants.CHANGE_TYPE_REDUCE,
  474. SystemConstants.CHARGE_DEDUCT_NOTE,
  475. chargeOrderInfo.getId()
  476. );
  477. }
  478. @Override
  479. public ChargeOrderInfoVO queryOrder(String chargeOrderNo) {
  480. return baseMapper.queryOrder(chargeOrderNo);
  481. }
  482. /**
  483. * 获取充电订单信息导出列表
  484. *
  485. * @param queryParams 查询参数
  486. * @return 充电订单信息导出列表
  487. */
  488. @Override
  489. public List<ChargeOrderInfoExportDTO> listExportChargeOrderInfo(ChargeOrderInfoQuery queryParams) {
  490. return baseMapper.listExportChargeOrderInfo(queryParams);
  491. }
  492. /**
  493. * 创建商户订单号
  494. * 要求 32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
  495. * 组成 两位前缀 + 17位时间戳 + 9位id补零 + 4位随机数 合计32位
  496. *
  497. * @param head 例如 商品-SP 退款-TK 等等
  498. * @param id 用户id
  499. * @return
  500. */
  501. public String generateNo(String head, Long id) {
  502. StringBuilder uid = new StringBuilder(id.toString());
  503. Date date = new Date();
  504. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
  505. int length = uid.length();
  506. for (int i = 0; i < 9 - length; i++) {
  507. uid.insert(0, "0");
  508. }
  509. return head + sdf.format(date) + uid + (int) ((Math.random() * 9 + 1) * 1000);
  510. }
  511. /**
  512. * 修复未处理的充电订单
  513. * 优先通过third_party_api_log表获取推送充电订单信息,
  514. * 若没有则通过third_party_charge_status表查询total_power>0的数据
  515. * 重新处理这些订单:更新状态、计算费用、扣减余额
  516. */
  517. @Override
  518. @Transactional(rollbackFor = Exception.class)
  519. public String repairUnprocessedOrders() {
  520. log.info("开始修复未处理的充电订单...");
  521. // 1. 查询状态为5(未成功充电)的订单
  522. List<ChargeOrderInfo> unprocessedOrders = this.list(Wrappers.<ChargeOrderInfo>lambdaQuery()
  523. .eq(ChargeOrderInfo::getStatus, 5)
  524. .isNotNull(ChargeOrderInfo::getStartChargeSeq));
  525. if (unprocessedOrders.isEmpty()) {
  526. log.info("没有找到需要修复的订单");
  527. return "没有找到需要修复的订单";
  528. }
  529. int totalCount = 0;
  530. int successCount = 0;
  531. int skipCount = 0;
  532. int apiLogCount = 0;
  533. int chargeStatusCount = 0;
  534. List<String> failedOrders = new ArrayList<>();
  535. for (ChargeOrderInfo order : unprocessedOrders) {
  536. totalCount++;
  537. try {
  538. // 2. 优先从third_party_api_log表查询推送充电订单信息
  539. ThirdPartyApiLog apiLog = thirdPartyApiLogMapper.selectOne(
  540. Wrappers.<ThirdPartyApiLog>lambdaQuery()
  541. .eq(ThirdPartyApiLog::getInterfaceDescription, "推送充电订单信息")
  542. .like(ThirdPartyApiLog::getDecryptedRequestData, order.getStartChargeSeq())
  543. .orderByDesc(ThirdPartyApiLog::getCreatedTime)
  544. .last("LIMIT 1"));
  545. if (apiLog != null && apiLog.getDecryptedRequestData() != null) {
  546. // 使用API日志中的推送数据处理订单
  547. boolean success = processOrderFromApiLog(order, apiLog);
  548. if (success) {
  549. // 设置补偿状态为已补偿
  550. order.setCompensateStatus(1);
  551. this.updateById(order);
  552. successCount++;
  553. apiLogCount++;
  554. log.info("订单{}通过API日志修复成功", order.getChargeOrderNo());
  555. continue;
  556. }
  557. }
  558. // 3. 备选方案:从third_party_charge_status表查询
  559. ThirdPartyChargeStatus chargeStatus = chargeStatusMapper.selectOne(
  560. Wrappers.<ThirdPartyChargeStatus>lambdaQuery()
  561. .eq(ThirdPartyChargeStatus::getStartChargeSeq, order.getStartChargeSeq()));
  562. if (chargeStatus == null || chargeStatus.getTotalPower() == null
  563. || chargeStatus.getTotalPower().compareTo(BigDecimal.ZERO) <= 0) {
  564. log.info("订单{}无有效充电数据,设置为异常无须补偿", order.getChargeOrderNo());
  565. // 设置补偿状态为异常无须补偿
  566. order.setCompensateStatus(2);
  567. this.updateById(order);
  568. skipCount++;
  569. continue;
  570. }
  571. log.info("开始通过充电状态表修复订单: {}, 充电量: {}", order.getChargeOrderNo(), chargeStatus.getTotalPower());
  572. // 4. 获取站点信息
  573. ThirdPartyConnectorInfo connectorInfo = connectorInfoMapper.selectOne(
  574. Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  575. .eq(ThirdPartyConnectorInfo::getConnectorId, order.getConnectorId())
  576. .last("LIMIT 1"));
  577. if (connectorInfo == null) {
  578. log.warn("订单{}找不到充电接口信息", order.getChargeOrderNo());
  579. failedOrders.add(order.getChargeOrderNo());
  580. continue;
  581. }
  582. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  583. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  584. .eq(ThirdPartyStationInfo::getStationId, connectorInfo.getStationId())
  585. .last("LIMIT 1"));
  586. if (stationInfo == null) {
  587. log.warn("订单{}找不到站点信息", order.getChargeOrderNo());
  588. failedOrders.add(order.getChargeOrderNo());
  589. continue;
  590. }
  591. // 5. 设置第三方费用信息
  592. order.setTotalCharge(chargeStatus.getTotalPower());
  593. order.setThirdPartyTotalCost(chargeStatus.getTotalMoney() != null ? chargeStatus.getTotalMoney() : BigDecimal.ZERO);
  594. order.setThirdPartyServerfee(chargeStatus.getServiceMoney() != null ? chargeStatus.getServiceMoney() : BigDecimal.ZERO);
  595. order.setThirdPartyElecfee(chargeStatus.getElecMoney() != null ? chargeStatus.getElecMoney() : BigDecimal.ZERO);
  596. if (StrUtil.isNotBlank(chargeStatus.getChargeDetails())) {
  597. order.setChargeDetails(chargeStatus.getChargeDetails());
  598. }
  599. // 6. 计算平台服务费
  600. BigDecimal serviceFee = calculateServiceFee(order, chargeStatus, stationInfo);
  601. // 7. 更新订单信息
  602. order.setRealServiceCost(serviceFee.setScale(2, RoundingMode.HALF_UP));
  603. order.setRealCost(serviceFee.add(order.getThirdPartyTotalCost()));
  604. order.setStatus(SystemConstants.STATUS_THREE); // 已完成
  605. // 设置充电时间
  606. if (chargeStatus.getStartTime() != null && chargeStatus.getEndTime() != null) {
  607. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  608. order.setStartTime(chargeStatus.getStartTime().format(formatter));
  609. order.setEndTime(chargeStatus.getEndTime().format(formatter));
  610. order.setChargeTime(DateUtils.getDuration(order.getStartTime(), order.getEndTime()));
  611. }
  612. // 设置修复备注
  613. order.setRemark("通过补偿修复处理");
  614. // 设置补偿状态为已补偿
  615. order.setCompensateStatus(1);
  616. this.updateById(order);
  617. // 8. 执行账户余额扣减(仅平台订单和企业订单)
  618. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_PLATFORM)
  619. || Objects.equals(order.getOrderType(), SystemConstants.STATUS_ONE)) {
  620. orderSettlement(order.getId());
  621. log.info("订单{}余额扣减完成", order.getChargeOrderNo());
  622. }
  623. successCount++;
  624. chargeStatusCount++;
  625. log.info("订单{}通过充电状态表修复成功,实际费用: {}", order.getChargeOrderNo(), order.getRealCost());
  626. } catch (Exception e) {
  627. log.error("修复订单{}失败: {}", order.getChargeOrderNo(), e.getMessage(), e);
  628. failedOrders.add(order.getChargeOrderNo());
  629. }
  630. }
  631. String result = String.format("修复完成!总计: %d, 成功: %d (API日志: %d, 充电状态: %d), 跳过: %d, 失败: %d",
  632. totalCount, successCount, apiLogCount, chargeStatusCount, skipCount, failedOrders.size());
  633. if (!failedOrders.isEmpty()) {
  634. result += ", 失败订单: " + String.join(",", failedOrders);
  635. }
  636. log.info(result);
  637. return result;
  638. }
  639. /**
  640. * 通过API日志中的推送数据处理订单
  641. * @param order 订单信息
  642. * @param apiLog API日志
  643. * @return 是否处理成功
  644. */
  645. private boolean processOrderFromApiLog(ChargeOrderInfo order, ThirdPartyApiLog apiLog) {
  646. try {
  647. JsonNode jsonNode = objectMapper.readTree(apiLog.getDecryptedRequestData());
  648. // 解析推送数据
  649. String startChargeSeq = getJsonTextValue(jsonNode, "StartChargeSeq");
  650. if (startChargeSeq == null || !startChargeSeq.equals(order.getStartChargeSeq())) {
  651. log.warn("订单{}的StartChargeSeq不匹配,跳过API日志处理", order.getChargeOrderNo());
  652. return false;
  653. }
  654. String totalPowerStr = getJsonTextValue(jsonNode, "TotalPower");
  655. if (totalPowerStr == null || new BigDecimal(totalPowerStr).compareTo(BigDecimal.ZERO) <= 0) {
  656. log.info("订单{}的API日志充电量为0,跳过处理", order.getChargeOrderNo());
  657. return false;
  658. }
  659. log.info("开始通过API日志修复订单: {}, 充电量: {}", order.getChargeOrderNo(), totalPowerStr);
  660. // 设置充电信息
  661. order.setTotalCharge(new BigDecimal(totalPowerStr));
  662. order.setStopReason(getJsonTextValue(jsonNode, "StopReason"));
  663. order.setStartTime(getJsonTextValue(jsonNode, "StartTime"));
  664. order.setEndTime(getJsonTextValue(jsonNode, "EndTime"));
  665. order.setChargeDetails(jsonNode.toString());
  666. // 第三方费用
  667. String totalMoney = getJsonTextValue(jsonNode, "TotalMoney");
  668. String totalSeviceMoney = getJsonTextValue(jsonNode, "TotalSeviceMoney");
  669. String totalElecMoney = getJsonTextValue(jsonNode, "TotalElecMoney");
  670. order.setThirdPartyTotalCost(totalMoney != null ? new BigDecimal(totalMoney) : BigDecimal.ZERO);
  671. order.setThirdPartyServerfee(totalSeviceMoney != null ? new BigDecimal(totalSeviceMoney) : BigDecimal.ZERO);
  672. order.setThirdPartyElecfee(totalElecMoney != null ? new BigDecimal(totalElecMoney) : BigDecimal.ZERO);
  673. // 获取连接器和站点信息
  674. String connectorId = getJsonTextValue(jsonNode, "ConnectorID");
  675. if (connectorId == null) {
  676. connectorId = order.getConnectorId();
  677. }
  678. ThirdPartyConnectorInfo connectorInfo = connectorInfoMapper.selectOne(
  679. Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  680. .eq(ThirdPartyConnectorInfo::getConnectorId, connectorId)
  681. .last("LIMIT 1"));
  682. if (connectorInfo == null) {
  683. log.warn("订单{}找不到充电接口信息", order.getChargeOrderNo());
  684. return false;
  685. }
  686. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  687. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  688. .eq(ThirdPartyStationInfo::getStationId, connectorInfo.getStationId())
  689. .last("LIMIT 1"));
  690. if (stationInfo == null) {
  691. log.warn("订单{}找不到站点信息", order.getChargeOrderNo());
  692. return false;
  693. }
  694. // 计算平台服务费(从ChargeDetails中解析)
  695. BigDecimal serviceFee = calculateServiceFeeFromChargeDetails(order, jsonNode, stationInfo);
  696. // 更新订单信息
  697. order.setRealServiceCost(serviceFee.setScale(2, RoundingMode.HALF_UP));
  698. order.setRealCost(serviceFee.add(order.getThirdPartyTotalCost()));
  699. order.setStatus(SystemConstants.STATUS_THREE); // 已完成
  700. // 计算充电时间
  701. if (order.getStartTime() != null && order.getEndTime() != null) {
  702. order.setChargeTime(DateUtils.getDuration(order.getStartTime(), order.getEndTime()));
  703. }
  704. // 设置修复备注
  705. order.setRemark("通过补偿修复处理");
  706. this.updateById(order);
  707. // 执行账户余额扣减(仅平台订单和企业订单)
  708. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_PLATFORM)
  709. || Objects.equals(order.getOrderType(), SystemConstants.STATUS_ONE)) {
  710. orderSettlement(order.getId());
  711. log.info("订单{}余额扣减完成", order.getChargeOrderNo());
  712. }
  713. log.info("订单{}通过API日志处理成功,实际费用: {}", order.getChargeOrderNo(), order.getRealCost());
  714. return true;
  715. } catch (Exception e) {
  716. log.error("通过API日志处理订单{}失败: {}", order.getChargeOrderNo(), e.getMessage(), e);
  717. return false;
  718. }
  719. }
  720. /**
  721. * 从ChargeDetails计算平台服务费
  722. */
  723. private BigDecimal calculateServiceFeeFromChargeDetails(ChargeOrderInfo order, JsonNode jsonNode,
  724. ThirdPartyStationInfo stationInfo) {
  725. BigDecimal serviceFee = BigDecimal.ZERO;
  726. JsonNode chargeDetails = jsonNode.get("ChargeDetails");
  727. if (chargeDetails != null && chargeDetails.isArray()) {
  728. for (JsonNode node : chargeDetails) {
  729. String itemFlag = getJsonTextValue(node, "ItemFlag");
  730. String detailPowerStr = getJsonTextValue(node, "DetailPower");
  731. if (itemFlag != null && detailPowerStr != null) {
  732. BigDecimal detailPower = new BigDecimal(detailPowerStr);
  733. PolicyFee policyFee = policyFeeMapper.selectOne(Wrappers.<PolicyFee>lambdaQuery()
  734. .eq(PolicyFee::getStationInfoId, stationInfo.getId())
  735. .eq(PolicyFee::getPeriodFlag, Integer.parseInt(itemFlag))
  736. .last("LIMIT 1"));
  737. if (policyFee != null && policyFee.getOpFee() != null) {
  738. serviceFee = serviceFee.add(policyFee.getOpFee().multiply(detailPower));
  739. }
  740. }
  741. }
  742. } else {
  743. // 无明细时,使用最高费用时段计算
  744. log.info("订单{}无充电明细,使用简化计算", order.getChargeOrderNo());
  745. ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
  746. Wrappers.<ThirdPartyEquipmentPricePolicy>lambdaQuery()
  747. .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, order.getConnectorId())
  748. .last("LIMIT 1"));
  749. if (pricePolicy != null) {
  750. List<ThirdPartyPolicyInfo> policyInfos = thirdPartyPolicyInfoMapper.selectList(
  751. Wrappers.<ThirdPartyPolicyInfo>lambdaQuery()
  752. .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
  753. .eq(ThirdPartyPolicyInfo::getIsDeleted, 0));
  754. PolicyFee maxPolicyFee = null;
  755. for (ThirdPartyPolicyInfo policyInfo : policyInfos) {
  756. PolicyFee policyFee = policyFeeMapper.selectOne(Wrappers.<PolicyFee>lambdaQuery()
  757. .eq(PolicyFee::getStationInfoId, stationInfo.getId())
  758. .eq(PolicyFee::getPeriodFlag, policyInfo.getPeriodFlag())
  759. .last("LIMIT 1"));
  760. if (policyFee != null && policyFee.getOpFee() != null) {
  761. if (maxPolicyFee == null || policyFee.getOpFee().compareTo(maxPolicyFee.getOpFee()) > 0) {
  762. maxPolicyFee = policyFee;
  763. }
  764. }
  765. }
  766. if (maxPolicyFee != null) {
  767. serviceFee = maxPolicyFee.getOpFee().multiply(order.getTotalCharge());
  768. }
  769. }
  770. }
  771. return serviceFee;
  772. }
  773. /**
  774. * 安全获取JSON节点的文本值
  775. */
  776. private String getJsonTextValue(JsonNode node, String fieldName) {
  777. if (node == null || !node.has(fieldName) || node.get(fieldName).isNull()) {
  778. return null;
  779. }
  780. return node.get(fieldName).asText();
  781. }
  782. /**
  783. * 计算平台服务费
  784. */
  785. private BigDecimal calculateServiceFee(ChargeOrderInfo order, ThirdPartyChargeStatus chargeStatus,
  786. ThirdPartyStationInfo stationInfo) {
  787. BigDecimal serviceFee = BigDecimal.ZERO;
  788. // 查询价格策略
  789. ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
  790. Wrappers.<ThirdPartyEquipmentPricePolicy>lambdaQuery()
  791. .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, chargeStatus.getConnectorId())
  792. .last("LIMIT 1"));
  793. if (pricePolicy == null) {
  794. log.warn("订单{}找不到价格策略", order.getChargeOrderNo());
  795. return serviceFee;
  796. }
  797. // 查询时段信息
  798. List<ThirdPartyPolicyInfo> allPolicyInfos = thirdPartyPolicyInfoMapper.selectList(
  799. Wrappers.<ThirdPartyPolicyInfo>lambdaQuery()
  800. .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
  801. .eq(ThirdPartyPolicyInfo::getIsDeleted, 0)
  802. .orderByAsc(ThirdPartyPolicyInfo::getStartTime));
  803. if (allPolicyInfos.isEmpty()) {
  804. log.warn("订单{}找不到时段信息", order.getChargeOrderNo());
  805. return serviceFee;
  806. }
  807. // 根据充电时间段匹配费用
  808. if (chargeStatus.getStartTime() != null && chargeStatus.getEndTime() != null) {
  809. String chargeStartTimeStr = chargeStatus.getStartTime().format(DateTimeFormatter.ofPattern("HHmmss"));
  810. String chargeEndTimeStr = chargeStatus.getEndTime().format(DateTimeFormatter.ofPattern("HHmmss"));
  811. // 找到充电时间跨越的所有时段,使用最高费用时段计算
  812. PolicyFee maxPolicyFee = null;
  813. for (ThirdPartyPolicyInfo policyInfo : allPolicyInfos) {
  814. if (chargeStartTimeStr.compareTo(policyInfo.getStartTime()) >= 0
  815. || chargeEndTimeStr.compareTo(policyInfo.getStartTime()) >= 0) {
  816. PolicyFee policyFee = policyFeeMapper.selectOne(
  817. Wrappers.<PolicyFee>lambdaQuery()
  818. .eq(PolicyFee::getStationInfoId, stationInfo.getId())
  819. .eq(PolicyFee::getPeriodFlag, policyInfo.getPeriodFlag())
  820. .last("LIMIT 1"));
  821. if (policyFee != null && policyFee.getOpFee() != null) {
  822. if (maxPolicyFee == null || policyFee.getOpFee().compareTo(maxPolicyFee.getOpFee()) > 0) {
  823. maxPolicyFee = policyFee;
  824. }
  825. }
  826. }
  827. }
  828. if (maxPolicyFee != null) {
  829. serviceFee = maxPolicyFee.getOpFee().multiply(order.getTotalCharge());
  830. }
  831. }
  832. return serviceFee;
  833. }
  834. /**
  835. * 根据充电订单号修复已完成订单
  836. * 修复状态为3(已完成)的订单,重新处理订单状态变更、数据修改及余额扣减
  837. * 优先通过third_party_api_log表获取推送数据,备选通过third_party_charge_status表查询
  838. */
  839. @Override
  840. @Transactional(rollbackFor = Exception.class)
  841. public String repairOrderByOrderNo(String chargeOrderNo) {
  842. log.info("开始根据订单号修复订单: {}", chargeOrderNo);
  843. // 1. 查询订单
  844. ChargeOrderInfo order = this.getOne(Wrappers.<ChargeOrderInfo>lambdaQuery()
  845. .eq(ChargeOrderInfo::getChargeOrderNo, chargeOrderNo)
  846. .last("LIMIT 1"));
  847. if (order == null) {
  848. return "订单不存在: " + chargeOrderNo;
  849. }
  850. log.info("开始修复订单: {}, 当前状态: {}", chargeOrderNo, order.getStatus());
  851. // 2. 检查订单是否需要修复(只有充电度数、平台费用、三方费用同时为0扏null的订单才处理)
  852. boolean needRepair = isZeroOrNull(order.getTotalCharge())
  853. && isZeroOrNull(order.getRealCost())
  854. && isZeroOrNull(order.getRealServiceCost())
  855. && isZeroOrNull(order.getThirdPartyTotalCost())
  856. && isZeroOrNull(order.getThirdPartyServerfee());
  857. if (!needRepair) {
  858. return "订单" + chargeOrderNo + "已有充电数据,无需修复。充电度数:" + order.getTotalCharge()
  859. + ", 平台收取金额:" + order.getRealCost()
  860. + ", 平台服务费:" + order.getRealServiceCost()
  861. + ", 三方消费总额:" + order.getThirdPartyTotalCost()
  862. + ", 三方服务费:" + order.getThirdPartyServerfee();
  863. }
  864. if (order.getStartChargeSeq() == null) {
  865. return "订单缺少StartChargeSeq,无法修复";
  866. }
  867. try {
  868. // 3. 优先从third_party_api_log表查询推送充电订单信息
  869. ThirdPartyApiLog apiLog = thirdPartyApiLogMapper.selectOne(
  870. Wrappers.<ThirdPartyApiLog>lambdaQuery()
  871. .eq(ThirdPartyApiLog::getInterfaceDescription, "推送充电订单信息")
  872. .like(ThirdPartyApiLog::getDecryptedRequestData, order.getStartChargeSeq())
  873. .orderByDesc(ThirdPartyApiLog::getCreatedTime)
  874. .last("LIMIT 1"));
  875. if (apiLog != null && apiLog.getDecryptedRequestData() != null) {
  876. // 使用API日志中的推送数据处理订单
  877. boolean success = repairOrderFromApiLogByOrderNo(order, apiLog);
  878. if (success) {
  879. // 设置补偿状态为已补偿
  880. order.setCompensateStatus(1);
  881. order.setStatus(3);
  882. this.updateById(order);
  883. log.info("订单{}通过API日志修复成功", chargeOrderNo);
  884. return "订单" + chargeOrderNo + "通过API日志修复成功,实际费用: " + order.getRealCost();
  885. }
  886. }
  887. // 4. 备选方案:从third_party_charge_status表查询
  888. ThirdPartyChargeStatus chargeStatus = chargeStatusMapper.selectOne(
  889. Wrappers.<ThirdPartyChargeStatus>lambdaQuery()
  890. .eq(ThirdPartyChargeStatus::getStartChargeSeq, order.getStartChargeSeq()));
  891. if (chargeStatus == null || chargeStatus.getTotalPower() == null
  892. || chargeStatus.getTotalPower().compareTo(BigDecimal.ZERO) <= 0) {
  893. // 设置补偿状态为异常无须补偿
  894. order.setCompensateStatus(2);
  895. order.setStatus(5);
  896. this.updateById(order);
  897. return "订单" + chargeOrderNo + "无有效充电数据,设置为异常无须补偿";
  898. }
  899. log.info("开始通过充电状态表修复订单: {}, 充电量: {}", chargeOrderNo, chargeStatus.getTotalPower());
  900. // 5. 获取站点信息
  901. ThirdPartyConnectorInfo connectorInfo = connectorInfoMapper.selectOne(
  902. Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  903. .eq(ThirdPartyConnectorInfo::getConnectorId, order.getConnectorId())
  904. .last("LIMIT 1"));
  905. if (connectorInfo == null) {
  906. return "订单" + chargeOrderNo + "找不到充电接口信息";
  907. }
  908. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  909. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  910. .eq(ThirdPartyStationInfo::getStationId, connectorInfo.getStationId())
  911. .last("LIMIT 1"));
  912. if (stationInfo == null) {
  913. return "订单" + chargeOrderNo + "找不到站点信息";
  914. }
  915. // 6. 设置第三方费用信息
  916. order.setTotalCharge(chargeStatus.getTotalPower());
  917. order.setThirdPartyTotalCost(chargeStatus.getTotalMoney() != null ? chargeStatus.getTotalMoney() : BigDecimal.ZERO);
  918. order.setThirdPartyServerfee(chargeStatus.getServiceMoney() != null ? chargeStatus.getServiceMoney() : BigDecimal.ZERO);
  919. order.setThirdPartyElecfee(chargeStatus.getElecMoney() != null ? chargeStatus.getElecMoney() : BigDecimal.ZERO);
  920. if (StrUtil.isNotBlank(chargeStatus.getChargeDetails())) {
  921. order.setChargeDetails(chargeStatus.getChargeDetails());
  922. }
  923. // 7. 计算平台服务费
  924. BigDecimal serviceFee = calculateServiceFee(order, chargeStatus, stationInfo);
  925. // 8. 更新订单信息
  926. order.setRealServiceCost(serviceFee.setScale(2, RoundingMode.HALF_UP));
  927. order.setRealCost(serviceFee.add(order.getThirdPartyTotalCost()));
  928. // 设置充电时间
  929. if (chargeStatus.getStartTime() != null && chargeStatus.getEndTime() != null) {
  930. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  931. order.setStartTime(chargeStatus.getStartTime().format(formatter));
  932. order.setEndTime(chargeStatus.getEndTime().format(formatter));
  933. order.setChargeTime(DateUtils.getDuration(order.getStartTime(), order.getEndTime()));
  934. }
  935. // 设置修复备注
  936. order.setRemark("通过补偿修复处理");
  937. // 设置补偿状态为已补偿
  938. order.setCompensateStatus(1);
  939. order.setStatus(3);
  940. this.updateById(order);
  941. // 9. 执行账户余额扣减(仅平台订单和企业订单)
  942. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_PLATFORM)
  943. || Objects.equals(order.getOrderType(), SystemConstants.STATUS_ONE)) {
  944. orderSettlement(order.getId());
  945. log.info("订单{}余额扣减完成", chargeOrderNo);
  946. }
  947. log.info("订单{}通过充电状态表修复成功,实际费用: {}", chargeOrderNo, order.getRealCost());
  948. return "订单" + chargeOrderNo + "通过充电状态表修复成功,实际费用: " + order.getRealCost();
  949. } catch (Exception e) {
  950. log.error("修复订单{}失败: {}", chargeOrderNo, e.getMessage(), e);
  951. throw new BusinessException("修复订单失败: " + e.getMessage());
  952. }
  953. }
  954. /**
  955. * 通过API日志修复单个订单(根据订单号)
  956. * @param order 订单信息
  957. * @param apiLog API日志
  958. * @return 是否处理成功
  959. */
  960. private boolean repairOrderFromApiLogByOrderNo(ChargeOrderInfo order, ThirdPartyApiLog apiLog) {
  961. try {
  962. JsonNode jsonNode = objectMapper.readTree(apiLog.getDecryptedRequestData());
  963. // 解析推送数据
  964. String startChargeSeq = getJsonTextValue(jsonNode, "StartChargeSeq");
  965. if (startChargeSeq == null || !startChargeSeq.equals(order.getStartChargeSeq())) {
  966. log.warn("订单{}的StartChargeSeq不匹配,跳过API日志处理", order.getChargeOrderNo());
  967. return false;
  968. }
  969. String totalPowerStr = getJsonTextValue(jsonNode, "TotalPower");
  970. if (totalPowerStr == null || new BigDecimal(totalPowerStr).compareTo(BigDecimal.ZERO) <= 0) {
  971. log.info("订单{}的API日志充电量为0,跳过处理", order.getChargeOrderNo());
  972. return false;
  973. }
  974. log.info("开始通过API日志修复订单: {}, 充电量: {}", order.getChargeOrderNo(), totalPowerStr);
  975. // 设置充电信息
  976. order.setTotalCharge(new BigDecimal(totalPowerStr));
  977. order.setStopReason(getJsonTextValue(jsonNode, "StopReason"));
  978. order.setStartTime(getJsonTextValue(jsonNode, "StartTime"));
  979. order.setEndTime(getJsonTextValue(jsonNode, "EndTime"));
  980. order.setChargeDetails(jsonNode.toString());
  981. // 第三方费用
  982. String totalMoney = getJsonTextValue(jsonNode, "TotalMoney");
  983. String totalSeviceMoney = getJsonTextValue(jsonNode, "TotalSeviceMoney");
  984. String totalElecMoney = getJsonTextValue(jsonNode, "TotalElecMoney");
  985. order.setThirdPartyTotalCost(totalMoney != null ? new BigDecimal(totalMoney) : BigDecimal.ZERO);
  986. order.setThirdPartyServerfee(totalSeviceMoney != null ? new BigDecimal(totalSeviceMoney) : BigDecimal.ZERO);
  987. order.setThirdPartyElecfee(totalElecMoney != null ? new BigDecimal(totalElecMoney) : BigDecimal.ZERO);
  988. // 获取连接器和站点信息
  989. String connectorId = getJsonTextValue(jsonNode, "ConnectorID");
  990. if (connectorId == null) {
  991. connectorId = order.getConnectorId();
  992. }
  993. ThirdPartyConnectorInfo connectorInfo = connectorInfoMapper.selectOne(
  994. Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  995. .eq(ThirdPartyConnectorInfo::getConnectorId, connectorId)
  996. .last("LIMIT 1"));
  997. if (connectorInfo == null) {
  998. log.warn("订单{}找不到充电接口信息", order.getChargeOrderNo());
  999. return false;
  1000. }
  1001. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  1002. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  1003. .eq(ThirdPartyStationInfo::getStationId, connectorInfo.getStationId())
  1004. .last("LIMIT 1"));
  1005. if (stationInfo == null) {
  1006. log.warn("订单{}找不到站点信息", order.getChargeOrderNo());
  1007. return false;
  1008. }
  1009. // 计算平台服务费(从ChargeDetails中解析)
  1010. BigDecimal serviceFee = calculateServiceFeeFromChargeDetails(order, jsonNode, stationInfo);
  1011. // 更新订单信息
  1012. order.setRealServiceCost(serviceFee.setScale(2, RoundingMode.HALF_UP));
  1013. order.setRealCost(serviceFee.add(order.getThirdPartyTotalCost()));
  1014. // 计算充电时间
  1015. if (order.getStartTime() != null && order.getEndTime() != null) {
  1016. order.setChargeTime(DateUtils.getDuration(order.getStartTime(), order.getEndTime()));
  1017. }
  1018. // 设置修复备注
  1019. order.setRemark("通过补偿修复处理");
  1020. this.updateById(order);
  1021. // 执行账户余额扣减(仅平台订单和企业订单)
  1022. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_PLATFORM)
  1023. || Objects.equals(order.getOrderType(), SystemConstants.STATUS_ONE)) {
  1024. orderSettlement(order.getId());
  1025. log.info("订单{}余额扣减完成", order.getChargeOrderNo());
  1026. }
  1027. // 渠道方订单:推送充电订单信息 + 渠道方账户余额扣减
  1028. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_CHANNEL)) {
  1029. compensateChannelOrder(order, apiLog.getDecryptedRequestData());
  1030. }
  1031. log.info("订单{}通过API日志处理成功,实际费用: {}", order.getChargeOrderNo(), order.getRealCost());
  1032. return true;
  1033. } catch (Exception e) {
  1034. log.error("通过API日志处理订单{}失败: {}", order.getChargeOrderNo(), e.getMessage(), e);
  1035. return false;
  1036. }
  1037. }
  1038. /**
  1039. * 判断BigDecimal是否为null或0
  1040. * @param value 要判断的值
  1041. * @return 如果为null或0返回true,否则返回false
  1042. */
  1043. private boolean isZeroOrNull(BigDecimal value) {
  1044. return value == null || value.compareTo(BigDecimal.ZERO) == 0;
  1045. }
  1046. /**
  1047. * 补偿未处理的充电订单(定时任务调用)
  1048. * 查找状态为3(已完成)或5(未成功充电)且充电数据为0的订单
  1049. */
  1050. @Override
  1051. @Transactional(rollbackFor = Exception.class)
  1052. public String compensateUnprocessedOrders() {
  1053. log.info("开始执行充电订单补偿定时任务...");
  1054. // 1. 查询状态为3(已完成)或5(未成功充电)且充电数据为0的订单
  1055. List<ChargeOrderInfo> unprocessedOrders = this.list(Wrappers.<ChargeOrderInfo>lambdaQuery()
  1056. .in(ChargeOrderInfo::getStatus, 3, 5)
  1057. .isNotNull(ChargeOrderInfo::getStartChargeSeq)
  1058. // 充电度数为0或null
  1059. .and(wrapper -> wrapper
  1060. .isNull(ChargeOrderInfo::getTotalCharge)
  1061. .or()
  1062. .eq(ChargeOrderInfo::getTotalCharge, BigDecimal.ZERO))
  1063. // 平台实际收取金额为0或null
  1064. .and(wrapper -> wrapper
  1065. .isNull(ChargeOrderInfo::getRealCost)
  1066. .or()
  1067. .eq(ChargeOrderInfo::getRealCost, BigDecimal.ZERO))
  1068. // 平台总服务费为0或null
  1069. .and(wrapper -> wrapper
  1070. .isNull(ChargeOrderInfo::getRealServiceCost)
  1071. .or()
  1072. .eq(ChargeOrderInfo::getRealServiceCost, BigDecimal.ZERO))
  1073. // 三方充电消费总额为0或null
  1074. .and(wrapper -> wrapper
  1075. .isNull(ChargeOrderInfo::getThirdPartyTotalCost)
  1076. .or()
  1077. .eq(ChargeOrderInfo::getThirdPartyTotalCost, BigDecimal.ZERO))
  1078. // 三方充电服务费为0或null
  1079. .and(wrapper -> wrapper
  1080. .isNull(ChargeOrderInfo::getThirdPartyServerfee)
  1081. .or()
  1082. .eq(ChargeOrderInfo::getThirdPartyServerfee, BigDecimal.ZERO))
  1083. // 补偿状态为0或null
  1084. .and(wrapper -> wrapper
  1085. .isNull(ChargeOrderInfo::getCompensateStatus)
  1086. .or()
  1087. .eq(ChargeOrderInfo::getCompensateStatus, 0))
  1088. );
  1089. if (unprocessedOrders.isEmpty()) {
  1090. log.info("充电订单补偿定时任务: 没有找到需要补偿的订单");
  1091. return "没有找到需要补偿的订单";
  1092. }
  1093. log.info("充电订单补偿定时任务: 找到{}个需要补偿的订单", unprocessedOrders.size());
  1094. int totalCount = 0;
  1095. int successCount = 0;
  1096. int skipCount = 0;
  1097. int apiLogCount = 0;
  1098. int chargeStatusCount = 0;
  1099. List<String> failedOrders = new ArrayList<>();
  1100. for (ChargeOrderInfo order : unprocessedOrders) {
  1101. totalCount++;
  1102. try {
  1103. // 2. 优先从third_party_api_log表查询推送充电订单信息
  1104. ThirdPartyApiLog apiLog = thirdPartyApiLogMapper.selectOne(
  1105. Wrappers.<ThirdPartyApiLog>lambdaQuery()
  1106. .eq(ThirdPartyApiLog::getInterfaceDescription, "推送充电订单信息")
  1107. .like(ThirdPartyApiLog::getDecryptedRequestData, order.getStartChargeSeq())
  1108. .orderByDesc(ThirdPartyApiLog::getCreatedTime)
  1109. .last("LIMIT 1"));
  1110. if (apiLog != null && apiLog.getDecryptedRequestData() != null) {
  1111. // 使用API日志中的推送数据处理订单
  1112. boolean success = processOrderFromApiLog(order, apiLog);
  1113. if (success) {
  1114. // 设置补偿状态为已补偿
  1115. order.setCompensateStatus(1);
  1116. this.updateById(order);
  1117. successCount++;
  1118. apiLogCount++;
  1119. log.info("补偿任务: 订单{}通过API日志补偿成功", order.getChargeOrderNo());
  1120. continue;
  1121. }
  1122. }
  1123. // 3. 备选方案:从third_party_charge_status表查询
  1124. ThirdPartyChargeStatus chargeStatus = chargeStatusMapper.selectOne(
  1125. Wrappers.<ThirdPartyChargeStatus>lambdaQuery()
  1126. .eq(ThirdPartyChargeStatus::getStartChargeSeq, order.getStartChargeSeq()));
  1127. if (chargeStatus == null || chargeStatus.getTotalPower() == null
  1128. || chargeStatus.getTotalPower().compareTo(BigDecimal.ZERO) <= 0) {
  1129. log.info("补偿任务: 订单{}无有效充电数据,设置为异常无须补偿", order.getChargeOrderNo());
  1130. // 设置补偿状态为异常无须补偿
  1131. order.setCompensateStatus(2);
  1132. this.updateById(order);
  1133. skipCount++;
  1134. continue;
  1135. }
  1136. log.info("补偿任务: 开始通过充电状态表补偿订单: {}, 充电量: {}", order.getChargeOrderNo(), chargeStatus.getTotalPower());
  1137. // 4. 获取站点信息
  1138. ThirdPartyConnectorInfo connectorInfo = connectorInfoMapper.selectOne(
  1139. Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  1140. .eq(ThirdPartyConnectorInfo::getConnectorId, order.getConnectorId())
  1141. .last("LIMIT 1"));
  1142. if (connectorInfo == null) {
  1143. log.warn("补偿任务: 订单{}找不到充电接口信息", order.getChargeOrderNo());
  1144. failedOrders.add(order.getChargeOrderNo());
  1145. continue;
  1146. }
  1147. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  1148. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  1149. .eq(ThirdPartyStationInfo::getStationId, connectorInfo.getStationId())
  1150. .last("LIMIT 1"));
  1151. if (stationInfo == null) {
  1152. log.warn("补偿任务: 订单{}找不到站点信息", order.getChargeOrderNo());
  1153. failedOrders.add(order.getChargeOrderNo());
  1154. continue;
  1155. }
  1156. // 5. 设置第三方费用信息
  1157. order.setTotalCharge(chargeStatus.getTotalPower());
  1158. order.setThirdPartyTotalCost(chargeStatus.getTotalMoney() != null ? chargeStatus.getTotalMoney() : BigDecimal.ZERO);
  1159. order.setThirdPartyServerfee(chargeStatus.getServiceMoney() != null ? chargeStatus.getServiceMoney() : BigDecimal.ZERO);
  1160. order.setThirdPartyElecfee(chargeStatus.getElecMoney() != null ? chargeStatus.getElecMoney() : BigDecimal.ZERO);
  1161. if (StrUtil.isNotBlank(chargeStatus.getChargeDetails())) {
  1162. order.setChargeDetails(chargeStatus.getChargeDetails());
  1163. }
  1164. // 6. 计算平台服务费
  1165. BigDecimal serviceFee = calculateServiceFee(order, chargeStatus, stationInfo);
  1166. // 7. 更新订单信息
  1167. order.setRealServiceCost(serviceFee.setScale(2, RoundingMode.HALF_UP));
  1168. order.setRealCost(serviceFee.add(order.getThirdPartyTotalCost()));
  1169. order.setStatus(SystemConstants.STATUS_THREE); // 已完成
  1170. // 设置充电时间
  1171. if (chargeStatus.getStartTime() != null && chargeStatus.getEndTime() != null) {
  1172. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  1173. order.setStartTime(chargeStatus.getStartTime().format(formatter));
  1174. order.setEndTime(chargeStatus.getEndTime().format(formatter));
  1175. order.setChargeTime(DateUtils.getDuration(order.getStartTime(), order.getEndTime()));
  1176. }
  1177. // 设置修复备注
  1178. order.setRemark("通过补偿修复处理");
  1179. this.updateById(order);
  1180. // 8. 执行账户余额扣减(仅平台订单和企业订单)
  1181. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_PLATFORM)
  1182. || Objects.equals(order.getOrderType(), SystemConstants.STATUS_ONE)) {
  1183. orderSettlement(order.getId());
  1184. log.info("补偿任务: 订单{}余额扣减完成", order.getChargeOrderNo());
  1185. }
  1186. // 渠道方订单:推送充电订单信息 + 渠道方账户余额扣减
  1187. if (Objects.equals(order.getOrderType(), SystemConstants.CHARGE_ORDER_TYPE_CHANNEL)) {
  1188. compensateChannelOrder(order, null);
  1189. }
  1190. successCount++;
  1191. chargeStatusCount++;
  1192. log.info("补偿任务: 订单{}通过充电状态表补偿成功,实际费用: {}", order.getChargeOrderNo(), order.getRealCost());
  1193. } catch (Exception e) {
  1194. log.error("补偿任务: 补偿订单{}失败: {}", order.getChargeOrderNo(), e.getMessage(), e);
  1195. failedOrders.add(order.getChargeOrderNo());
  1196. }
  1197. }
  1198. String result = String.format("充电订单补偿完成!总计: %d, 成功: %d (API日志: %d, 充电状态: %d), 跳过: %d, 失败: %d",
  1199. totalCount, successCount, apiLogCount, chargeStatusCount, skipCount, failedOrders.size());
  1200. if (!failedOrders.isEmpty()) {
  1201. result += ", 失败订单: " + String.join(",", failedOrders);
  1202. }
  1203. log.info(result);
  1204. return result;
  1205. }
  1206. /**
  1207. * 渠道方订单补偿:推送充电订单信息给渠道方 + 渠道方账户余额扣减
  1208. * 推送失败只记日志,不回滚补偿
  1209. *
  1210. * @param order 补偿完成的订单
  1211. * @param apiLogData API日志原始数据(可为null,则从订单字段构建推送数据)
  1212. */
  1213. private void compensateChannelOrder(ChargeOrderInfo order, String apiLogData) {
  1214. try {
  1215. FirmInfo firmInfo = firmInfoMapper.selectById(order.getFirmId());
  1216. if (firmInfo == null) {
  1217. log.warn("补偿任务: 订单{}找不到渠道方信息,firmId: {}", order.getChargeOrderNo(), order.getFirmId());
  1218. return;
  1219. }
  1220. // 1. 推送充电订单信息给渠道方
  1221. pushChargeOrderInfoToChannel(order, firmInfo, apiLogData);
  1222. // 2. 渠道方账户余额扣减
  1223. deductChannelFirmBalance(order, firmInfo);
  1224. } catch (Exception e) {
  1225. log.error("补偿任务: 渠道方订单{}补偿处理异常: {}", order.getChargeOrderNo(), e.getMessage(), e);
  1226. }
  1227. }
  1228. /**
  1229. * 推送充电订单信息给渠道方(参考 /notification_charge_order_info 接口推送格式)
  1230. * 失败只记日志,不影响补偿结果
  1231. */
  1232. private void pushChargeOrderInfoToChannel(ChargeOrderInfo order, FirmInfo firmInfo, String apiLogData) {
  1233. try {
  1234. // 构建推送数据
  1235. Map<String, Object> pushData;
  1236. if (apiLogData != null) {
  1237. // 使用API日志原始数据
  1238. pushData = objectMapper.readValue(apiLogData, Map.class);
  1239. } else {
  1240. // 从order字段构建推送数据
  1241. pushData = buildCompensationPushData(order);
  1242. }
  1243. normalizeCompensationPushData(pushData, order);
  1244. pushData.put("chargeOrderNo", order.getChargeOrderNo());
  1245. String url = firmInfo.getChannelUrl() + "/notification_charge_order_info";
  1246. String pushJson = objectMapper.writeValueAsString(pushData);
  1247. int maxRetries = 3;
  1248. int retryIntervalMs = 5000;
  1249. for (int attempt = 1; attempt <= maxRetries; attempt++) {
  1250. try {
  1251. JsonNode response = okHttpUtil.doPostJson(url, pushJson, null);
  1252. log.info("补偿任务: 渠道方推送充电订单信息成功 - chargeOrderNo: {}, firmId: {}, response: {}",
  1253. order.getChargeOrderNo(), order.getFirmId(), response);
  1254. return;
  1255. } catch (Exception e) {
  1256. log.error("补偿任务: 渠道方推送充电订单信息失败(第{}次) - chargeOrderNo: {}, firmId: {}, url: {}, 错误: {}",
  1257. attempt, order.getChargeOrderNo(), order.getFirmId(), url, e.getMessage(), e);
  1258. if (attempt < maxRetries) {
  1259. try {
  1260. Thread.sleep(retryIntervalMs);
  1261. } catch (InterruptedException ie) {
  1262. Thread.currentThread().interrupt();
  1263. break;
  1264. }
  1265. }
  1266. }
  1267. }
  1268. } catch (Exception e) {
  1269. log.error("补偿任务: 构建渠道方推送数据失败 - chargeOrderNo: {}, 错误: {}", order.getChargeOrderNo(), e.getMessage(), e);
  1270. }
  1271. }
  1272. /**
  1273. * 渠道方账户余额扣减 + 记录资金流水
  1274. */
  1275. private Map<String, Object> buildCompensationPushData(ChargeOrderInfo order) throws JsonProcessingException {
  1276. Map<String, Object> pushData = new LinkedHashMap<>();
  1277. if (StrUtil.isNotBlank(order.getChargeDetails())) {
  1278. JsonNode chargeDetailsNode = objectMapper.readTree(order.getChargeDetails());
  1279. if (chargeDetailsNode.isObject()) {
  1280. pushData.putAll(objectMapper.convertValue(chargeDetailsNode, LinkedHashMap.class));
  1281. } else if (chargeDetailsNode.isArray()) {
  1282. pushData.put("ChargeDetails", objectMapper.convertValue(chargeDetailsNode, List.class));
  1283. }
  1284. }
  1285. normalizeCompensationPushData(pushData, order);
  1286. return pushData;
  1287. }
  1288. private void normalizeCompensationPushData(Map<String, Object> pushData, ChargeOrderInfo order) throws JsonProcessingException {
  1289. List<Map<String, Object>> chargeDetails = resolveChargeDetails(pushData, order);
  1290. putIfBlank(pushData, "ArrearsAmt", BigDecimal.ZERO);
  1291. pushData.put("ChargeDetails", chargeDetails);
  1292. pushData.put("SumPeriod", chargeDetails.size());
  1293. putIfBlank(pushData, "ConnectorID", order.getConnectorId());
  1294. putIfBlank(pushData, "EndTime", order.getEndTime());
  1295. putIfBlank(pushData, "OriginElecMoney", defaultDecimal(order.getThirdPartyElecfee()));
  1296. putIfBlank(pushData, "OriginMoney", defaultDecimal(order.getThirdPartyTotalCost()));
  1297. putIfBlank(pushData, "OriginServiceMoney", defaultDecimal(order.getThirdPartyServerfee()));
  1298. putIfBlank(pushData, "PlatOrderId", order.getId());
  1299. putIfBlank(pushData, "ReceiptsAmt", defaultDecimal(order.getThirdPartyTotalCost()));
  1300. putIfBlank(pushData, "StartChargeSeq", order.getStartChargeSeq());
  1301. putIfBlank(pushData, "StartTime", order.getStartTime());
  1302. putIfBlank(pushData, "StopReason", parseIntegerOrDefault(order.getStopReason(), 0));
  1303. putIfBlank(pushData, "TotalElecMoney", defaultDecimal(order.getThirdPartyElecfee()));
  1304. putIfBlank(pushData, "TotalMoney", defaultDecimal(order.getThirdPartyTotalCost()));
  1305. putIfBlank(pushData, "TotalPower", defaultDecimal(order.getTotalCharge()));
  1306. putIfBlank(pushData, "TotalSeviceMoney", defaultDecimal(order.getThirdPartyServerfee()));
  1307. }
  1308. private List<Map<String, Object>> resolveChargeDetails(Map<String, Object> pushData, ChargeOrderInfo order) throws JsonProcessingException {
  1309. List<Map<String, Object>> chargeDetails = convertChargeDetails(pushData.get("ChargeDetails"));
  1310. if (!chargeDetails.isEmpty()) {
  1311. return chargeDetails;
  1312. }
  1313. if (StrUtil.isNotBlank(order.getChargeDetails())) {
  1314. JsonNode chargeDetailsNode = objectMapper.readTree(order.getChargeDetails());
  1315. if (chargeDetailsNode.isArray()) {
  1316. chargeDetails = convertChargeDetails(chargeDetailsNode);
  1317. } else if (chargeDetailsNode.isObject() && chargeDetailsNode.has("ChargeDetails")) {
  1318. chargeDetails = convertChargeDetails(chargeDetailsNode.get("ChargeDetails"));
  1319. }
  1320. }
  1321. if (!chargeDetails.isEmpty()) {
  1322. return chargeDetails;
  1323. }
  1324. return buildFallbackChargeDetails(order);
  1325. }
  1326. private List<Map<String, Object>> convertChargeDetails(Object rawChargeDetails) {
  1327. if (rawChargeDetails == null) {
  1328. return new ArrayList<>();
  1329. }
  1330. List<Map<String, Object>> chargeDetails = new ArrayList<>();
  1331. if (rawChargeDetails instanceof JsonNode jsonNode && jsonNode.isArray()) {
  1332. for (JsonNode detailNode : jsonNode) {
  1333. chargeDetails.add(objectMapper.convertValue(detailNode, LinkedHashMap.class));
  1334. }
  1335. return chargeDetails;
  1336. }
  1337. if (rawChargeDetails instanceof List<?> detailList) {
  1338. for (Object detail : detailList) {
  1339. chargeDetails.add(objectMapper.convertValue(detail, LinkedHashMap.class));
  1340. }
  1341. }
  1342. return chargeDetails;
  1343. }
  1344. private List<Map<String, Object>> buildFallbackChargeDetails(ChargeOrderInfo order) {
  1345. List<Map<String, Object>> chargeDetails = new ArrayList<>();
  1346. if (order == null) {
  1347. return chargeDetails;
  1348. }
  1349. Map<String, Object> detail = new LinkedHashMap<>();
  1350. BigDecimal totalPower = defaultDecimal(order.getTotalCharge());
  1351. BigDecimal totalElecMoney = defaultDecimal(order.getThirdPartyElecfee());
  1352. BigDecimal totalServiceMoney = defaultDecimal(order.getThirdPartyServerfee());
  1353. detail.put("DetailStartTime", order.getStartTime());
  1354. detail.put("DetailEndTime", order.getEndTime());
  1355. detail.put("ItemFlag", resolvePeriodFlag(order));
  1356. detail.put("ElecPrice", calculateUnitPrice(totalElecMoney, totalPower));
  1357. detail.put("SevicePrice", calculateUnitPrice(totalServiceMoney, totalPower));
  1358. detail.put("DetailPower", totalPower);
  1359. detail.put("DetailElecMoney", totalElecMoney);
  1360. detail.put("DetailSeviceMoney", totalServiceMoney);
  1361. chargeDetails.add(detail);
  1362. return chargeDetails;
  1363. }
  1364. private Integer resolvePeriodFlag(ChargeOrderInfo order) {
  1365. if (order == null || StrUtil.isBlank(order.getConnectorId())) {
  1366. return 3;
  1367. }
  1368. ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
  1369. Wrappers.<ThirdPartyEquipmentPricePolicy>lambdaQuery()
  1370. .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, order.getConnectorId())
  1371. .eq(ThirdPartyEquipmentPricePolicy::getIsDeleted, 0)
  1372. .last("LIMIT 1"));
  1373. if (pricePolicy == null) {
  1374. return 3;
  1375. }
  1376. List<ThirdPartyPolicyInfo> policyInfos = thirdPartyPolicyInfoMapper.selectList(
  1377. Wrappers.<ThirdPartyPolicyInfo>lambdaQuery()
  1378. .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
  1379. .eq(ThirdPartyPolicyInfo::getIsDeleted, 0)
  1380. .orderByAsc(ThirdPartyPolicyInfo::getStartTime));
  1381. if (policyInfos == null || policyInfos.isEmpty()) {
  1382. return 3;
  1383. }
  1384. String chargeStartTime = extractTimePart(order.getStartTime());
  1385. if (StrUtil.isBlank(chargeStartTime)) {
  1386. return policyInfos.get(0).getPeriodFlag() != null ? policyInfos.get(0).getPeriodFlag() : 3;
  1387. }
  1388. ThirdPartyPolicyInfo matchedPolicy = null;
  1389. for (ThirdPartyPolicyInfo policyInfo : policyInfos) {
  1390. if (policyInfo.getStartTime() == null) {
  1391. continue;
  1392. }
  1393. if (chargeStartTime.compareTo(policyInfo.getStartTime()) >= 0) {
  1394. matchedPolicy = policyInfo;
  1395. } else {
  1396. break;
  1397. }
  1398. }
  1399. if (matchedPolicy == null) {
  1400. matchedPolicy = policyInfos.get(policyInfos.size() - 1);
  1401. }
  1402. return matchedPolicy.getPeriodFlag() != null ? matchedPolicy.getPeriodFlag() : 3;
  1403. }
  1404. private String extractTimePart(String dateTime) {
  1405. if (StrUtil.isBlank(dateTime)) {
  1406. return null;
  1407. }
  1408. try {
  1409. return LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
  1410. .format(DateTimeFormatter.ofPattern("HHmmss"));
  1411. } catch (Exception ex) {
  1412. return null;
  1413. }
  1414. }
  1415. private BigDecimal calculateUnitPrice(BigDecimal amount, BigDecimal power) {
  1416. if (power == null || power.compareTo(BigDecimal.ZERO) <= 0) {
  1417. return BigDecimal.ZERO;
  1418. }
  1419. return amount.divide(power, 4, RoundingMode.HALF_UP);
  1420. }
  1421. private BigDecimal defaultDecimal(BigDecimal value) {
  1422. return value != null ? value : BigDecimal.ZERO;
  1423. }
  1424. private void putIfBlank(Map<String, Object> data, String key, Object value) {
  1425. if (!data.containsKey(key) || isNullLikeValue(data.get(key))) {
  1426. data.put(key, value);
  1427. }
  1428. }
  1429. private boolean isNullLikeValue(Object value) {
  1430. if (value == null) {
  1431. return true;
  1432. }
  1433. if (value instanceof String str) {
  1434. return StrUtil.isBlank(str) || "null".equalsIgnoreCase(str.trim());
  1435. }
  1436. return false;
  1437. }
  1438. private Integer parseIntegerOrDefault(String value, Integer defaultValue) {
  1439. if (StrUtil.isBlank(value) || "null".equalsIgnoreCase(value.trim())) {
  1440. return defaultValue;
  1441. }
  1442. try {
  1443. return Integer.parseInt(value.trim());
  1444. } catch (NumberFormatException ex) {
  1445. return defaultValue;
  1446. }
  1447. }
  1448. private void deductChannelFirmBalance(ChargeOrderInfo order, FirmInfo firmInfo) {
  1449. BigDecimal cost = order.getRealCost();
  1450. if (cost == null || cost.compareTo(BigDecimal.ZERO) <= 0) {
  1451. log.info("补偿任务: 订单{}实际费用为0,跳过渠道方余额扣减", order.getChargeOrderNo());
  1452. return;
  1453. }
  1454. // 记录资金流水
  1455. FirmAccountLog accountLog = new FirmAccountLog();
  1456. accountLog.setFirmId(firmInfo.getId());
  1457. accountLog.setFirmType(firmInfo.getFirmType());
  1458. accountLog.setEventDesc("渠道方充电订单下账(补偿)");
  1459. accountLog.setSerialNo(order.getChargeOrderNo());
  1460. accountLog.setIncomeType(2);
  1461. accountLog.setBeforeChange(firmInfo.getBalance());
  1462. accountLog.setAfterChange(firmInfo.getBalance().subtract(cost));
  1463. accountLog.setMoneyChange(cost);
  1464. firmAccountLogMapper.insert(accountLog);
  1465. // 渠道方账户余额修改
  1466. firmInfo.setBalance(firmInfo.getBalance().subtract(cost));
  1467. firmInfoMapper.updateById(firmInfo);
  1468. log.info("补偿任务: 订单{}渠道方余额扣减完成,扣减金额: {}, 扣减后余额: {}",
  1469. order.getChargeOrderNo(), cost, firmInfo.getBalance());
  1470. }
  1471. @Override
  1472. @Transactional(rollbackFor = Exception.class)
  1473. public String compensateChannelOrderPush() {
  1474. log.info("开始执行渠道方订单推送补偿...");
  1475. // 查询已完成(status=3)、备注为"通过补偿修复处理"的渠道方订单
  1476. List<ChargeOrderInfo> channelOrders = this.list(Wrappers.<ChargeOrderInfo>lambdaQuery()
  1477. .eq(ChargeOrderInfo::getStatus, 3)
  1478. .eq(ChargeOrderInfo::getOrderType, SystemConstants.CHARGE_ORDER_TYPE_CHANNEL)
  1479. .eq(ChargeOrderInfo::getRemark, "通过补偿修复处理")
  1480. );
  1481. if (channelOrders.isEmpty()) {
  1482. log.info("渠道方推送补偿: 没有找到需要补偿的渠道方订单");
  1483. return "没有找到需要补偿的渠道方订单";
  1484. }
  1485. log.info("渠道方推送补偿: 找到{}个需要补偿的渠道方订单", channelOrders.size());
  1486. int totalCount = 0;
  1487. int successCount = 0;
  1488. List<String> failedOrders = new ArrayList<>();
  1489. for (ChargeOrderInfo order : channelOrders) {
  1490. totalCount++;
  1491. try {
  1492. // 优先从third_party_api_log获取原始推送数据
  1493. String apiLogData = null;
  1494. if (order.getStartChargeSeq() != null) {
  1495. ThirdPartyApiLog apiLog = thirdPartyApiLogMapper.selectOne(
  1496. Wrappers.<ThirdPartyApiLog>lambdaQuery()
  1497. .eq(ThirdPartyApiLog::getInterfaceDescription, "推送充电订单信息")
  1498. .like(ThirdPartyApiLog::getDecryptedRequestData, order.getStartChargeSeq())
  1499. .orderByDesc(ThirdPartyApiLog::getCreatedTime)
  1500. .last("LIMIT 1"));
  1501. if (apiLog != null && apiLog.getDecryptedRequestData() != null) {
  1502. apiLogData = apiLog.getDecryptedRequestData();
  1503. }
  1504. }
  1505. compensateChannelOrder(order, apiLogData);
  1506. successCount++;
  1507. log.info("渠道方推送补偿: 订单{}补偿成功", order.getChargeOrderNo());
  1508. } catch (Exception e) {
  1509. log.error("渠道方推送补偿: 订单{}补偿失败: {}", order.getChargeOrderNo(), e.getMessage(), e);
  1510. failedOrders.add(order.getChargeOrderNo());
  1511. }
  1512. }
  1513. String result = String.format("渠道方推送补偿完成!总计: %d, 成功: %d, 失败: %d",
  1514. totalCount, successCount, failedOrders.size());
  1515. if (!failedOrders.isEmpty()) {
  1516. result += ", 失败订单: " + String.join(",", failedOrders);
  1517. }
  1518. log.info(result);
  1519. return result;
  1520. }
  1521. @Override
  1522. public void failOrderDispose() {
  1523. //1.查询所有待启动的订单
  1524. List<ChargeOrderInfo> orderInfoList = this.list(Wrappers.<ChargeOrderInfo>lambdaQuery().eq(ChargeOrderInfo::getStatus, 0).last("LIMIT 1000"));
  1525. //2.遍历判断订单创建时间是否已超过3分钟
  1526. for (ChargeOrderInfo chargeOrderInfo : orderInfoList) {
  1527. LocalDateTime createTime = chargeOrderInfo.getCreateTime();
  1528. if (createTime != null && createTime.plusMinutes(3).isBefore(LocalDateTime.now())) {
  1529. String chargeOrderNo = chargeOrderInfo.getChargeOrderNo();
  1530. try {
  1531. log.info("失败订单处理: 订单{}创建时间已超过3分钟,调用订单修复", chargeOrderNo);
  1532. this.repairOrderByOrderNo(chargeOrderNo);
  1533. } catch (Exception e) {
  1534. log.error("失败订单处理: 订单{}修复失败: {}", chargeOrderNo, e.getMessage(), e);
  1535. }
  1536. }
  1537. }
  1538. }
  1539. @Override
  1540. public void settleOrderFailDispose() {
  1541. //1.查询所有结算中的订单
  1542. List<ChargeOrderInfo> orderInfoList = this.list(Wrappers.<ChargeOrderInfo>lambdaQuery().eq(ChargeOrderInfo::getStatus, 2).last("LIMIT 1000"));
  1543. //2.遍历判断订单结算时间是否已超过5分钟
  1544. for (ChargeOrderInfo chargeOrderInfo : orderInfoList) {
  1545. LocalDateTime settleTime = chargeOrderInfo.getSettleTime();
  1546. if (settleTime != null && settleTime.plusMinutes(5).isBefore(LocalDateTime.now())) {
  1547. String chargeOrderNo = chargeOrderInfo.getChargeOrderNo();
  1548. try {
  1549. log.info("失败订单处理: 订单{}结算时间已超过5分钟,调用订单修复", chargeOrderNo);
  1550. this.repairOrderByOrderNo(chargeOrderNo);
  1551. } catch (Exception e) {
  1552. log.error("失败订单处理: 订单{}修复失败: {}", chargeOrderNo, e.getMessage(), e);
  1553. }
  1554. }
  1555. }
  1556. }
  1557. }