package com.tourguide.modules.order.service; import com.alibaba.fastjson.JSONObject; import com.tourguide.common.exception.BusinessException; import com.tourguide.common.huifu.properties.HuiFuProperties; import com.tourguide.modules.order.domain.RefundStatus; import com.tourguide.modules.order.entity.TourOrder; import com.tourguide.modules.order.mapper.TourOrderMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @Service public class HuifuRefundCallbackService { private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); private final HuiFuProperties properties; private final TourOrderMapper orderMapper; public HuifuRefundCallbackService(HuiFuProperties properties, TourOrderMapper orderMapper) { this.properties = properties; this.orderMapper = orderMapper; } @Transactional public boolean handle(JSONObject body) { String refundOrderNo = first(body, "req_seq_id", "refund_req_seq_id"); if (!StringUtils.hasText(refundOrderNo)) { throw new BusinessException(400, "Huifu refund callback request sequence is missing"); } validateMerchant(first(body, "huifu_id", "out_huifu_id")); TourOrder order = orderMapper.selectByRefundOrderNoForUpdate(refundOrderNo); if (order == null) throw new BusinessException(404, "Refund order does not exist"); String status = first(body, "refund_stat", "trans_stat"); if (!isSuccess(status)) { if (!RefundStatus.REFUNDED.matches(order.getRefundStatus())) { order.setRefundStatus(RefundStatus.REFUND_FAILED.getCode()); orderMapper.updateById(order); } return false; } if (RefundStatus.REFUNDED.matches(order.getRefundStatus())) { return false; } BigDecimal callbackAmount = amount(body); if (order.getRefundAmount() == null || order.getRefundAmount().compareTo(callbackAmount) != 0) { throw new BusinessException(409, "Refund amount does not match order"); } order.setRefundStatus(RefundStatus.REFUNDED.getCode()); order.setRefundTime(callbackTime(body)); orderMapper.updateById(order); return true; } private void validateMerchant(String callbackHuifuId) { if (!StringUtils.hasText(callbackHuifuId) || !callbackHuifuId.equals(properties.getHuifuId())) { throw new BusinessException(400, "Huifu refund callback merchant does not match"); } } private BigDecimal amount(JSONObject body) { String value = first(body, "ord_amt", "refund_amt", "trans_amt"); if (!StringUtils.hasText(value)) { throw new BusinessException(400, "Huifu refund callback amount is missing"); } try { return new BigDecimal(value); } catch (NumberFormatException exception) { throw new BusinessException(400, "Huifu refund callback amount is invalid"); } } private LocalDateTime callbackTime(JSONObject body) { String value = first(body, "end_time", "trans_time", "refund_time"); if (!StringUtils.hasText(value)) return LocalDateTime.now(); String normalized = value.replace("-", "").replace(":", "").replace(" ", ""); try { return LocalDateTime.parse(normalized, TIME_FORMAT); } catch (DateTimeParseException exception) { return LocalDateTime.now(); } } private boolean isSuccess(String status) { return "S".equalsIgnoreCase(status) || "SUCCESS".equalsIgnoreCase(status); } private String first(JSONObject body, String... keys) { for (String key : keys) { String value = body.getString(key); if (StringUtils.hasText(value)) return value; } return null; } }