| | |
| | | |
| | | import com.tourguide.common.exception.BusinessException; |
| | | import com.tourguide.modules.order.domain.OrderStatus; |
| | | import com.tourguide.modules.order.domain.RefundAmountCalculator; |
| | | import com.tourguide.modules.order.domain.RefundStatus; |
| | | import com.tourguide.modules.order.entity.TourOrder; |
| | | import com.tourguide.modules.order.mapper.TourOrderMapper; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** 将到期且未分账的订单收益幂等计入导游余额。 */ |
| | |
| | | TourOrder order = orderMapper.selectForUpdate(orderId); |
| | | if (!canSettle(order, settledAt)) return false; |
| | | |
| | | BigDecimal earning = order.getGuideEarningAmount(); |
| | | BigDecimal earning = effectiveEarning(order); |
| | | if (earning.signum() > 0) { |
| | | if (order.getCommissionTeamId() != null) { |
| | | balanceService.creditTeam(order.getCommissionTeamId(), earning); |
| | |
| | | return true; |
| | | } |
| | | |
| | | /** 退款成功后冲正已入账收益;按累计退款差额计算,保证部分退款幂等。 */ |
| | | @Transactional |
| | | public void reverseRefund(TourOrder order, BigDecimal previousRefundedAmount, |
| | | BigDecimal currentRefundedAmount) { |
| | | if (order == null || order.getSettledAt() == null |
| | | || order.getGuideEarningAmount() == null) return; |
| | | BigDecimal paidAmount = RefundAmountCalculator.paidAmount(order); |
| | | if (paidAmount.signum() <= 0) return; |
| | | BigDecimal previous = nonNegative(previousRefundedAmount); |
| | | BigDecimal current = nonNegative(currentRefundedAmount).min(paidAmount); |
| | | BigDecimal previousReversal = proratedEarning(order.getGuideEarningAmount(), previous, paidAmount); |
| | | BigDecimal currentReversal = proratedEarning(order.getGuideEarningAmount(), current, paidAmount); |
| | | BigDecimal reversal = currentReversal.subtract(previousReversal).setScale(2, RoundingMode.HALF_UP); |
| | | if (reversal.signum() <= 0) return; |
| | | if (order.getCommissionTeamId() != null) { |
| | | balanceService.debitTeamForRefund(order.getCommissionTeamId(), reversal); |
| | | } else if (order.getGuideId() != null) { |
| | | balanceService.debitForRefund(order.getGuideId(), reversal); |
| | | } |
| | | } |
| | | |
| | | private boolean canSettle(TourOrder order, LocalDateTime now) { |
| | | return order != null |
| | | && OrderStatus.COMPLETED.matches(order.getStatus()) |
| | |
| | | && order.getGuideEarningAmount().signum() >= 0 |
| | | && !RefundStatus.REFUNDED.matches(order.getRefundStatus()); |
| | | } |
| | | |
| | | private BigDecimal effectiveEarning(TourOrder order) { |
| | | BigDecimal earning = order.getGuideEarningAmount(); |
| | | BigDecimal paidAmount = RefundAmountCalculator.paidAmount(order); |
| | | if (paidAmount.signum() <= 0) return earning; |
| | | BigDecimal refunded = RefundAmountCalculator.refundedAmount(order).min(paidAmount); |
| | | return earning.multiply(paidAmount.subtract(refunded)) |
| | | .divide(paidAmount, 2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private BigDecimal proratedEarning(BigDecimal earning, BigDecimal refunded, BigDecimal paidAmount) { |
| | | return earning.multiply(refunded).divide(paidAmount, 2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private BigDecimal nonNegative(BigDecimal value) { |
| | | return value == null ? BigDecimal.ZERO : value.max(BigDecimal.ZERO); |
| | | } |
| | | } |