diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/NotDelayMerchantProgramLogic.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/NotDelayMerchantProgramLogic.java index f9bc2fd94..312e0525a 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/NotDelayMerchantProgramLogic.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/NotDelayMerchantProgramLogic.java @@ -19,6 +19,7 @@ import com.jsowell.common.core.domain.ykc.TransactionRecordsData; import com.jsowell.common.enums.AcquirerEnum; import com.jsowell.common.enums.DelFlagEnum; import com.jsowell.common.enums.MemberWalletEnum; +import com.jsowell.common.enums.adapay.AdapayStatusEnum; import com.jsowell.common.enums.adapay.MerchantDelayModeEnum; import com.jsowell.common.enums.ykc.*; import com.jsowell.common.exception.BusinessException; @@ -716,6 +717,14 @@ public class NotDelayMerchantProgramLogic extends AbstractProgramLogic { refundInfo.setReverseId(refund.getRefund_id()); refundInfo.setPaymentId(refund.getPayment_id()); refundInfo.setReverseAmt(refund.getRefund_amt()); + String transStatus = refund.getTrans_status(); + if (StringUtils.equalsIgnoreCase("S", transStatus)) { + refundInfo.setStatus(AdapayStatusEnum.SUCCEEDED.getValue()); + } else if (StringUtils.equalsIgnoreCase("F", transStatus)) { + refundInfo.setStatus(AdapayStatusEnum.FAILED.getValue()); + } else { + refundInfo.setStatus(AdapayStatusEnum.PENDING.getValue()); + } // LocalDateTime createdTime = DateUtils.timestampToDatetime(Long.parseLong(refund.getCreated_time())); // refundInfo.setCreatedTime(DateUtils.formatDateTime(createdTime)); // LocalDateTime succeedTime = DateUtils.timestampToDatetime(Long.parseLong(refund.getSucceed_time())); diff --git a/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml index fcd505dbc..f64b5bc50 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml @@ -750,13 +750,13 @@ pay_amount as payAmount, settle_amount as settleAmount, confirmed_split_amount as confirmedSplitAmount, - GREATEST(0, settle_amount - confirmed_split_amount - payment_revoke_amount) AS waitSplitAmount, + GREATEST(0, IFNULL(pay_amount, 0) - IFNULL(due_refund_amount, 0) - IFNULL(confirmed_split_amount, 0)) AS waitSplitAmount, due_refund_amount as refundAmount, refund_amount as paidAmount, - GREATEST(0, due_refund_amount - refund_amount) AS refundPayAmount + GREATEST(0, IFNULL(due_refund_amount, 0) - IFNULL(refund_amount, 0)) AS refundPayAmount FROM adapay_unsplit_record WHERE - (settle_amount > confirmed_split_amount - payment_revoke_amount) - OR (due_refund_amount > refund_amount) + (IFNULL(pay_amount, 0) - IFNULL(due_refund_amount, 0) > IFNULL(confirmed_split_amount, 0)) + OR (IFNULL(due_refund_amount, 0) > IFNULL(refund_amount, 0)) - \ No newline at end of file + diff --git a/jsowell-quartz/src/main/java/com/jsowell/quartz/service/AdapayUnsplitRecordHandleService.java b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/AdapayUnsplitRecordHandleService.java new file mode 100644 index 000000000..4d01168b6 --- /dev/null +++ b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/AdapayUnsplitRecordHandleService.java @@ -0,0 +1,14 @@ +package com.jsowell.quartz.service; + +public interface AdapayUnsplitRecordHandleService { + + void processUnsplitRecordToDefaultMember(); + + void processUnsplitRecordToDefaultMember(String wechatAppId, Integer pageSize); + + void importAdapayUnsplitRecordAndCompleteFields(); + + void importAdapayUnsplitRecordAndCompleteFields(String filePath); + + int completeAdapayUnsplitRecordFields(String startTime, String endTime); +} diff --git a/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java new file mode 100644 index 000000000..b47d74d60 --- /dev/null +++ b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java @@ -0,0 +1,735 @@ +package com.jsowell.quartz.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.google.common.collect.Lists; +import com.jsowell.adapay.common.DivMember; +import com.jsowell.adapay.common.PaymentConfirmInfo; +import com.jsowell.adapay.dto.PaymentConfirmParam; +import com.jsowell.adapay.dto.QueryPaymentConfirmDTO; +import com.jsowell.adapay.response.PaymentConfirmResponse; +import com.jsowell.adapay.response.QueryPaymentConfirmDetailResponse; +import com.jsowell.adapay.service.AdapayService; +import com.jsowell.common.YouDianUtils; +import com.jsowell.common.constant.Constants; +import com.jsowell.common.enums.adapay.AdapayStatusEnum; +import com.jsowell.common.util.DateUtils; +import com.jsowell.common.util.PageUtils; +import com.jsowell.common.util.StringUtils; +import com.jsowell.pile.domain.AdapayUnsplitRecord; +import com.jsowell.pile.domain.OrderBasicInfo; +import com.jsowell.pile.dto.ApplyRefundDTO; +import com.jsowell.pile.service.AdapayUnsplitRecordService; +import com.jsowell.pile.service.OrderBasicInfoService; +import com.jsowell.pile.vo.AdapayUnsplitRecordVO; +import com.jsowell.pile.vo.web.OrderDetailInfoVO; +import com.jsowell.quartz.service.AdapayUnsplitRecordHandleService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecordHandleService { + + private static final int IMPORT_BATCH_SIZE = 500; + + private final Logger log = LoggerFactory.getLogger(AdapayUnsplitRecordHandleServiceImpl.class); + + @Autowired + private OrderBasicInfoService orderBasicInfoService; + + @Autowired + private AdapayService adapayService; + + @Autowired + private AdapayUnsplitRecordService adapayUnsplitRecordService; + + @Override + public void processUnsplitRecordToDefaultMember() { + processUnsplitRecordToDefaultMember(Constants.DEFAULT_APP_ID, 500); + } + + @Override + public void processUnsplitRecordToDefaultMember(String wechatAppId, Integer pageSize) { + int size = pageSize == null || pageSize <= 0 ? 500 : pageSize; + int pageNum = 1; + int total = 0; + int success = 0; + int skipped = 0; + int failed = 0; + + while (true) { + PageUtils.startPage(pageNum, size); + List list = adapayUnsplitRecordService.queryList(); + if (CollectionUtils.isEmpty(list)) { + break; + } + + log.info("处理未分账数据到默认账户, pageNum:{}, pageSize:{}, 当前页:{}条", pageNum, size, list.size()); + for (AdapayUnsplitRecordVO item : list) { + total++; + String paymentId = item.getPaymentId(); + String orderCode = item.getOrderCode(); + BigDecimal dueRefundAmount = parseAmount(item.getRefundAmount()); + BigDecimal waitSplitAmount = parseAmount(item.getWaitSplitAmount()); + + if (StringUtils.isBlank(paymentId) || StringUtils.isBlank(orderCode)) { + skipped++; + continue; + } + + if (dueRefundAmount.compareTo(BigDecimal.ZERO) > 0 && !ensureRefundBeforeSplit(item, wechatAppId)) { + skipped++; + continue; + } + + if (waitSplitAmount.compareTo(BigDecimal.ZERO) <= 0) { + skipped++; + continue; + } + + BigDecimal confirmAmt = getLatestConfirmAmount(waitSplitAmount, item.getPayAmount(), item.getRefundAmount(), paymentId, wechatAppId); + if (confirmAmt.compareTo(BigDecimal.ZERO) <= 0) { + skipped++; + continue; + } + + PaymentConfirmResponse response; + try { + DivMember divMember = new DivMember(); + divMember.setMemberId(Constants.ZERO); + divMember.setAmount(confirmAmt.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()); + divMember.setFeeFlag(Constants.Y); + + PaymentConfirmParam param = PaymentConfirmParam.builder() + .paymentId(paymentId) + .divMemberList(Lists.newArrayList(divMember)) + .confirmAmt(confirmAmt) + .orderCode(orderCode) + .wechatAppId(wechatAppId) + .build(); + response = adapayService.createPaymentConfirmRequest(param); + } catch (Exception e) { + failed++; + log.error("处理未分账数据到默认账户异常, paymentId:{}, orderCode:{}, confirmAmt:{}", + paymentId, orderCode, confirmAmt, e); + markSplitResult(paymentId, "FAILED"); + continue; + } + + if (response != null && response.isSuccess()) { + success++; + updateConfirmedSplitAmount(item, confirmAmt, paymentId); + markSplitResult(paymentId, "SUCCESS"); + log.info("处理未分账数据成功, paymentId:{}, orderCode:{}, confirmAmt:{}, response:{}", + paymentId, orderCode, confirmAmt, JSON.toJSONString(response)); + } else { + failed++; + String errorCode = response == null ? "response_null" : response.getError_code(); + String errorMsg = response == null ? "response_is_null" : response.getError_msg(); + log.error("处理未分账数据失败, paymentId:{}, orderCode:{}, confirmAmt:{}, errorCode:{}, errorMsg:{}", + paymentId, orderCode, confirmAmt, errorCode, errorMsg); + markSplitResult(paymentId, "FAILED"); + } + } + + if (list.size() < size) { + break; + } + pageNum++; + } + + log.info("处理未分账数据到默认账户结束, total:{}, success:{}, skipped:{}, failed:{}", + total, success, skipped, failed); + } + + @Override + public void importAdapayUnsplitRecordAndCompleteFields() { + importAdapayUnsplitRecordAndCompleteFields("doc/万车充小程序-未分账明细20260526.xlsx"); + } + + @Override + public void importAdapayUnsplitRecordAndCompleteFields(String filePath) { + Path path = Paths.get(filePath); + if (!path.isAbsolute()) { + path = Paths.get(System.getProperty("user.dir"), filePath); + } + if (!Files.exists(path)) { + log.error("导入未分账数据失败,文件不存在:{}", path.toAbsolutePath()); + return; + } + + ImportSummary summary = importAdapayUnsplitRecord(path); + if (summary.successRows == 0) { + log.info("导入未分账数据结束,未写入任何数据,统计:{}", summary); + return; + } + + String startTime = summary.minPayTime == null + ? "2024-01-01 00:00:00" + : DateUtils.formatDateTime(summary.minPayTime); + String endTime = summary.maxPayTime == null + ? DateUtils.getDateTime() + : DateUtils.formatDateTime(summary.maxPayTime); + + int updatedCount = completeUnsplitRecordMissingFields(startTime, endTime, 1000); + log.info("导入并补齐未分账数据完成, 导入统计:{}, 补齐更新:{}条", summary, updatedCount); + } + + @Override + public int completeAdapayUnsplitRecordFields(String startTime, String endTime) { + int updatedCount = completeUnsplitRecordMissingFields(startTime, endTime, 1000); + log.info("补齐未分账数据缺失字段完成, startTime:{}, endTime:{}, 更新:{}条", startTime, endTime, updatedCount); + return updatedCount; + } + + private ImportSummary importAdapayUnsplitRecord(Path filePath) { + ImportSummary summary = new ImportSummary(); + DataFormatter formatter = new DataFormatter(); + + try (InputStream inputStream = Files.newInputStream(filePath); + Workbook workbook = WorkbookFactory.create(inputStream)) { + Sheet sheet = workbook.getSheetAt(0); + if (sheet == null) { + log.error("导入未分账数据失败,Excel没有sheet, file:{}", filePath.toAbsolutePath()); + return summary; + } + + Row headerRow = sheet.getRow(sheet.getFirstRowNum()); + if (headerRow == null) { + log.error("导入未分账数据失败,Excel没有表头, file:{}", filePath.toAbsolutePath()); + return summary; + } + + Map headerIndexMap = buildHeaderIndexMap(headerRow, formatter); + List requiredHeaders = Lists.newArrayList( + "商户号", "支付时间", "交易流水号", "交易订单号", "交易订单金额", "已确认分账金额", "已撤销金额", "支付确认撤销金额", "剩余未分账金额" + ); + for (String requiredHeader : requiredHeaders) { + if (!headerIndexMap.containsKey(normalizeHeader(requiredHeader))) { + log.error("导入未分账数据失败,缺少字段:{}, file:{}", requiredHeader, filePath.toAbsolutePath()); + return summary; + } + } + + int firstDataRow = sheet.getFirstRowNum() + 1; + int lastDataRow = sheet.getLastRowNum(); + List batch = new ArrayList<>(IMPORT_BATCH_SIZE); + for (int rowNum = firstDataRow; rowNum <= lastDataRow; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null || isRowEmpty(row)) { + continue; + } + summary.totalRows++; + + try { + AdapayUnsplitRecord record = convertRowToRecord(row, headerIndexMap, formatter); + if (record == null) { + summary.skippedRows++; + continue; + } + batch.add(record); + summary.updatePayTimeRange(record.getPayTime()); + + if (batch.size() >= IMPORT_BATCH_SIZE) { + flushImportBatch(batch); + summary.successRows += batch.size(); + batch.clear(); + } + } catch (Exception e) { + summary.failedRows++; + log.error("导入未分账数据失败, rowNum:{}, file:{}", rowNum + 1, filePath.toAbsolutePath(), e); + } + + if (summary.totalRows % 1000 == 0) { + log.info("导入未分账数据进行中, total:{}, success:{}, skipped:{}, failed:{}", + summary.totalRows, summary.successRows, summary.skippedRows, summary.failedRows); + } + } + if (!batch.isEmpty()) { + flushImportBatch(batch); + summary.successRows += batch.size(); + } + } catch (Exception e) { + log.error("导入未分账数据失败, file:{}", filePath.toAbsolutePath(), e); + } + + return summary; + } + + private void flushImportBatch(List batch) { + if (CollectionUtils.isEmpty(batch)) { + return; + } + + Map uniqueByPaymentId = new LinkedHashMap<>(); + for (AdapayUnsplitRecord record : batch) { + if (StringUtils.isNotBlank(record.getPaymentId())) { + uniqueByPaymentId.put(record.getPaymentId(), record); + } + } + if (uniqueByPaymentId.isEmpty()) { + return; + } + + List paymentIds = new ArrayList<>(uniqueByPaymentId.keySet()); + List existingList = adapayUnsplitRecordService.selectByPaymentIds(paymentIds); + Map existingIdMap = new HashMap<>(); + if (CollectionUtils.isNotEmpty(existingList)) { + for (AdapayUnsplitRecord existing : existingList) { + existingIdMap.put(existing.getPaymentId(), existing.getId()); + } + } + + List insertList = new ArrayList<>(); + List updateList = new ArrayList<>(); + for (AdapayUnsplitRecord record : uniqueByPaymentId.values()) { + Integer existingId = existingIdMap.get(record.getPaymentId()); + if (existingId != null) { + record.setId(existingId); + updateList.add(record); + } else { + insertList.add(record); + } + } + + if (!insertList.isEmpty()) { + adapayUnsplitRecordService.batchInsert(insertList); + } + if (!updateList.isEmpty()) { + adapayUnsplitRecordService.updateBatchSelective(updateList); + } + } + + private int completeUnsplitRecordMissingFields(String startTime, String endTime, int pageSize) { + int pageNum = 1; + int updatedCount = 0; + + while (true) { + PageUtils.startPage(pageNum, pageSize); + List list = adapayUnsplitRecordService.queryUnsplitOrders(startTime, endTime); + if (CollectionUtils.isEmpty(list)) { + break; + } + + Set orderCodeSet = new HashSet<>(); + for (AdapayUnsplitRecord record : list) { + if (StringUtils.isBlank(record.getOrderCode())) { + String extractedOrderCode = extractOrderCode(record.getOrderNo()); + if (StringUtils.isNotBlank(extractedOrderCode)) { + record.setOrderCode(extractedOrderCode); + } + } + if (StringUtils.isNotBlank(record.getOrderCode())) { + orderCodeSet.add(record.getOrderCode()); + } + } + + Map orderMap = new HashMap<>(); + if (CollectionUtils.isNotEmpty(orderCodeSet)) { + List orderList = orderBasicInfoService.selectOrderTemp(orderCodeSet); + orderMap = orderList.stream() + .collect(Collectors.toMap(OrderBasicInfo::getOrderCode, v -> v, (k1, k2) -> k1)); + } + + List updateList = new ArrayList<>(); + Date now = DateUtils.getNowDate(); + for (AdapayUnsplitRecord record : list) { + boolean needUpdate = false; + + String orderCode = record.getOrderCode(); + if (StringUtils.isBlank(orderCode)) { + orderCode = extractOrderCode(record.getOrderNo()); + if (StringUtils.isNotBlank(orderCode)) { + record.setOrderCode(orderCode); + needUpdate = true; + } + } + + if (StringUtils.isNotBlank(orderCode)) { + OrderBasicInfo orderBasicInfo = orderMap.get(orderCode); + if (orderBasicInfo != null) { + BigDecimal refundAmount = orderBasicInfo.getRefundAmount(); + if (!isSameAmount(record.getDueRefundAmount(), refundAmount)) { + record.setDueRefundAmount(refundAmount); + needUpdate = true; + } + BigDecimal settleAmount = orderBasicInfo.getSettleAmount(); + if (!isSameAmount(record.getSettleAmount(), settleAmount)) { + record.setSettleAmount(settleAmount); + needUpdate = true; + } + String pileType = YouDianUtils.isEBikePileSn(orderBasicInfo.getPileSn()) ? "eBike" : "EV"; + if (!StringUtils.equals(record.getPileType(), pileType)) { + record.setPileType(pileType); + needUpdate = true; + } + } + } + + if (needUpdate) { + record.setUpdateTime(now); + updateList.add(record); + } + } + + if (CollectionUtils.isNotEmpty(updateList)) { + adapayUnsplitRecordService.updateBatchSelective(updateList); + updatedCount += updateList.size(); + } + + if (list.size() < pageSize) { + break; + } + pageNum++; + } + + return updatedCount; + } + + private boolean ensureRefundBeforeSplit(AdapayUnsplitRecordVO item, String wechatAppId) { + String orderCode = item.getOrderCode(); + String paymentId = item.getPaymentId(); + BigDecimal dueRefundAmount = parseAmount(item.getRefundAmount()); + if (dueRefundAmount.compareTo(BigDecimal.ZERO) <= 0) { + return true; + } + + OrderBasicInfo orderBasicInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode); + if (orderBasicInfo == null) { + log.warn("未分账数据退款前置校验失败,订单不存在, paymentId:{}, orderCode:{}", paymentId, orderCode); + markRefundResult(paymentId, "FAILED"); + return false; + } + + BigDecimal refundedAmount = getSucceededRefundAmount(orderBasicInfo); + updateRefundAmount(paymentId, refundedAmount); + if (refundedAmount.compareTo(dueRefundAmount) >= 0) { + markRefundResult(paymentId, "SUCCESS"); + return true; + } + + BigDecimal refundAmount = dueRefundAmount.subtract(refundedAmount).setScale(2, BigDecimal.ROUND_HALF_UP); + try { + ApplyRefundDTO dto = new ApplyRefundDTO(); + dto.setOrderCode(orderCode); + dto.setRefundType(Constants.ONE); + dto.setRefundAmount(refundAmount); + dto.setWechatAppId(wechatAppId); + dto.setMemberId(orderBasicInfo.getMemberId()); + orderBasicInfoService.refundOrderWithAdapay(dto); + markRefundResult(paymentId, "PROCESSING"); + log.info("未分账数据先执行退款, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, refundAmount:{}", + paymentId, orderCode, dueRefundAmount, refundedAmount, refundAmount); + } catch (Exception e) { + markRefundResult(paymentId, "FAILED"); + log.error("未分账数据执行退款失败, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, refundAmount:{}", + paymentId, orderCode, dueRefundAmount, refundedAmount, refundAmount, e); + } + return false; + } + + private BigDecimal getSucceededRefundAmount(OrderBasicInfo orderBasicInfo) { + List refundInfoList = orderBasicInfoService.getOrderRefundInfoList(orderBasicInfo); + if (CollectionUtils.isEmpty(refundInfoList)) { + return BigDecimal.ZERO; + } + + BigDecimal refundedAmount = BigDecimal.ZERO; + for (OrderDetailInfoVO.OrderRefundInfo refundInfo : refundInfoList) { + if (refundInfo == null) { + continue; + } + String status = refundInfo.getStatus(); + if (StringUtils.isNotBlank(status) && !StringUtils.equals(status, AdapayStatusEnum.SUCCEEDED.getValue())) { + continue; + } + refundedAmount = refundedAmount.add(parseAmount(refundInfo.getReverseAmt())); + } + return refundedAmount.setScale(2, BigDecimal.ROUND_HALF_UP); + } + + private BigDecimal getLatestConfirmAmount(BigDecimal waitSplitAmount, String payAmount, String refundAmount, String paymentId, String wechatAppId) { + BigDecimal confirmAmt = waitSplitAmount; + try { + QueryPaymentConfirmDTO dto = new QueryPaymentConfirmDTO(); + dto.setWechatAppId(wechatAppId); + dto.setPaymentId(paymentId); + QueryPaymentConfirmDetailResponse response = adapayService.queryPaymentConfirmList(dto); + BigDecimal latestRemaining = calculateLatestRemainingAmount(payAmount, refundAmount, response); + if (latestRemaining.compareTo(BigDecimal.ZERO) > 0) { + confirmAmt = waitSplitAmount.min(latestRemaining); + } + } catch (Exception e) { + log.warn("查询汇付确认金额异常,使用数据库待分账金额继续处理, paymentId:{}, waitSplitAmount:{}", + paymentId, waitSplitAmount, e); + } + return confirmAmt; + } + + private BigDecimal calculateLatestRemainingAmount(String payAmount, String refundAmount, QueryPaymentConfirmDetailResponse response) { + BigDecimal splitLimitAmount = parseAmount(payAmount).subtract(parseAmount(refundAmount)); + if (splitLimitAmount.compareTo(BigDecimal.ZERO) <= 0 || response == null || CollectionUtils.isEmpty(response.getPaymentConfirms())) { + return splitLimitAmount.compareTo(BigDecimal.ZERO) > 0 ? splitLimitAmount : BigDecimal.ZERO; + } + + BigDecimal maxConfirmedAmt = BigDecimal.ZERO; + BigDecimal maxReservedAmt = BigDecimal.ZERO; + List confirms = response.getPaymentConfirms(); + for (PaymentConfirmInfo confirm : confirms) { + BigDecimal confirmedAmt = parseAmount(confirm.getConfirmedAmt()); + BigDecimal reservedAmt = parseAmount(confirm.getReservedAmt()); + if (confirmedAmt.compareTo(maxConfirmedAmt) > 0) { + maxConfirmedAmt = confirmedAmt; + } + if (reservedAmt.compareTo(maxReservedAmt) > 0) { + maxReservedAmt = reservedAmt; + } + } + BigDecimal latestRemaining = splitLimitAmount.subtract(maxConfirmedAmt).subtract(maxReservedAmt); + return latestRemaining.compareTo(BigDecimal.ZERO) > 0 ? latestRemaining : BigDecimal.ZERO; + } + + private void updateConfirmedSplitAmount(AdapayUnsplitRecordVO item, BigDecimal confirmAmt, String paymentId) { + BigDecimal oldConfirmedAmt = parseAmount(item.getConfirmedSplitAmount()); + BigDecimal newConfirmedAmt = oldConfirmedAmt.add(confirmAmt).setScale(2, BigDecimal.ROUND_HALF_UP); + AdapayUnsplitRecord updateRecord = new AdapayUnsplitRecord(); + updateRecord.setPaymentId(paymentId); + updateRecord.setConfirmedSplitAmount(newConfirmedAmt); + updateRecord.setUpdateTime(DateUtils.getNowDate()); + adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); + } + + private void markSplitResult(String paymentId, String splitFlag) { + AdapayUnsplitRecord updateRecord = new AdapayUnsplitRecord(); + updateRecord.setPaymentId(paymentId); + updateRecord.setSplitFlag(splitFlag); + updateRecord.setUpdateTime(DateUtils.getNowDate()); + adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); + } + + private void updateRefundAmount(String paymentId, BigDecimal refundAmount) { + AdapayUnsplitRecord updateRecord = new AdapayUnsplitRecord(); + updateRecord.setPaymentId(paymentId); + updateRecord.setRefundAmount(refundAmount.setScale(2, BigDecimal.ROUND_HALF_UP)); + updateRecord.setUpdateTime(DateUtils.getNowDate()); + adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); + } + + private void markRefundResult(String paymentId, String refundFlag) { + AdapayUnsplitRecord updateRecord = new AdapayUnsplitRecord(); + updateRecord.setPaymentId(paymentId); + updateRecord.setRefundFlag(refundFlag); + updateRecord.setUpdateTime(DateUtils.getNowDate()); + adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); + } + + private AdapayUnsplitRecord convertRowToRecord(Row row, Map headerIndexMap, DataFormatter formatter) { + String paymentId = getCellString(row, headerIndexMap.get(normalizeHeader("交易流水号")), formatter); + if (StringUtils.isBlank(paymentId)) { + return null; + } + + String orderNo = getCellString(row, headerIndexMap.get(normalizeHeader("交易订单号")), formatter); + Date payTime = parsePayTime(getCell(row, headerIndexMap.get(normalizeHeader("支付时间"))), formatter); + + AdapayUnsplitRecord record = new AdapayUnsplitRecord(); + record.setMerchantCode(getCellString(row, headerIndexMap.get(normalizeHeader("商户号")), formatter)); + record.setPayTime(payTime); + record.setPaymentId(paymentId); + record.setOrderNo(orderNo); + record.setOrderCode(extractOrderCode(orderNo)); + record.setPayAmount(getCellDecimal(row, headerIndexMap.get(normalizeHeader("交易订单金额")), formatter)); + record.setConfirmedSplitAmount(getCellDecimal(row, headerIndexMap.get(normalizeHeader("已确认分账金额")), formatter)); + record.setRefundAmount(getCellDecimal(row, headerIndexMap.get(normalizeHeader("已撤销金额")), formatter)); + record.setPaymentRevokeAmount(getCellDecimal(row, headerIndexMap.get(normalizeHeader("支付确认撤销金额")), formatter)); + record.setRemainingSplitAmount(getCellDecimal(row, headerIndexMap.get(normalizeHeader("剩余未分账金额")), formatter)); + record.setUpdateTime(DateUtils.getNowDate()); + return record; + } + + private Map buildHeaderIndexMap(Row headerRow, DataFormatter formatter) { + Map headerIndexMap = new HashMap<>(); + short firstCellNum = headerRow.getFirstCellNum(); + short lastCellNum = headerRow.getLastCellNum(); + if (firstCellNum < 0 || lastCellNum < 0) { + return headerIndexMap; + } + for (int cellIndex = firstCellNum; cellIndex < lastCellNum; cellIndex++) { + Cell cell = headerRow.getCell(cellIndex, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL); + if (cell == null) { + continue; + } + String header = normalizeHeader(formatter.formatCellValue(cell)); + if (StringUtils.isNotBlank(header)) { + headerIndexMap.put(header, cellIndex); + } + } + return headerIndexMap; + } + + private String getCellString(Row row, Integer columnIndex, DataFormatter formatter) { + Cell cell = getCell(row, columnIndex); + if (cell == null) { + return null; + } + String value = formatter.formatCellValue(cell); + return StringUtils.isBlank(value) ? null : value.trim(); + } + + private Cell getCell(Row row, Integer columnIndex) { + if (row == null || columnIndex == null) { + return null; + } + return row.getCell(columnIndex, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL); + } + + private BigDecimal getCellDecimal(Row row, Integer columnIndex, DataFormatter formatter) { + String value = getCellString(row, columnIndex, formatter); + return parseAmount(value); + } + + private Date parsePayTime(Cell cell, DataFormatter formatter) { + if (cell == null) { + return null; + } + if (cell.getCellType() == CellType.NUMERIC) { + return DateUtil.getJavaDate(cell.getNumericCellValue()); + } + if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.NUMERIC) { + return DateUtil.getJavaDate(cell.getNumericCellValue()); + } + + String value = formatter.formatCellValue(cell); + if (StringUtils.isBlank(value)) { + return null; + } + Date date = DateUtils.parseDate(value.trim()); + if (date != null) { + return date; + } + try { + return DateUtil.getJavaDate(Double.parseDouble(value.trim())); + } catch (Exception e) { + log.warn("解析支付时间失败,value:{}", value); + return null; + } + } + + private String extractOrderCode(String orderNo) { + if (StringUtils.isBlank(orderNo)) { + return null; + } + int index = orderNo.indexOf("_"); + String orderCode = index > 0 ? orderNo.substring(0, index) : orderNo; + if (orderCode.length() >= 16) { + log.warn("order_code 字段长度超出限制,order_no:{}", orderNo); + } + return orderCode.length() <= 16 ? orderCode : null; + } + + private String normalizeHeader(String header) { + return header == null ? "" : header.replace(" ", "").trim(); + } + + private boolean isSameAmount(BigDecimal left, BigDecimal right) { + if (left == null && right == null) { + return true; + } + if (left == null || right == null) { + return false; + } + return left.compareTo(right) == 0; + } + + private BigDecimal parseAmount(String value) { + if (StringUtils.isBlank(value)) { + return BigDecimal.ZERO; + } + String normalizedValue = value.replace(",", "").trim(); + try { + return new BigDecimal(normalizedValue); + } catch (NumberFormatException e) { + log.warn("解析数字失败,value:{}", value); + return BigDecimal.ZERO; + } + } + + private boolean isRowEmpty(Row row) { + if (row == null) { + return true; + } + short firstCellNum = row.getFirstCellNum(); + short lastCellNum = row.getLastCellNum(); + if (firstCellNum < 0 || lastCellNum < 0) { + return true; + } + for (int i = firstCellNum; i < lastCellNum; i++) { + Cell cell = row.getCell(i, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL); + if (cell == null) { + continue; + } + if (cell.getCellType() != CellType.BLANK) { + return false; + } + } + return true; + } + + private static class ImportSummary { + private int totalRows; + private int successRows; + private int skippedRows; + private int failedRows; + private Date minPayTime; + private Date maxPayTime; + + private void updatePayTimeRange(Date payTime) { + if (payTime == null) { + return; + } + if (minPayTime == null || payTime.before(minPayTime)) { + minPayTime = payTime; + } + if (maxPayTime == null || payTime.after(maxPayTime)) { + maxPayTime = payTime; + } + } + + @Override + public String toString() { + return "{" + + "\"totalRows\":" + totalRows + + ", \"successRows\":" + successRows + + ", \"skippedRows\":" + skippedRows + + ", \"failedRows\":" + failedRows + + ", \"minPayTime\":\"" + (minPayTime == null ? "" : DateUtils.formatDateTime(minPayTime)) + "\"" + + ", \"maxPayTime\":\"" + (maxPayTime == null ? "" : DateUtils.formatDateTime(maxPayTime)) + "\"" + + "}"; + } + } +} diff --git a/jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java b/jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java index af2012706..f0d919e3c 100644 --- a/jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java +++ b/jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java @@ -15,6 +15,7 @@ import com.jsowell.common.constant.CacheConstants; import com.jsowell.common.constant.Constants; import com.jsowell.common.core.redis.RedisCache; import com.jsowell.common.enums.SoftwareProtocolEnum; +import com.jsowell.common.enums.adapay.AdapayStatusEnum; import com.jsowell.common.enums.thirdparty.ThirdPlatformTypeEnum; import com.jsowell.common.enums.ykc.PileChannelEntity; import com.jsowell.common.util.DateUtils; @@ -26,6 +27,7 @@ import com.jsowell.pile.domain.OrderBasicInfo; import com.jsowell.pile.domain.PileBasicInfo; import com.jsowell.pile.domain.PileMerchantInfo; import com.jsowell.pile.domain.PileStationInfo; +import com.jsowell.pile.dto.ApplyRefundDTO; import com.jsowell.pile.domain.ykcCommond.PublishPileBillingTemplateCommand; import com.jsowell.pile.domain.ykcCommond.ProofreadTimeCommand; import com.jsowell.pile.domain.ykcCommond.StartChargingCommand; @@ -33,6 +35,8 @@ import com.jsowell.pile.service.*; import com.jsowell.pile.vo.AdapayUnsplitRecordVO; import com.jsowell.pile.vo.base.StationInfoVO; import com.jsowell.pile.vo.web.BillingTemplateVO; +import com.jsowell.pile.vo.web.OrderDetailInfoVO; +import com.jsowell.quartz.service.AdapayUnsplitRecordHandleService; import com.jsowell.thirdparty.amap.service.AMapService; import com.jsowell.thirdparty.common.NotificationDTO; import com.jsowell.thirdparty.common.NotificationService; @@ -100,6 +104,9 @@ public class JsowellTask { @Autowired private AdapayUnsplitRecordService adapayUnsplitRecordService; + @Autowired + private AdapayUnsplitRecordHandleService adapayUnsplitRecordHandleService; + private static final long YKC_DAILY_TIMECHECK_INTERVAL_MILLIS = 200L; /** @@ -514,7 +521,7 @@ public class JsowellTask { * jsowellTask.processUnsplitRecordToDefaultMember() */ public void processUnsplitRecordToDefaultMember() { - processUnsplitRecordToDefaultMember(Constants.DEFAULT_APP_ID, 500); + adapayUnsplitRecordHandleService.processUnsplitRecordToDefaultMember(); } /** @@ -522,85 +529,7 @@ public class JsowellTask { * jsowellTask.processUnsplitRecordToDefaultMember(wechatAppId, pageSize) */ public void processUnsplitRecordToDefaultMember(String wechatAppId, Integer pageSize) { - int size = pageSize == null || pageSize <= 0 ? 500 : pageSize; - int pageNum = 1; - int total = 0; - int success = 0; - int skipped = 0; - int failed = 0; - - while (true) { - PageUtils.startPage(pageNum, size); - List list = adapayUnsplitRecordService.queryList(); - if (CollectionUtils.isEmpty(list)) { - break; - } - - log.info("处理未分账数据到默认账户, pageNum:{}, pageSize:{}, 当前页:{}条", pageNum, size, list.size()); - for (AdapayUnsplitRecordVO item : list) { - total++; - String paymentId = item.getPaymentId(); - String orderCode = item.getOrderCode(); - BigDecimal waitSplitAmount = parseAmount(item.getWaitSplitAmount()); - - if (StringUtils.isBlank(paymentId) || waitSplitAmount.compareTo(BigDecimal.ZERO) <= 0) { - skipped++; - continue; - } - - BigDecimal confirmAmt = getLatestConfirmAmount(waitSplitAmount, item.getPayAmount(), paymentId, wechatAppId); - if (confirmAmt.compareTo(BigDecimal.ZERO) <= 0) { - skipped++; - continue; - } - - PaymentConfirmResponse response; - try { - DivMember divMember = new DivMember(); - divMember.setMemberId(Constants.ZERO); - divMember.setAmount(confirmAmt.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()); - divMember.setFeeFlag(Constants.Y); - - PaymentConfirmParam param = PaymentConfirmParam.builder() - .paymentId(paymentId) - .divMemberList(Lists.newArrayList(divMember)) - .confirmAmt(confirmAmt) - .orderCode(orderCode) - .wechatAppId(wechatAppId) - .build(); - response = adapayService.createPaymentConfirmRequest(param); - } catch (Exception e) { - failed++; - log.error("处理未分账数据到默认账户异常, paymentId:{}, orderCode:{}, confirmAmt:{}", - paymentId, orderCode, confirmAmt, e); - markSplitResult(paymentId, "FAILED"); - continue; - } - - if (response != null && response.isSuccess()) { - success++; - updateConfirmedSplitAmount(item, confirmAmt, paymentId); - markSplitResult(paymentId, "SUCCESS"); - log.info("处理未分账数据成功, paymentId:{}, orderCode:{}, confirmAmt:{}, response:{}", - paymentId, orderCode, confirmAmt, JSON.toJSONString(response)); - } else { - failed++; - String errorCode = response == null ? "response_null" : response.getError_code(); - String errorMsg = response == null ? "response_is_null" : response.getError_msg(); - log.error("处理未分账数据失败, paymentId:{}, orderCode:{}, confirmAmt:{}, errorCode:{}, errorMsg:{}", - paymentId, orderCode, confirmAmt, errorCode, errorMsg); - markSplitResult(paymentId, "FAILED"); - } - } - - if (list.size() < size) { - break; - } - pageNum++; - } - - log.info("处理未分账数据到默认账户结束, total:{}, success:{}, skipped:{}, failed:{}", - total, success, skipped, failed); + adapayUnsplitRecordHandleService.processUnsplitRecordToDefaultMember(wechatAppId, pageSize); } /** @@ -614,7 +543,7 @@ public class JsowellTask { * jsowellTask.importAdapayUnsplitRecordAndCompleteFields() */ public void importAdapayUnsplitRecordAndCompleteFields() { - importAdapayUnsplitRecordAndCompleteFields("doc/万车充小程序-未分账明细20260526.xlsx"); + adapayUnsplitRecordHandleService.importAdapayUnsplitRecordAndCompleteFields(); } /** @@ -624,8 +553,7 @@ public class JsowellTask { * 示例:jsowellTask.completeAdapayUnsplitRecordFields('2024-01-01 00:00:00', '2025-12-31 23:59:59') */ public void completeAdapayUnsplitRecordFields(String startTime, String endTime) { - int updatedCount = completeUnsplitRecordMissingFields(startTime, endTime, 1000); - log.info("补齐未分账数据缺失字段完成, startTime:{}, endTime:{}, 更新:{}条", startTime, endTime, updatedCount); + adapayUnsplitRecordHandleService.completeAdapayUnsplitRecordFields(startTime, endTime); } /** @@ -638,35 +566,7 @@ public class JsowellTask { * jsowellTask.importAdapayUnsplitRecordAndCompleteFields(文件路径) */ public void importAdapayUnsplitRecordAndCompleteFields(String filePath) { - // 相对路径转绝对路径(基于 JVM 工作目录) - Path path = Paths.get(filePath); - if (!path.isAbsolute()) { - path = Paths.get(System.getProperty("user.dir"), filePath); - } - if (!Files.exists(path)) { - log.error("导入未分账数据失败,文件不存在:{}", path.toAbsolutePath()); - return; - } - - // 第一步:读取Excel,将每行数据 insertOrUpdate 到 adapay_unsplit_record 表 - ImportSummary summary = importAdapayUnsplitRecord(path); - - if (summary.successRows == 0) { - log.info("导入未分账数据结束,未写入任何数据,统计:{}", summary); - return; - } - - // 第二步:以导入数据中最早/最晚支付时间为范围,补齐已入库记录的缺失字段 - // 若 Excel 中无有效支付时间,则使用兜底时间范围(2024-01-01 至当前时间) - String startTime = summary.minPayTime == null - ? "2024-01-01 00:00:00" - : DateUtils.formatDateTime(summary.minPayTime); - String endTime = summary.maxPayTime == null - ? DateUtils.getDateTime() - : DateUtils.formatDateTime(summary.maxPayTime); - - int updatedCount = completeUnsplitRecordMissingFields(startTime, endTime, 1000); - log.info("导入并补齐未分账数据完成, 导入统计:{}, 补齐更新:{}条", summary, updatedCount); + adapayUnsplitRecordHandleService.importAdapayUnsplitRecordAndCompleteFields(filePath); } private static final int IMPORT_BATCH_SIZE = 500; @@ -885,14 +785,76 @@ public class JsowellTask { return updatedCount; } - private BigDecimal getLatestConfirmAmount(BigDecimal waitSplitAmount, String payAmount, String paymentId, String wechatAppId) { + private boolean ensureRefundBeforeSplit(AdapayUnsplitRecordVO item, String wechatAppId) { + String orderCode = item.getOrderCode(); + String paymentId = item.getPaymentId(); + BigDecimal dueRefundAmount = parseAmount(item.getRefundAmount()); + if (dueRefundAmount.compareTo(BigDecimal.ZERO) <= 0) { + return true; + } + + OrderBasicInfo orderBasicInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode); + if (orderBasicInfo == null) { + log.warn("未分账数据退款前置校验失败,订单不存在, paymentId:{}, orderCode:{}", paymentId, orderCode); + markRefundResult(paymentId, "FAILED"); + return false; + } + + BigDecimal refundedAmount = getSucceededRefundAmount(orderBasicInfo); + updateRefundAmount(paymentId, refundedAmount); + if (refundedAmount.compareTo(dueRefundAmount) >= 0) { + markRefundResult(paymentId, "SUCCESS"); + return true; + } + + BigDecimal refundAmount = dueRefundAmount.subtract(refundedAmount).setScale(2, BigDecimal.ROUND_HALF_UP); + try { + ApplyRefundDTO dto = new ApplyRefundDTO(); + dto.setOrderCode(orderCode); + dto.setRefundType(Constants.ONE); + dto.setRefundAmount(refundAmount); + dto.setWechatAppId(wechatAppId); + dto.setMemberId(orderBasicInfo.getMemberId()); + orderBasicInfoService.refundOrderWithAdapay(dto); + markRefundResult(paymentId, "PROCESSING"); + log.info("未分账数据先执行退款, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, refundAmount:{}", + paymentId, orderCode, dueRefundAmount, refundedAmount, refundAmount); + } catch (Exception e) { + markRefundResult(paymentId, "FAILED"); + log.error("未分账数据执行退款失败, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, refundAmount:{}", + paymentId, orderCode, dueRefundAmount, refundedAmount, refundAmount, e); + } + return false; + } + + private BigDecimal getSucceededRefundAmount(OrderBasicInfo orderBasicInfo) { + List refundInfoList = orderBasicInfoService.getOrderRefundInfoList(orderBasicInfo); + if (CollectionUtils.isEmpty(refundInfoList)) { + return BigDecimal.ZERO; + } + + BigDecimal refundedAmount = BigDecimal.ZERO; + for (OrderDetailInfoVO.OrderRefundInfo refundInfo : refundInfoList) { + if (refundInfo == null) { + continue; + } + String status = refundInfo.getStatus(); + if (StringUtils.isNotBlank(status) && !StringUtils.equals(status, AdapayStatusEnum.SUCCEEDED.getValue())) { + continue; + } + refundedAmount = refundedAmount.add(parseAmount(refundInfo.getReverseAmt())); + } + return refundedAmount.setScale(2, BigDecimal.ROUND_HALF_UP); + } + + private BigDecimal getLatestConfirmAmount(BigDecimal waitSplitAmount, String payAmount, String refundAmount, String paymentId, String wechatAppId) { BigDecimal confirmAmt = waitSplitAmount; try { QueryPaymentConfirmDTO dto = new QueryPaymentConfirmDTO(); dto.setWechatAppId(wechatAppId); dto.setPaymentId(paymentId); QueryPaymentConfirmDetailResponse response = adapayService.queryPaymentConfirmList(dto); - BigDecimal latestRemaining = calculateLatestRemainingAmount(payAmount, response); + BigDecimal latestRemaining = calculateLatestRemainingAmount(payAmount, refundAmount, response); if (latestRemaining.compareTo(BigDecimal.ZERO) > 0) { confirmAmt = waitSplitAmount.min(latestRemaining); } @@ -903,10 +865,10 @@ public class JsowellTask { return confirmAmt; } - private BigDecimal calculateLatestRemainingAmount(String payAmount, QueryPaymentConfirmDetailResponse response) { - BigDecimal payAmt = parseAmount(payAmount); - if (payAmt.compareTo(BigDecimal.ZERO) <= 0 || response == null || CollectionUtils.isEmpty(response.getPaymentConfirms())) { - return payAmt; + private BigDecimal calculateLatestRemainingAmount(String payAmount, String refundAmount, QueryPaymentConfirmDetailResponse response) { + BigDecimal splitLimitAmount = parseAmount(payAmount).subtract(parseAmount(refundAmount)); + if (splitLimitAmount.compareTo(BigDecimal.ZERO) <= 0 || response == null || CollectionUtils.isEmpty(response.getPaymentConfirms())) { + return splitLimitAmount.compareTo(BigDecimal.ZERO) > 0 ? splitLimitAmount : BigDecimal.ZERO; } BigDecimal maxConfirmedAmt = BigDecimal.ZERO; @@ -922,7 +884,7 @@ public class JsowellTask { maxReservedAmt = reservedAmt; } } - BigDecimal latestRemaining = payAmt.subtract(maxConfirmedAmt).subtract(maxReservedAmt); + BigDecimal latestRemaining = splitLimitAmount.subtract(maxConfirmedAmt).subtract(maxReservedAmt); return latestRemaining.compareTo(BigDecimal.ZERO) > 0 ? latestRemaining : BigDecimal.ZERO; } @@ -944,6 +906,22 @@ public class JsowellTask { adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); } + private void updateRefundAmount(String paymentId, BigDecimal refundAmount) { + AdapayUnsplitRecord updateRecord = new AdapayUnsplitRecord(); + updateRecord.setPaymentId(paymentId); + updateRecord.setRefundAmount(refundAmount.setScale(2, BigDecimal.ROUND_HALF_UP)); + updateRecord.setUpdateTime(DateUtils.getNowDate()); + adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); + } + + private void markRefundResult(String paymentId, String refundFlag) { + AdapayUnsplitRecord updateRecord = new AdapayUnsplitRecord(); + updateRecord.setPaymentId(paymentId); + updateRecord.setRefundFlag(refundFlag); + updateRecord.setUpdateTime(DateUtils.getNowDate()); + adapayUnsplitRecordService.insertOrUpdateSelective(updateRecord); + } + private AdapayUnsplitRecord convertRowToRecord(Row row, Map headerIndexMap, DataFormatter formatter) { String paymentId = getCellString(row, headerIndexMap.get(normalizeHeader("交易流水号")), formatter); if (StringUtils.isBlank(paymentId)) {