diff --git a/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml index f64b5bc50..bde27a10b 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/AdapayUnsplitRecordMapper.xml @@ -756,7 +756,7 @@ GREATEST(0, IFNULL(due_refund_amount, 0) - IFNULL(refund_amount, 0)) AS refundPayAmount FROM adapay_unsplit_record WHERE - (IFNULL(pay_amount, 0) - IFNULL(due_refund_amount, 0) > IFNULL(confirmed_split_amount, 0)) - OR (IFNULL(due_refund_amount, 0) > IFNULL(refund_amount, 0)) + (refund_flag IS NULL OR refund_flag != 'SUCCESS') + OR (split_flag IS NULL OR split_flag != 'SUCCESS') 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 index 6a8b47dea..a2094918d 100644 --- a/jsowell-quartz/src/main/java/com/jsowell/quartz/service/AdapayUnsplitRecordHandleService.java +++ b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/AdapayUnsplitRecordHandleService.java @@ -12,5 +12,9 @@ public interface AdapayUnsplitRecordHandleService { int completeAdapayUnsplitRecordFields(String startTime, String endTime); + int refreshAdapayUnsplitRecordHandleFlag(String paymentId, String wechatAppId); + + int refreshAdapayUnsplitRecordHandleFlag(String startTime, String endTime, String wechatAppId, Integer pageSize); + void processUnSettledOrder(); } 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 index c60743942..6c8f7af55 100644 --- 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 @@ -58,6 +58,9 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord private static final int IMPORT_BATCH_SIZE = 500; private static final int REFUND_WAIT_MAX_ATTEMPTS = 12; private static final long REFUND_WAIT_INTERVAL_MILLIS = 5000L; + private static final String HANDLE_FLAG_SUCCESS = "SUCCESS"; + private static final String HANDLE_FLAG_PROCESSING = "PROCESSING"; + private static final String HANDLE_FLAG_FAILED = "FAILED"; private final Logger log = LoggerFactory.getLogger(AdapayUnsplitRecordHandleServiceImpl.class); @@ -104,6 +107,7 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord total++; String paymentId = item.getPaymentId(); String orderCode = item.getOrderCode(); + // queryList 中 refundAmount 映射的是 due_refund_amount,即订单应退款金额。 BigDecimal dueRefundAmount = parseAmount(item.getRefundAmount()); BigDecimal waitSplitAmount = parseAmount(item.getWaitSplitAmount()); @@ -112,18 +116,22 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord continue; } + // 有应退款金额时,必须先确认退款已足额成功;未退足会先发起差额退款并等待,未足额则本轮不分账。 if (dueRefundAmount.compareTo(BigDecimal.ZERO) > 0 && !ensureRefundBeforeSplit(item, wechatAppId)) { + refreshHandleFlagQuietly(paymentId, wechatAppId); skipped++; continue; } if (waitSplitAmount.compareTo(BigDecimal.ZERO) <= 0) { + refreshHandleFlagQuietly(paymentId, wechatAppId); skipped++; continue; } BigDecimal confirmAmt = getLatestConfirmAmount(waitSplitAmount, item.getPayAmount(), item.getRefundAmount(), paymentId, wechatAppId); if (confirmAmt.compareTo(BigDecimal.ZERO) <= 0) { + refreshHandleFlagQuietly(paymentId, wechatAppId); skipped++; continue; } @@ -147,14 +155,15 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord failed++; log.error("处理未分账数据到默认账户异常, paymentId:{}, orderCode:{}, confirmAmt:{}", paymentId, orderCode, confirmAmt, e); - markSplitResult(paymentId, "FAILED"); + markSplitResult(paymentId, HANDLE_FLAG_FAILED); + refreshHandleFlagQuietly(paymentId, wechatAppId); continue; } if (response != null && response.isSuccess()) { success++; updateConfirmedSplitAmount(item, confirmAmt, paymentId); - markSplitResult(paymentId, "SUCCESS"); + refreshHandleFlagQuietly(paymentId, wechatAppId); log.info("处理未分账数据成功, paymentId:{}, orderCode:{}, confirmAmt:{}, response:{}", paymentId, orderCode, confirmAmt, JSON.toJSONString(response)); } else { @@ -163,7 +172,8 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord 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"); + markSplitResult(paymentId, HANDLE_FLAG_FAILED); + refreshHandleFlagQuietly(paymentId, wechatAppId); } } @@ -217,6 +227,44 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord return updatedCount; } + @Override + public int refreshAdapayUnsplitRecordHandleFlag(String paymentId, String wechatAppId) { + if (StringUtils.isBlank(paymentId)) { + return 0; + } + List list = adapayUnsplitRecordService.selectByPaymentIds(Lists.newArrayList(paymentId)); + if (CollectionUtils.isEmpty(list)) { + log.warn("刷新未分账处理标识失败,记录不存在, paymentId:{}", paymentId); + return 0; + } + return refreshUnsplitRecordHandleFlag(list, StringUtils.isBlank(wechatAppId) ? Constants.DEFAULT_APP_ID : wechatAppId); + } + + @Override + public int refreshAdapayUnsplitRecordHandleFlag(String startTime, String endTime, String wechatAppId, Integer pageSize) { + int size = pageSize == null || pageSize <= 0 ? 1000 : pageSize; + int pageNum = 1; + int updatedCount = 0; + String appId = StringUtils.isBlank(wechatAppId) ? Constants.DEFAULT_APP_ID : wechatAppId; + + while (true) { + PageUtils.startPage(pageNum, size); + List list = adapayUnsplitRecordService.queryUnsplitOrders(startTime, endTime); + if (CollectionUtils.isEmpty(list)) { + break; + } + + updatedCount += refreshUnsplitRecordHandleFlag(list, appId); + if (list.size() < size) { + break; + } + pageNum++; + } + + log.info("刷新未分账处理标识完成, startTime:{}, endTime:{}, 更新:{}条", startTime, endTime, updatedCount); + return updatedCount; + } + /** * V1方法,获取退款金额与结算金额 */ @@ -481,9 +529,177 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord return updatedCount; } + private int refreshUnsplitRecordHandleFlag(List list, String wechatAppId) { + if (CollectionUtils.isEmpty(list)) { + return 0; + } + + Set orderCodeSet = new HashSet<>(); + for (AdapayUnsplitRecord record : list) { + String orderCode = record.getOrderCode(); + if (StringUtils.isBlank(orderCode)) { + orderCode = extractOrderCode(record.getOrderNo()); + } + if (StringUtils.isNotBlank(orderCode)) { + orderCodeSet.add(orderCode); + } + } + + 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) { + if (record == null || StringUtils.isBlank(record.getPaymentId())) { + continue; + } + + boolean needUpdate = false; + String paymentId = record.getPaymentId(); + String orderCode = record.getOrderCode(); + if (StringUtils.isBlank(orderCode)) { + orderCode = extractOrderCode(record.getOrderNo()); + if (StringUtils.isNotBlank(orderCode)) { + record.setOrderCode(orderCode); + needUpdate = true; + } + } + + OrderBasicInfo orderBasicInfo = StringUtils.isBlank(orderCode) ? null : orderMap.get(orderCode); + if (orderBasicInfo != null) { + if (!isSameAmount(record.getDueRefundAmount(), orderBasicInfo.getRefundAmount())) { + record.setDueRefundAmount(defaultAmount(orderBasicInfo.getRefundAmount())); + needUpdate = true; + } + if (!isSameAmount(record.getSettleAmount(), orderBasicInfo.getSettleAmount())) { + record.setSettleAmount(orderBasicInfo.getSettleAmount()); + needUpdate = true; + } + String pileType = YouDianUtils.isEBikePileSn(orderBasicInfo.getPileSn()) ? "eBike" : "EV"; + if (!StringUtils.equals(record.getPileType(), pileType)) { + record.setPileType(pileType); + needUpdate = true; + } + } + + BigDecimal dueRefundAmount = defaultAmount(record.getDueRefundAmount()); + RefundAmountCheck refundAmountCheck = checkRefundAmount(orderBasicInfo, dueRefundAmount); + if (!isSameAmount(record.getRefundAmount(), refundAmountCheck.refundedAmount)) { + record.setRefundAmount(refundAmountCheck.refundedAmount); + needUpdate = true; + } + String refundFlag = calculateHandleFlag(dueRefundAmount, refundAmountCheck.refundedAmount, refundAmountCheck.acceptedRefundAmount); + if (!StringUtils.equals(record.getRefundFlag(), refundFlag)) { + record.setRefundFlag(refundFlag); + needUpdate = true; + } + + BigDecimal expectedSplitAmount = defaultAmount(record.getPayAmount()).subtract(dueRefundAmount).setScale(2, BigDecimal.ROUND_HALF_UP); + if (expectedSplitAmount.compareTo(BigDecimal.ZERO) < 0) { + expectedSplitAmount = BigDecimal.ZERO; + } + SplitAmountCheck splitAmountCheck = checkSplitAmount(paymentId, wechatAppId); + BigDecimal confirmedSplitAmount = defaultAmount(record.getConfirmedSplitAmount()).max(splitAmountCheck.confirmedSplitAmount); + if (!isSameAmount(record.getConfirmedSplitAmount(), confirmedSplitAmount)) { + record.setConfirmedSplitAmount(confirmedSplitAmount); + needUpdate = true; + } + String splitFlag = calculateHandleFlag(expectedSplitAmount, confirmedSplitAmount, confirmedSplitAmount.add(splitAmountCheck.reservedSplitAmount)); + if (!StringUtils.equals(record.getSplitFlag(), splitFlag)) { + record.setSplitFlag(splitFlag); + needUpdate = true; + } + + if (needUpdate) { + record.setUpdateTime(now); + updateList.add(record); + } + log.info("刷新未分账处理标识, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, expectedSplitAmount:{}, confirmedSplitAmount:{}, refundFlag:{}, splitFlag:{}", + paymentId, orderCode, dueRefundAmount, refundAmountCheck.refundedAmount, expectedSplitAmount, confirmedSplitAmount, refundFlag, splitFlag); + } + + if (CollectionUtils.isNotEmpty(updateList)) { + adapayUnsplitRecordService.updateBatchSelective(updateList); + return updateList.size(); + } + return 0; + } + + private void refreshHandleFlagQuietly(String paymentId, String wechatAppId) { + try { + refreshAdapayUnsplitRecordHandleFlag(paymentId, wechatAppId); + } catch (Exception e) { + log.warn("刷新未分账处理标识异常,本轮继续处理后续记录, paymentId:{}", paymentId, e); + } + } + + private RefundAmountCheck checkRefundAmount(OrderBasicInfo orderBasicInfo, BigDecimal dueRefundAmount) { + if (dueRefundAmount.compareTo(BigDecimal.ZERO) <= 0) { + return new RefundAmountCheck(BigDecimal.ZERO, BigDecimal.ZERO); + } + if (orderBasicInfo == null) { + return new RefundAmountCheck(BigDecimal.ZERO, BigDecimal.ZERO); + } + BigDecimal refundedAmount = getRefundedAmount(orderBasicInfo, false); + BigDecimal acceptedRefundAmount = getRefundedAmount(orderBasicInfo, true); + return new RefundAmountCheck(refundedAmount, acceptedRefundAmount); + } + + private SplitAmountCheck checkSplitAmount(String paymentId, String wechatAppId) { + try { + QueryPaymentConfirmDTO dto = new QueryPaymentConfirmDTO(); + dto.setWechatAppId(wechatAppId); + dto.setPaymentId(paymentId); + QueryPaymentConfirmDetailResponse response = adapayService.queryPaymentConfirmList(dto); + if (response == null || CollectionUtils.isEmpty(response.getPaymentConfirms())) { + return new SplitAmountCheck(BigDecimal.ZERO, BigDecimal.ZERO); + } + + BigDecimal maxConfirmedAmount = BigDecimal.ZERO; + BigDecimal maxReservedAmount = BigDecimal.ZERO; + for (PaymentConfirmInfo confirm : response.getPaymentConfirms()) { + if (confirm == null) { + continue; + } + BigDecimal confirmedAmount = parseAmount(confirm.getConfirmedAmt()); + BigDecimal reservedAmount = parseAmount(confirm.getReservedAmt()); + if (confirmedAmount.compareTo(maxConfirmedAmount) > 0) { + maxConfirmedAmount = confirmedAmount; + } + if (reservedAmount.compareTo(maxReservedAmount) > 0) { + maxReservedAmount = reservedAmount; + } + } + return new SplitAmountCheck(maxConfirmedAmount.setScale(2, BigDecimal.ROUND_HALF_UP), + maxReservedAmount.setScale(2, BigDecimal.ROUND_HALF_UP)); + } catch (Exception e) { + log.warn("刷新未分账处理标识时查询分账金额失败, paymentId:{}", paymentId, e); + return new SplitAmountCheck(BigDecimal.ZERO, BigDecimal.ZERO); + } + } + + private String calculateHandleFlag(BigDecimal expectedAmount, BigDecimal successAmount, BigDecimal acceptedAmount) { + BigDecimal expected = defaultAmount(expectedAmount); + BigDecimal success = defaultAmount(successAmount); + BigDecimal accepted = defaultAmount(acceptedAmount); + if (expected.compareTo(BigDecimal.ZERO) <= 0 || success.compareTo(expected) >= 0) { + return HANDLE_FLAG_SUCCESS; + } + if (accepted.compareTo(expected) >= 0) { + return HANDLE_FLAG_PROCESSING; + } + return HANDLE_FLAG_FAILED; + } + private boolean ensureRefundBeforeSplit(AdapayUnsplitRecordVO item, String wechatAppId) { String orderCode = item.getOrderCode(); String paymentId = item.getPaymentId(); + // VO 中 refundAmount 来源于 adapay_unsplit_record.due_refund_amount,表示分账前必须完成的应退款金额。 BigDecimal dueRefundAmount = parseAmount(item.getRefundAmount()); if (dueRefundAmount.compareTo(BigDecimal.ZERO) <= 0) { return true; @@ -492,23 +708,26 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord OrderBasicInfo orderBasicInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode); if (orderBasicInfo == null) { log.warn("未分账数据退款前置校验失败,订单不存在, paymentId:{}, orderCode:{}", paymentId, orderCode); - markRefundResult(paymentId, "FAILED"); + markRefundResult(paymentId, HANDLE_FLAG_FAILED); return false; } + // 先只统计成功退款金额;成功退款已足额时才允许继续后续分账。 BigDecimal refundedAmount = getRefundedAmount(orderBasicInfo, false); updateRefundAmount(paymentId, refundedAmount); if (refundedAmount.compareTo(dueRefundAmount) >= 0) { - markRefundResult(paymentId, "SUCCESS"); + markRefundResult(paymentId, HANDLE_FLAG_SUCCESS); return true; } + // 再统计已受理退款金额(成功 + 处理中),避免处理中退款未回调时重复发起退款。 BigDecimal acceptedRefundAmount = getRefundedAmount(orderBasicInfo, true); if (acceptedRefundAmount.compareTo(dueRefundAmount) >= 0) { - markRefundResult(paymentId, "PROCESSING"); + markRefundResult(paymentId, HANDLE_FLAG_PROCESSING); return waitRefundFullySucceeded(orderBasicInfo, paymentId, dueRefundAmount); } + // 已受理退款仍不足时,只补发差额退款;补发后等待成功退款金额达到应退款金额。 BigDecimal refundAmount = dueRefundAmount.subtract(acceptedRefundAmount).setScale(2, BigDecimal.ROUND_HALF_UP); try { ApplyRefundDTO dto = new ApplyRefundDTO(); @@ -518,12 +737,12 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord dto.setWechatAppId(wechatAppId); dto.setMemberId(orderBasicInfo.getMemberId()); orderBasicInfoService.refundOrderWithAdapay(dto); - markRefundResult(paymentId, "PROCESSING"); + markRefundResult(paymentId, HANDLE_FLAG_PROCESSING); log.info("未分账数据先执行退款, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, refundAmount:{}", paymentId, orderCode, dueRefundAmount, acceptedRefundAmount, refundAmount); return waitRefundFullySucceeded(orderBasicInfo, paymentId, dueRefundAmount); } catch (Exception e) { - markRefundResult(paymentId, "FAILED"); + markRefundResult(paymentId, HANDLE_FLAG_FAILED); log.error("未分账数据执行退款失败, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}, refundAmount:{}", paymentId, orderCode, dueRefundAmount, acceptedRefundAmount, refundAmount, e); } @@ -540,10 +759,11 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord return false; } + // 这里仍然只认成功退款金额,处理中退款不满足“退款足额后再分账”的条件。 BigDecimal refundedAmount = getRefundedAmount(orderBasicInfo, false); updateRefundAmount(paymentId, refundedAmount); if (refundedAmount.compareTo(dueRefundAmount) >= 0) { - markRefundResult(paymentId, "SUCCESS"); + markRefundResult(paymentId, HANDLE_FLAG_SUCCESS); log.info("未分账数据退款已足额,继续分账, paymentId:{}, orderCode:{}, dueRefundAmount:{}, refundedAmount:{}", paymentId, orderBasicInfo.getOrderCode(), dueRefundAmount, refundedAmount); return true; @@ -552,7 +772,7 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord paymentId, orderBasicInfo.getOrderCode(), i, REFUND_WAIT_MAX_ATTEMPTS, dueRefundAmount, refundedAmount); } - markRefundResult(paymentId, "PROCESSING"); + markRefundResult(paymentId, HANDLE_FLAG_PROCESSING); log.warn("等待退款足额超时,本轮不分账, paymentId:{}, orderCode:{}, dueRefundAmount:{}", paymentId, orderBasicInfo.getOrderCode(), dueRefundAmount); return false; @@ -569,6 +789,7 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord if (refundInfo == null) { continue; } + // includeProcessing=false:只累计成功退款;includeProcessing=true:成功 + 处理中,用于判断是否还需要补发差额退款。 String status = refundInfo.getStatus(); if (StringUtils.isNotBlank(status) && !StringUtils.equals(status, AdapayStatusEnum.SUCCEEDED.getValue()) @@ -786,6 +1007,10 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord } } + private BigDecimal defaultAmount(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value.setScale(2, BigDecimal.ROUND_HALF_UP); + } + private boolean isRowEmpty(Row row) { if (row == null) { return true; @@ -839,4 +1064,24 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord + "}"; } } + + private static class RefundAmountCheck { + private final BigDecimal refundedAmount; + private final BigDecimal acceptedRefundAmount; + + private RefundAmountCheck(BigDecimal refundedAmount, BigDecimal acceptedRefundAmount) { + this.refundedAmount = refundedAmount == null ? BigDecimal.ZERO : refundedAmount; + this.acceptedRefundAmount = acceptedRefundAmount == null ? BigDecimal.ZERO : acceptedRefundAmount; + } + } + + private static class SplitAmountCheck { + private final BigDecimal confirmedSplitAmount; + private final BigDecimal reservedSplitAmount; + + private SplitAmountCheck(BigDecimal confirmedSplitAmount, BigDecimal reservedSplitAmount) { + this.confirmedSplitAmount = confirmedSplitAmount == null ? BigDecimal.ZERO : confirmedSplitAmount; + this.reservedSplitAmount = reservedSplitAmount == null ? BigDecimal.ZERO : reservedSplitAmount; + } + } } 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 55adbb8c0..0512a010a 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 @@ -528,6 +528,23 @@ public class JsowellTask { adapayUnsplitRecordHandleService.completeAdapayUnsplitRecordFields(startTime, endTime); } + /** + * 按 paymentId 刷新 adapay_unsplit_record 的退款/分账处理标识 + * jsowellTask.refreshAdapayUnsplitRecordHandleFlag(paymentId, wechatAppId) + */ + public void refreshAdapayUnsplitRecordHandleFlag(String paymentId, String wechatAppId) { + adapayUnsplitRecordHandleService.refreshAdapayUnsplitRecordHandleFlag(paymentId, wechatAppId); + } + + /** + * 批量刷新 adapay_unsplit_record 的退款/分账处理标识 + * jsowellTask.refreshAdapayUnsplitRecordHandleFlag(startTime, endTime, wechatAppId, pageSize) + * 示例:jsowellTask.refreshAdapayUnsplitRecordHandleFlag('2024-01-01 00:00:00', '2025-12-31 23:59:59', 'app_id', 500) + */ + public void refreshAdapayUnsplitRecordHandleFlag(String startTime, String endTime, String wechatAppId, Integer pageSize) { + adapayUnsplitRecordHandleService.refreshAdapayUnsplitRecordHandleFlag(startTime, endTime, wechatAppId, pageSize); + } + /** * 从Excel导入adapay_unsplit_record,并补齐缺失字段 * 流程: