ChargeOrderInfoServiceImpl.java 83 KB

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