package com.crm.settlement.service.impl; import com.alibaba.fastjson2.JSON; import com.crm.rely.backend.core.constant.Constants; import com.crm.rely.backend.core.dto.base.BaseResultDto; import com.crm.rely.backend.core.dto.base.BaseResultWithPagerDto; import com.crm.rely.backend.core.dto.base.PageDto; import com.crm.rely.backend.core.entity.base.PageEntity; import com.crm.rely.backend.core.exception.PayValidatedException; import com.crm.rely.backend.core.exception.ServiceException; import com.crm.rely.backend.model.constant.ConfigConstants; import com.crm.rely.backend.model.constant.SettlementConstant; import com.crm.rely.backend.model.dto.settlement.SettlementDepositDto; import com.crm.rely.backend.model.dto.settlement.SettlementValidateDto; import com.crm.rely.backend.model.dto.settlement.SettlementWithdrawDto; import com.crm.rely.backend.model.entity.base.MerchantPayInRequestEntity; import com.crm.rely.backend.model.entity.base.MerchantPayOutRequestEntity; import com.crm.rely.backend.model.entity.settlement.SettlementDepositListSearchEntity; import com.crm.rely.backend.model.entity.settlement.SettlementValidateDepositEntity; import com.crm.rely.backend.model.entity.settlement.SettlementValidateWithdrawEntity; import com.crm.rely.backend.model.entity.settlement.SettlementWithdrawListSearchEntity; import com.crm.rely.backend.model.entity.settlement.log.SettlementCallbackLogAddEntity; import com.crm.rely.backend.model.entity.settlement.vaultody.VaultodyDepositRequestEntity; import com.crm.rely.backend.model.entity.settlement.vaultody.VaultodyWithdrawRequestEntity; import com.crm.rely.backend.model.pojo.table.SettlementDepositRecordTable; import com.crm.rely.backend.model.pojo.table.SettlementWithdrawRecordTable; import com.crm.rely.backend.model.pojo.table.SysSettlementConfigTable; import com.crm.rely.backend.model.util.MapToEntityConverterUtil; import com.crm.rely.backend.service.MqSendService; import com.crm.rely.backend.util.FormatPage; import com.crm.rely.backend.util.HttpUtil; import com.crm.rely.backend.util.MapUtil; import com.crm.settlement.dao.mapper.SettlementDepositRecordMapper; import com.crm.settlement.dao.mapper.SettlementWithdrawRecordMapper; import com.crm.settlement.dao.repository.SettlementDepositRecordRepository; import com.crm.settlement.dao.repository.SettlementWithdrawRecordRepository; import com.crm.settlement.service.SettlementLogService; import com.crm.settlement.service.SettlementService; import com.crm.settlement.service.SysSettlementConfigService; import com.crm.settlement.service.VaultodyService; import com.crm.settlement.service.impl.base.BaseSettlementServiceImpl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jsoup.Connection; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.annotation.JmsListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.util.*; @Slf4j @Service public class SettlementServiceImpl extends BaseSettlementServiceImpl implements SettlementService { @Autowired private SettlementDepositRecordRepository settlementDepositRecordRepository; @Autowired private SettlementWithdrawRecordRepository settlementWithdrawRecordRepository; @Autowired private SettlementDepositRecordMapper settlementDepositRecordMapper; @Autowired private SettlementWithdrawRecordMapper settlementWithdrawRecordMapper; @Autowired private SysSettlementConfigService sysSettlementConfigService; @Autowired private VaultodyService vaultodyService; @Autowired private SettlementLogService settlementLogService; @Autowired private MqSendService mqSendService; /** * 验证入金订单 */ @Override public BaseResultDto validateDepositOrder(SettlementValidateDepositEntity entity) { try { //验证时间 Long requestTimestamp = entity.getRequestTime(); //验证时间 long serverTimestamp = System.currentTimeMillis(); long timeDiff = Math.abs(serverTimestamp - requestTimestamp); if (timeDiff > 60000) { log.error("time out :" + JSON.toJSONString(entity) + ",serverTime:" + serverTimestamp + ",requestTime:" + requestTimestamp + ",diff:" + timeDiff); throw ServiceException.exception("Time out"); } verifySign(entity.getSign(), getDepositSignString(entity), entity.getMerchantId()); SettlementDepositRecordTable recordTable = settlementDepositRecordRepository.findFirstBySerialAndChannelCode(entity.getSerial(), entity.getChannelCode()); if (recordTable == null || recordTable.getStatus() == null || recordTable.getStatus().equals(0)) { return BaseResultDto.error(); } SettlementValidateDto dto = new SettlementValidateDto(); dto.setStatus(recordTable.getStatus()); return BaseResultDto.success(dto); } catch (Exception e) { return BaseResultDto.error(); } } /** * 验证出金订单 */ @Override public BaseResultDto validateWithdrawOrder(SettlementValidateWithdrawEntity entity) throws PayValidatedException { try { //验证时间 Long requestTimestamp = entity.getRequestTime(); //验证时间 long serverTimestamp = System.currentTimeMillis(); long timeDiff = Math.abs(serverTimestamp - requestTimestamp); if (timeDiff > 60000) { log.error("time out :" + JSON.toJSONString(entity) + ",serverTime:" + serverTimestamp + ",requestTime:" + requestTimestamp + ",diff:" + timeDiff); throw ServiceException.exception("Time out"); } verifySign(entity.getSign(), getWithdrawSignString(entity), entity.getMerchantId()); SettlementWithdrawRecordTable recordTable = settlementWithdrawRecordRepository.findFirstBySerialAndChannelCode(entity.getSerial(), entity.getChannelCode()); if (recordTable == null || recordTable.getStatus() == null || recordTable.getStatus().equals(0)) { return BaseResultDto.error(); } SettlementValidateDto dto = new SettlementValidateDto(); dto.setStatus(recordTable.getStatus()); return BaseResultDto.success(dto); } catch (Exception e) { return BaseResultDto.error(); } } @Override public BaseResultDto deposit(Map request) throws Exception { MerchantPayInRequestEntity requestEntity = verifyDepositRequest(request); String channelCode = requestEntity.getChannelCode(); switch (channelCode) { case SettlementConstant.VAULTODY_PAY_KEY: VaultodyDepositRequestEntity vaultodyDepositRequest = MapToEntityConverterUtil.convertAndValidate(request, VaultodyDepositRequestEntity.class); return vaultodyService.deposit(vaultodyDepositRequest); default: throw ServiceException.exception("channel Not support"); } } @Override public BaseResultDto withdraw(Map request) throws Exception{ MerchantPayOutRequestEntity requestEntity = verifyWithdrawRequest(request); String channelCode = requestEntity.getChannelCode(); switch (channelCode) { case SettlementConstant.VAULTODY_PAY_REMIT: VaultodyWithdrawRequestEntity vaultodyWithdrawRequest = MapToEntityConverterUtil.convertAndValidate(request, VaultodyWithdrawRequestEntity.class); return vaultodyService.withdraw(vaultodyWithdrawRequest); default: throw ServiceException.exception("channel Not support"); } } @Override public BaseResultWithPagerDto> depositSearchList(SettlementDepositListSearchEntity entity) throws Exception { String signString = getDepositlistSignString(entity); verifySign(entity.getSign(), signString, entity.getMerchantId()); if (entity.getCurrent() != null && entity.getRow() != null){ PageEntity page = new PageEntity(); page.setCurrent(entity.getCurrent()); page.setRow(entity.getRow()); entity.setPage(page); } /* 获取总数 */ Integer count = settlementDepositRecordMapper.countList(entity); PageDto pageDto = FormatPage.format(entity.getPage(), count); if (count <= 0) { if (count == null || count == 0) { return BaseResultWithPagerDto.success(pageDto); } } /* 获取列表 */ List depositRecordTables = settlementDepositRecordMapper.pageList(entity); if (depositRecordTables == null || depositRecordTables.size() <= 0) { throw new ServiceException(Constants.SYSTEM_ERROR); } List depositDtos = new ArrayList<>(depositRecordTables.size()); for (SettlementDepositRecordTable depositRecordTable : depositRecordTables) { SettlementDepositDto depositDto = depositTransform(depositRecordTable); depositDtos.add(depositDto); } return BaseResultWithPagerDto.success(depositDtos, pageDto); } @Override public BaseResultWithPagerDto> withdrawSearchList(SettlementWithdrawListSearchEntity entity) throws Exception { String signString = getWithdrawlistSignString(entity); verifySign(entity.getSign(), signString, entity.getMerchantId()); if (entity.getCurrent() != null && entity.getRow() != null){ PageEntity page = new PageEntity(); page.setCurrent(entity.getCurrent()); page.setRow(entity.getRow()); entity.setPage(page); } /* 获取总数 */ Integer count = settlementWithdrawRecordMapper.countList(entity); PageDto pageDto = FormatPage.format(entity.getPage(), count); if (count <= 0) { if (count == null || count == 0) { return BaseResultWithPagerDto.success(pageDto); } } /* 获取列表 */ List withdrawRecordTables = settlementWithdrawRecordMapper.pageList(entity); if (withdrawRecordTables == null || withdrawRecordTables.size() <= 0) { throw new ServiceException(Constants.SYSTEM_ERROR); } List withdrawDtos = new ArrayList<>(withdrawRecordTables.size()); for (SettlementWithdrawRecordTable depositRecordTable : withdrawRecordTables) { SettlementWithdrawDto depositDto = withdrawTransform(depositRecordTable); withdrawDtos.add(depositDto); } return BaseResultWithPagerDto.success(withdrawDtos, pageDto); } private SettlementDepositDto depositTransform(SettlementDepositRecordTable depositRecordTable) { SettlementDepositDto depositDto = new SettlementDepositDto(); BeanUtils.copyProperties(depositRecordTable, depositDto); return depositDto; } private SettlementWithdrawDto withdrawTransform(SettlementWithdrawRecordTable withdrawRecordTable) { SettlementWithdrawDto withdrawDto = new SettlementWithdrawDto(); BeanUtils.copyProperties(withdrawRecordTable, withdrawDto); return withdrawDto; } /** * 验证请求时间和金额 */ protected MerchantPayOutRequestEntity verifyWithdrawRequest(Map request) throws ServiceException { MerchantPayOutRequestEntity requestEntity = MapToEntityConverterUtil.convertAndValidate(request, MerchantPayOutRequestEntity.class); String merchantId = requestEntity.getMerchantId(); BigDecimal amount = requestEntity.getAmount(); Long requestTimestamp = requestEntity.getRequestTime(); //验证时间 long serverTimestamp = System.currentTimeMillis(); long timeDiff = Math.abs(serverTimestamp - requestTimestamp); if (timeDiff > 60000) { log.error("time out :" + JSON.toJSONString(request) + ",serverTime:" + serverTimestamp + ",requestTime:" + requestTimestamp + ",diff:" + timeDiff); throw ServiceException.exception("Time out"); } // 验证金额 if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw ServiceException.exception("Invalid amount"); } // 验证商户 SysSettlementConfigTable table = sysSettlementConfigService.getByCode(merchantId); if (table == null || StringUtils.isEmpty(table.getValue())) { throw ServiceException.exception("Merchant error"); } // 验证订单 if (settlementWithdrawRecordRepository.existsByMerchantIdAndSerial(request.get("merchantId"), request.get("serial"))) { throw ServiceException.exception("Duplicate order"); } return requestEntity; } /** * 验证请求时间和金额 */ protected MerchantPayInRequestEntity verifyDepositRequest(Map request) throws ServiceException { MerchantPayInRequestEntity requestEntity = MapToEntityConverterUtil.convertAndValidate(request, MerchantPayInRequestEntity.class); String merchantId = requestEntity.getMerchantId(); BigDecimal amount = requestEntity.getAmount(); Long requestTimestamp = requestEntity.getRequestTime(); //验证时间 long serverTimestamp = System.currentTimeMillis(); long timeDiff = Math.abs(serverTimestamp - requestTimestamp); if (timeDiff > 60000) { log.error("time out :" + JSON.toJSONString(request) + ",serverTime:" + serverTimestamp + ",requestTime:" + requestTimestamp + ",diff:" + timeDiff); throw ServiceException.exception("Time out"); } // 验证金额 if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw ServiceException.exception("Invalid amount"); } // 验证商户 SysSettlementConfigTable table = sysSettlementConfigService.getByCode(merchantId); if (table == null || StringUtils.isEmpty(table.getValue())) { throw ServiceException.exception("Merchant error"); } // 验证订单 if (settlementDepositRecordRepository.existsByMerchantIdAndSerial(request.get("merchantId"), request.get("serial"))) { throw ServiceException.exception("Duplicate order"); } return requestEntity; } @Transactional(rollbackFor = Exception.class) @JmsListener(destination = ConfigConstants.DEPOSIT_CALLBACK) public void depositCallback(String content) throws Exception { log.info("DEPOSIT_CALLBACK: content:" + content); SettlementDepositRecordTable recordTable = JSON.parseObject(content, SettlementDepositRecordTable.class); SettlementDepositRecordTable table = settlementDepositRecordRepository.findFirstById(recordTable.getId()); if (table == null) { log.error("DEPOSIT_CALLBACK SettlementDepositRecordTable is null"); return; } // 判断三方回调状态 if (!table.getCallbackStatus()) { log.info("DEPOSIT_CALLBACK: callbackStatus is Failed "); return; } // 判断状态 if (table.getCallbackMerchantStatus().equals(1)) { log.info("DEPOSIT_CALLBACK: callbackMerchantStatus is Success "); return; } if (table.getCallbackMerchantCount() >= table.getCallbackMerchantMaxRetryCount()) { log.info("DEPOSIT_CALLBACK: callbackMerchantCount is max retry count"); return; } String callbackUrl = table.getCallbackUrl(); Map params = new HashMap<>(); params.put("serial", table.getSerial()); params.put("login", String.valueOf(table.getLogin())); params.put("channelCode", table.getChannelCode()); params.put("callbackAmount", table.getCallbackAmount().stripTrailingZeros().toPlainString()); params.put("callbackCurrency", table.getCallbackCurrency()); params.put("callbackSerial", table.getCallbackSerial()); String sign = getSign(params, table.getMerchantId()); params.put("sign", sign); String callbackContent = JSON.toJSONString(params); try { Connection.Response response = HttpUtil.post(callbackUrl, callbackContent); //打印请求日志 log.info("DEPOSIT_CALLBACK Request URL: {},data:{},response body:{}", callbackUrl, callbackContent, response.body()); BaseResultDto requestEntity = JSON.parseObject(response.body(), BaseResultDto.class); if (requestEntity == null || requestEntity.getCode() != 200) { table.setCallbackMerchantStatus(2); } else { table.setCallbackMerchantStatus(1); } } catch (Exception e) { log.error("DEPOSIT_CALLBACK, Request URL: {}, data:{}", callbackUrl, callbackContent, e); table.setCallbackMerchantStatus(2); } finally { SettlementCallbackLogAddEntity addEntity = new SettlementCallbackLogAddEntity(); addEntity.setMerchantId(table.getMerchantId()); addEntity.setSerial(table.getSerial()); addEntity.setAmount(table.getCallbackAmount()); addEntity.setChannelCode(table.getChannelCode()); addEntity.setCallbackUrl(callbackUrl); addEntity.setCallbackData(callbackContent); addEntity.setAddTime(new Date()); addEntity.setNote("入金回调"); settlementLogService.add(addEntity); } table.setCallbackMerchantCount(table.getCallbackMerchantCount() + 1); table.setCallbackMerchantTime(new Date()); settlementDepositRecordRepository.save(table); if (table.getCallbackMerchantStatus().equals(2) && table.getCallbackMerchantCount() < table.getCallbackMerchantMaxRetryCount()) { mqSendService.send(ConfigConstants.DEPOSIT_CALLBACK, table, 3 * 60 * 1000); } } @Transactional(rollbackFor = Exception.class) @JmsListener(destination = ConfigConstants.WITHDRAW_CALLBACK) public void withdrawCallback(String content) throws Exception { log.info("WITHDRAW_CALLBACK: content:" + content); SettlementWithdrawRecordTable withdrawRecordTable = JSON.parseObject(content, SettlementWithdrawRecordTable.class); SettlementWithdrawRecordTable table = settlementWithdrawRecordRepository.findFirstById(withdrawRecordTable.getId()); if (table == null) { log.error("WITHDRAW_CALLBACK SettlementDepositRecordTable is null"); return; } // 判断状态 if (table.getCallbackMerchantStatus().equals(1)) { log.info("WITHDRAW_CALLBACK: callbackMerchantStatus is Success "); return; } if (table.getCallbackMerchantCount() >= table.getCallbackMerchantMaxRetryCount()) { log.info("WITHDRAW_CALLBACK: callbackMerchantCount is max retry count"); return; } String callbackUrl = table.getCallbackUrl(); Map params = new HashMap<>(); params.put("serial", table.getSerial()); params.put("login", String.valueOf(table.getLogin())); params.put("channelCode", table.getChannelCode()); params.put("callbackAmount", table.getCallbackAmount().stripTrailingZeros().toPlainString()); params.put("callbackCurrency", table.getCallbackCurrency()); params.put("callbackSerial", table.getCallbackSerial()); String sign = getSign(params, table.getMerchantId()); params.put("sign", sign); String callbackContent = JSON.toJSONString(params); try { Connection.Response response = HttpUtil.post(callbackUrl, callbackContent); //打印请求日志 log.info("WITHDRAW_CALLBACK Request URL: {},data:{},response body:{}", callbackUrl, callbackContent, response.body()); BaseResultDto requestEntity = JSON.parseObject(response.body(), BaseResultDto.class); if (requestEntity == null || requestEntity.getCode() != 200) { table.setCallbackMerchantStatus(2); } else { table.setCallbackMerchantStatus(1); } } catch (Exception e) { log.error("WITHDRAW_CALLBACK, Request URL: {}, data:{}", callbackUrl, callbackContent, e); table.setCallbackMerchantStatus(2); } finally { SettlementCallbackLogAddEntity addEntity = new SettlementCallbackLogAddEntity(); addEntity.setMerchantId(table.getMerchantId()); addEntity.setSerial(table.getSerial()); addEntity.setAmount(table.getCallbackAmount()); addEntity.setChannelCode(table.getChannelCode()); addEntity.setCallbackUrl(callbackUrl); addEntity.setCallbackData(callbackContent); addEntity.setAddTime(new Date()); addEntity.setNote("入金回调"); settlementLogService.add(addEntity); } // table.setCallbackMerchantBody(callbackMerchantBody); table.setCallbackMerchantCount(table.getCallbackMerchantCount() + 1); table.setCallbackMerchantTime(new Date()); settlementWithdrawRecordRepository.save(table); if (table.getCallbackMerchantStatus().equals(2) && table.getCallbackMerchantCount() < table.getCallbackMerchantMaxRetryCount()) { mqSendService.send(ConfigConstants.WITHDRAW_CALLBACK, table, 3 * 60 * 1000); } } /** * 订单通知失败重新发送通知 * 查询入金和出金 回调失败的 未达到最大回调次数的 超过一天的数据 分别发送回调 */ @Scheduled(cron = "0 23 0 * * ?") @Transactional(rollbackFor = Exception.class) public void scheduleCallback() throws ServiceException { log.info("scheduleCallback start"); List depositRecordTables = settlementDepositRecordMapper.getAllCallback(); List withdrawRecordTables = settlementWithdrawRecordMapper.getAllCallback(); if (!CollectionUtils.isEmpty(depositRecordTables)) { log.info("scheduleCallback: depositRecordTables size:{}", depositRecordTables.size()); for (SettlementDepositRecordTable table : depositRecordTables) { mqSendService.send(ConfigConstants.DEPOSIT_CALLBACK, table); } } if (!CollectionUtils.isEmpty(withdrawRecordTables)) { log.info("scheduleCallback: withdrawRecordTables size:{}", withdrawRecordTables.size()); for (SettlementWithdrawRecordTable table : withdrawRecordTables) { mqSendService.send(ConfigConstants.WITHDRAW_CALLBACK, table); } } log.info("scheduleCallback end"); } private String getDepositSignString(SettlementValidateDepositEntity entity) { Map map = new HashMap<>(); map.put("merchantId", entity.getMerchantId()); map.put("channelCode", entity.getChannelCode()); map.put("serial", entity.getSerial()); map.put("requestTime", String.valueOf(entity.getRequestTime())); return MapUtil.getStringSortByKey(map); } private String getWithdrawSignString(SettlementValidateWithdrawEntity entity) { Map map = new HashMap<>(); map.put("merchantId", entity.getMerchantId()); map.put("channelCode", entity.getChannelCode()); map.put("serial", entity.getSerial()); map.put("requestTime", String.valueOf(entity.getRequestTime())); return MapUtil.getStringSortByKey(map); } private String getDepositlistSignString(SettlementDepositListSearchEntity entity) { Map map = new HashMap<>(); map.put("merchantId", entity.getMerchantId()); map.put("name", entity.getName()); map.put("email", entity.getEmail()); map.put("serial", entity.getSerial()); map.put("login", String.valueOf(entity.getLogin())); map.put("channelCode", entity.getChannelCode()); map.put("callbackMerchantStatus", String.valueOf(entity.getCallbackMerchantStatus())); map.put("startDate", String.valueOf(entity.getStartDate())); map.put("endDate", String.valueOf(entity.getEndDate())); map.put("current", String.valueOf(entity.getCurrent())); map.put("row", String.valueOf(entity.getRow())); map.put("requestTime", String.valueOf(entity.getRequestTime())); return MapUtil.getStringSortByKey(map); } private String getWithdrawlistSignString(SettlementWithdrawListSearchEntity entity) { Map map = new HashMap<>(); map.put("merchantId", entity.getMerchantId()); map.put("name", entity.getName()); map.put("email", entity.getEmail()); map.put("serial", entity.getSerial()); map.put("login", String.valueOf(entity.getLogin())); map.put("channelCode", entity.getChannelCode()); map.put("callbackMerchantStatus", String.valueOf(entity.getCallbackMerchantStatus())); map.put("startDate", String.valueOf(entity.getStartDate())); map.put("endDate", String.valueOf(entity.getEndDate())); map.put("current", String.valueOf(entity.getCurrent())); map.put("row", String.valueOf(entity.getRow())); map.put("requestTime", String.valueOf(entity.getRequestTime())); return MapUtil.getStringSortByKey(map); } }