Commit a25711b3 by 程裕兵

feat:refund

parent 8fcbc3b2
......@@ -19,6 +19,7 @@ import com.jiejing.fitness.finance.api.merchant.request.UnbindStudioMerchantRequ
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.paycenter.common.model.vo.PayVO;
import com.jiejing.paycenter.common.model.vo.RefundVO;
......@@ -98,6 +99,6 @@ public interface StudioMerchantApi {
@ApiOperation(value = "退款", tags = {TAG})
@PostMapping(value = "/private/studioMerchant/refund")
JsonResult<RefundVO> refund(StudioMerchantRefundRequest request);
JsonResult<StudioMerchantRefundVO> refund(StudioMerchantRefundRequest request);
}
\ No newline at end of file
......@@ -19,6 +19,7 @@ import com.jiejing.fitness.finance.api.merchant.request.UnbindStudioMerchantRequ
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.paycenter.common.model.vo.PayVO;
import com.jiejing.paycenter.common.model.vo.RefundVO;
......@@ -116,7 +117,7 @@ public class StudioMerchantApiFallback implements FallbackFactory<StudioMerchant
}
@Override
public JsonResult<RefundVO> refund(StudioMerchantRefundRequest request) {
public JsonResult<StudioMerchantRefundVO> refund(StudioMerchantRefundRequest request) {
return JsonResult.rpcError();
}
};
......
package com.jiejing.fitness.finance.api.merchant.vo;
import com.jiejing.common.swagger.EnumMapping;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.enums.error.PayErrorEnums;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Arrays;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* @author chengyubing
* @since 2024/5/10 13:40
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "场馆商户退款VO")
public class StudioMerchantRefundVO {
@ApiModelProperty(name = "退款单号")
private String transNo;
@EnumMapping(enumClass = TransStateEnums.class)
@ApiModelProperty(name = "退款状态")
private String refundState;
@ApiModelProperty(name = "三方交易单号")
private String thirdTransNo;
@ApiModelProperty(name = "失败原因")
private String failMessage;
@ApiModelProperty(name = "完成时间")
private Date successTime;
@EnumMapping(enumClass = CheckRefundCodeEnum.class)
@ApiModelProperty(value = "具体的错误码", notes = "退款校验或者退款失败时此code有值")
private String code;
@Getter
@AllArgsConstructor
public enum CheckRefundCodeEnum {
/**
* 退款校验CODE,退款失败CODE
*/
SUCCESS("校验成功"),
NOT_CURRENT_MERCHANT("不是当前商户号收款,金额无法线上原路退回。如仍需退款请私下处理,系统会产生新的收支"),
DATE_LIMIT("仅支持350天内交易进行退款,金额无法线上原路退回。如仍需退款请私下处理,系统会产生新的收支"),
TIME_LIMIT("当前时间段不支持退款,金额无法线上原路退回。如仍需退款请私下处理,系统会产生新的收支"),
AMOUNT_LIMIT("乐动收银可退金额不足"),
OTHER_ERROR("其他错误"),
;
private final String code = name();
private final String message;
public static CheckRefundCodeEnum getByCode(String code) {
return Arrays.stream(CheckRefundCodeEnum.values()).filter(e -> e.getCode().equals(code)).findFirst()
.orElse(OTHER_ERROR);
}
public static boolean isSuccess(String code) {
return CheckRefundCodeEnum.SUCCESS == getByCode(code);
}
public static boolean isFail(String code) {
return !isSuccess(code);
}
}
}
......@@ -21,6 +21,7 @@ import com.jiejing.fitness.finance.api.merchant.request.UnbindStudioMerchantRequ
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.fitness.finance.service.merchant.StudioMerchantService;
import com.jiejing.fitness.finance.service.merchant.params.ApplyStudioMerchantParams;
......@@ -181,7 +182,7 @@ public class StudioMerchantController implements StudioMerchantApi {
@ApiOperation(value = "退款", tags = {TAG})
@PostMapping(value = "/private/studioMerchant/refund")
@Override
public JsonResult<RefundVO> refund(@RequestBody @Valid StudioMerchantRefundRequest request) {
public JsonResult<StudioMerchantRefundVO> refund(@RequestBody @Valid StudioMerchantRefundRequest request) {
StudioMerchantRefundParams params = BeanUtil.map(request, StudioMerchantRefundParams.class);
return JsonResult.success(refundService.merchantRefund(params));
}
......
......@@ -49,4 +49,6 @@ public interface StudioCashierRecordMapper extends XBaseMapper<StudioCashierReco
StudioCashierStatisticVO statistic(@Param("query") PageBrandCashierRecordQuery query);
BigDecimal sumRefundTransAmountByPayTransNo(@Param("payTransNo") String payTransNo);
}
......@@ -25,7 +25,15 @@
from studio_cashier_record
where related_trans_no = #{payTransNo}
and trans_type = 'REFUND'
and trans_state in ('REFUNDING', 'REFUND_SUCCESS')
and trans_state in ('REFUND_INIT', 'REFUNDING', 'REFUND_SUCCESS')
</select>
<select id="sumRefundTransAmountByPayTransNo" resultType="java.math.BigDecimal">
select sum(trans_amount)
from studio_cashier_record
where related_trans_no = #{payTransNo}
and trans_type = 'REFUND'
and trans_state in ('REFUND_INIT', 'REFUNDING', 'REFUND_SUCCESS')
</select>
<select id="sumMerchantPaySuccess" resultType="java.math.BigDecimal">
......
......@@ -47,7 +47,13 @@ public class StudioCashierRecordRpService extends
MapperRepoService<Long, StudioCashierRecord, StudioCashierRecordMapper> {
public BigDecimal sumRefundActualAmountByPayTransNo(String payTransNo) {
return this.baseMapper.sumRefundActualAmountByPayTransNo(payTransNo);
return Optional.ofNullable(this.baseMapper.sumRefundActualAmountByPayTransNo(payTransNo))
.orElse(BigDecimal.ZERO);
}
public BigDecimal sumRefundTransAmountByPayTransNo(String payTransNo) {
return Optional.ofNullable(this.baseMapper.sumRefundTransAmountByPayTransNo(payTransNo))
.orElse(BigDecimal.ZERO);
}
public List<StudioCashierRecord> listByOrderNo(String orderNo) {
......
......@@ -18,6 +18,7 @@ public enum GlobalConfigEnums {
BRAND_MERCHANT_SUB_CHANNELS("BRAND_MERCHANT_SUB_CHANNELS", "品牌商户默认开通的子渠道以及对应费率"),
CASHIER_APP_IDS("CASHIER_APP_IDS", "收银所需的appId"),
CASHIER_ALI_INFO("CASHIER_ALI_INFO", "收银所需支付宝信息"),
CASHIER_REFUND_CONFIG("CASHIER_REFUND_CONFIG", "退款所需配置"),
;
@EnumValue
......
package com.jiejing.fitness.finance.service.global;
import com.jiejing.fitness.finance.service.global.dto.RefundConfigDTO;
import com.jiejing.fitness.finance.service.global.dto.SubChannelInfoDTO;
/**
......@@ -11,5 +12,6 @@ public interface ConfigService {
SubChannelInfoDTO getDefaultBrandSubChannelInfo();
RefundConfigDTO getRefundConfig();
}
package com.jiejing.fitness.finance.service.global.dto;
import java.util.List;
import lombok.Data;
/**
* @author chengyubing
* @since 2024/5/10 14:24
*/
@Data
public class RefundConfigDTO {
/**
* 最大天数
*/
private Long maxDate;
/**
* 拒绝时间区间
*/
private List<RefuseTime> refuseTimeList;
@Data
public static class RefuseTime {
/**
* 开始分钟
*/
private Integer start;
/**
* 结束分钟
*/
private Integer end;
}
}
......@@ -7,6 +7,7 @@ import com.jiejing.fitness.finance.repository.service.GlobalConfigRpService;
import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums;
import com.jiejing.fitness.finance.service.enums.GlobalConfigEnums;
import com.jiejing.fitness.finance.service.global.ConfigService;
import com.jiejing.fitness.finance.service.global.dto.RefundConfigDTO;
import com.jiejing.fitness.finance.service.global.dto.SubChannelInfoDTO;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
......@@ -29,4 +30,11 @@ public class ConfigServiceImpl implements ConfigService {
return JSON.parseObject(config.getConfigValue(), SubChannelInfoDTO.class);
}
@Override
public RefundConfigDTO getRefundConfig() {
GlobalConfig config = globalConfigRpService.getById(GlobalConfigEnums.CASHIER_REFUND_CONFIG.getCode())
.orElseThrow(() -> new BizException(FinanceErrorEnums.NOT_EXIST));
return JSON.parseObject(config.getConfigValue(), RefundConfigDTO.class);
}
}
......@@ -5,6 +5,7 @@ import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.fitness.finance.repository.entity.PartyToMerchant;
import com.jiejing.fitness.finance.service.merchant.params.ApplyStudioMerchantParams;
import com.jiejing.fitness.finance.service.merchant.params.PageStudioMerchantApplyParams;
import com.jiejing.paycenter.common.enums.merchant.SubChannelAuthTypeEnums;
......@@ -106,6 +107,14 @@ public interface StudioMerchantService {
StudioMerchantBindXcxAppIdVO bindXcxAppId(Long studioId, String appId);
/**
* 获取关联关系
*
* @param studioId 场馆ID
* @return 关联关系
*/
PartyToMerchant getRelation(Long studioId);
/**
* 授权子渠道
*
* @param studioId 场馆ID
......@@ -138,7 +147,7 @@ public interface StudioMerchantService {
/**
* 解绑前置校验
*
* @param studioId 场馆ID
* @param studioId 场馆ID
* @return true:绑定过其他场馆;false-没有绑定过
*/
List<String> checkUnbind(Long studioId);
......
......@@ -242,7 +242,8 @@ public class StudioMerchantServiceImpl implements StudioMerchantService {
.build();
}
private PartyToMerchant getRelation(Long studioId) {
@Override
public PartyToMerchant getRelation(Long studioId) {
List<PartyToMerchant> relations = partyToMerchantRpService.listByParty(studioId, PartyTypeEnum.STUDIO,
config.getCashier());
if (CollectionUtil.isEmpty(relations)) {
......
package com.jiejing.fitness.finance.service.pay;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO;
......@@ -11,12 +12,20 @@ import com.jiejing.paycenter.common.model.vo.RefundVO;
public interface RefundService {
/**
* 退款前检查
*
* @param params 参数
* @return 结果
*/
StudioMerchantRefundVO checkBeforeMerchantRefund(StudioMerchantRefundParams params);
/**
* 商户退款
*
* @param params 参数
* @return 结果
*/
RefundVO merchantRefund(StudioMerchantRefundParams params);
StudioMerchantRefundVO merchantRefund(StudioMerchantRefundParams params);
/**
* 退款回调
......
......@@ -3,6 +3,8 @@ package com.jiejing.fitness.finance.service.pay.convert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.jiejing.common.utils.convert.BeanUtil;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO.CheckRefundCodeEnum;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.utils.FeeUtil;
......@@ -82,4 +84,31 @@ public class RefundConvert {
return null;
}
}
public static StudioCashierRecord convertRefundFail(StudioCashierRecord record,
StudioMerchantRefundVO checkResult) {
return StudioCashierRecord.builder()
.id(record.getId())
.transState(BrandCashierTransStateEnum.REFUND_FAIL.getCode())
.failMessage(checkResult.getFailMessage())
.updateTime(new Date())
.build();
}
public static StudioMerchantRefundVO convertRefundVO(StudioCashierRecord refund, RefundVO vo) {
return StudioMerchantRefundVO.builder()
.thirdTransNo(vo.getThirdTransNo())
.transNo(refund.getTransNo())
.failMessage(vo.getFailMessage())
.refundState(vo.getRefundState())
.successTime(vo.getSuccessTime())
.code(TransStateEnums.FAIL == TransStateEnums.getByCode(vo.getRefundState())
? CheckRefundCodeEnum.OTHER_ERROR.getCode() : null)
.build();
}
public static StudioMerchantRefundVO convertRefundVO(CheckRefundCodeEnum code) {
return StudioMerchantRefundVO.builder().code(code.getCode()).failMessage(code.getMessage()).build();
}
}
......@@ -4,18 +4,27 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.collect.Lists;
import com.jiejing.common.exception.BizException;
import com.jiejing.common.utils.collection.CollectionUtil;
import com.jiejing.common.utils.time.TimeUtil;
import com.jiejing.fitness.enums.auth.AuthDomainEnum;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO.CheckRefundCodeEnum;
import com.jiejing.fitness.finance.repository.entity.PartyToMerchant;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService;
import com.jiejing.fitness.finance.service.config.AppUrlProperties;
import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums;
import com.jiejing.fitness.finance.service.global.ConfigService;
import com.jiejing.fitness.finance.service.global.dto.RefundConfigDTO;
import com.jiejing.fitness.finance.service.global.dto.RefundConfigDTO.RefuseTime;
import com.jiejing.fitness.finance.service.merchant.StudioMerchantService;
import com.jiejing.fitness.finance.service.pay.RefundService;
import com.jiejing.fitness.finance.service.pay.convert.RefundConvert;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.rpc.PayRpcService;
import com.jiejing.fitness.finance.service.rpc.PermissionRpcService;
import com.jiejing.fitness.finance.service.rpc.StudioRpcService;
import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.message.enums.MsgChannelEnum;
import com.jiejing.message.event.SendCommonMsgEvent;
import com.jiejing.paycenter.api.pay.request.RefundPayRequest;
......@@ -24,9 +33,13 @@ import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO;
import com.xiaomai.event.EventAgent;
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.annotation.Resource;
......@@ -46,6 +59,9 @@ public class RefundServiceImpl implements RefundService {
private PayRpcService payRpcService;
@Resource
private StudioMerchantService studioMerchantService;
@Resource
private StudioCashierRecordRpService studioCashierRecordRpService;
@Resource
......@@ -60,30 +76,55 @@ public class RefundServiceImpl implements RefundService {
@Resource
private AppUrlProperties appUrlProperties;
@Resource
private ConfigService configService;
@Resource(name = "financeThreadPool")
private Executor executor;
@Override
public RefundVO merchantRefund(StudioMerchantRefundParams params) {
StudioCashierRecord refund = transactionTemplate.execute(action -> {
Long payId = Long.parseLong(params.getPayTransNo());
StudioCashierRecord pay = studioCashierRecordRpService.getById(payId)
.orElseThrow(() -> new BizException(FinanceErrorEnums.NOT_EXIST));
public StudioMerchantRefundVO checkBeforeMerchantRefund(StudioMerchantRefundParams params) {
Long payId = Long.parseLong(params.getPayTransNo());
StudioCashierRecord pay = studioCashierRecordRpService.getById(payId)
.orElseThrow(() -> new BizException(FinanceErrorEnums.NOT_EXIST));
RefundConfigDTO config = configService.getRefundConfig();
// 不是当前商户号收款,金额无法线上原路退回。如仍需退款请私下处理,系统会产生新的收支
if (!this.isCurrentMerchant(params.getStudioId(), pay.getMerchantId())) {
return RefundConvert.convertRefundVO(CheckRefundCodeEnum.NOT_CURRENT_MERCHANT);
}
// 仅支持350天内交易进行退款,金额无法线上原路退回。如仍需退款请私下处理,系统会产生新的收支
if (this.isDateLimit(pay.getSuccessTime(), config.getMaxDate())) {
return RefundConvert.convertRefundVO(CheckRefundCodeEnum.DATE_LIMIT);
}
// 当前时间段不支持退款,金额无法线上原路退回。如仍需退款请私下处理,系统会产生新的收支
if (this.isTimeLimit(new Date(), config.getRefuseTimeList())) {
return RefundConvert.convertRefundVO(CheckRefundCodeEnum.TIME_LIMIT);
}
// 乐动收银可退金额不足
if (this.isAmountLimit(params.getTransAmount(), pay)) {
return RefundConvert.convertRefundVO(CheckRefundCodeEnum.AMOUNT_LIMIT);
}
return RefundConvert.convertRefundVO(CheckRefundCodeEnum.SUCCESS);
}
BigDecimal historyRefundActualAmount = studioCashierRecordRpService.sumRefundActualAmountByPayTransNo(
params.getPayTransNo());
@Override
public StudioMerchantRefundVO merchantRefund(StudioMerchantRefundParams params) {
StudioCashierRecord record = RefundConvert.convertRefundInit(params, pay, historyRefundActualAmount);
studioCashierRecordRpService.insert(record);
return record;
});
StudioCashierRecord refund = this.initRefundRecord(params);
StudioMerchantRefundVO checkResult = this.checkBeforeMerchantRefund(params);
if (CheckRefundCodeEnum.isFail(checkResult.getCode())) {
StudioCashierRecord toModify = RefundConvert.convertRefundFail(refund, checkResult);
studioCashierRecordRpService.updateById(toModify);
return checkResult;
}
RefundPayRequest request = RefundConvert.convert(params, refund);
RefundVO vo = payRpcService.refund(request);
StudioCashierRecord toModify = RefundConvert.convertRefund(refund, vo);
studioCashierRecordRpService.updateById(toModify);
return vo;
return RefundConvert.convertRefundVO(refund, vo);
}
@Override
......@@ -154,4 +195,44 @@ public class RefundServiceImpl implements RefundService {
Lists.newArrayList("FitSeeXmPay", "FitManageXmPay"), false);
}
private StudioCashierRecord initRefundRecord(StudioMerchantRefundParams params) {
return transactionTemplate.execute(action -> {
Long payId = Long.parseLong(params.getPayTransNo());
StudioCashierRecord pay = studioCashierRecordRpService.getById(payId)
.orElseThrow(() -> new BizException(FinanceErrorEnums.NOT_EXIST));
BigDecimal historyRefundActualAmount = studioCashierRecordRpService.sumRefundActualAmountByPayTransNo(
params.getPayTransNo());
StudioCashierRecord record = RefundConvert.convertRefundInit(params, pay, historyRefundActualAmount);
studioCashierRecordRpService.insert(record);
return record;
});
}
private boolean isCurrentMerchant(Long studioId, Long merchantId) {
PartyToMerchant merchant = studioMerchantService.getRelation(studioId);
if (null == merchant) {
return false;
}
return merchant.getMerchantId().equals(merchantId);
}
private boolean isDateLimit(Date successTime, Long maxDate) {
return TimeUtil.local().diff(new Date(), successTime, ChronoUnit.DAYS) > maxDate;
}
private boolean isTimeLimit(Date now, List<RefuseTime> refuseTimeList) {
int minute = TimeUtil.local().getMinuteOfDay(now);
return refuseTimeList.stream().anyMatch(refuseTime ->
minute >= refuseTime.getStart() && minute <= refuseTime.getEnd());
}
private boolean isAmountLimit(BigDecimal refundAmount, StudioCashierRecord pay) {
BigDecimal historyRefundAmount = studioCashierRecordRpService.sumRefundTransAmountByPayTransNo(
pay.getTransNo());
BigDecimal leftTransAmount = MoneyUtil.subtract(pay.getTransAmount(), historyRefundAmount);
return refundAmount.compareTo(leftTransAmount) > 0;
}
}
......@@ -2,7 +2,6 @@ package com.jiejing.fitness.finance.service.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
/**
* @author chengyubing
......@@ -14,7 +13,7 @@ public class FeeUtil {
* 【支付】计算支付手续费,保留小数点后两位,四舍五入
*
* @param feeRate 费率(%)
* @param amount 支付金额
* @param amount 支付金额(元)
* @return 手续费
*/
public static BigDecimal calPayFee(BigDecimal feeRate, BigDecimal amount) {
......@@ -24,23 +23,23 @@ public class FeeUtil {
}
/**
* 【退款】计算退款手续费
* 【退款】计算退款手续费(元)
*
* <p>实退手续费计算公式:退款手续费=向下取整(退款金额*原交易手续费金额/原交易金额)</p>
*
* @param refundTransAmount 退款申请金额
* @param payTransAmount 原支付交易金额
* @param payActualAmount 用户实收金额
* @param payFee 平台实收手续费
* @param historyRefundActualAmount 历史实退金额
* @return 本次退款应退手续费
* @param refundTransAmount 退款申请金额(元)
* @param payTransAmount 原支付交易金额(元)
* @param payActualAmount 用户实收金额(元)
* @param payFee 平台实收手续费(元)
* @param historyRefundActualAmount 历史实退金额(元)
* @return 本次退款应退手续费(元)
*/
public static BigDecimal calculateRefundFee(BigDecimal refundTransAmount, BigDecimal payTransAmount,
BigDecimal payActualAmount, BigDecimal payFee, BigDecimal historyRefundActualAmount) {
// 机构剩余实收金额
BigDecimal leftPayActualAmount = MoneyUtil.subtract(payActualAmount, historyRefundActualAmount);
// 试算手续费
BigDecimal trialFee = MoneyUtil.divide(refundTransAmount.multiply(payFee), payTransAmount, 0,
// 试算手续费:向下取整(退款金额/原交易金额*原支付交易手续费金额)
BigDecimal trialFee = MoneyUtil.divide(refundTransAmount.multiply(payFee), payTransAmount, 2,
RoundingMode.FLOOR);
// 试算实退金额
BigDecimal trialActualAmount = MoneyUtil.subtract(refundTransAmount, trialFee);
......@@ -49,77 +48,17 @@ public class FeeUtil {
: MoneyUtil.add(trialFee, MoneyUtil.subtract(trialActualAmount, leftPayActualAmount));
}
/**
* 计算补贴回退金额(向上取整ceil)
*
* @param refundTransAmount 退款申请金额
* @param payTransAmount 原支付交易金额
* @param subsidyAmount 补贴金额
* @param historyBackAmount 历史补贴回退金额
* @return 补贴回退金额
*/
public static BigDecimal calculateSubsidyBackAmount(BigDecimal refundTransAmount,
BigDecimal payTransAmount, BigDecimal subsidyAmount, BigDecimal historyBackAmount) {
if (Objects.isNull(subsidyAmount)) {
return BigDecimal.ZERO;
}
// 剩余可回退补贴金额
BigDecimal leftSubsidyBackAmount = MoneyUtil.subtract(subsidyAmount, historyBackAmount);
// 试算补贴回退金额
BigDecimal trialAmount = MoneyUtil.divide(refundTransAmount.multiply(subsidyAmount),
payTransAmount, 0, RoundingMode.CEILING);
return leftSubsidyBackAmount.compareTo(trialAmount) >= 0 ? trialAmount : leftSubsidyBackAmount;
}
public static BigDecimal calculateAdvanceAmount(BigDecimal refundAmount, BigDecimal qyqbhTransitBalance,
BigDecimal yxyjhTransitBalance) {
BigDecimal transitBalance = MoneyUtil.subtract(qyqbhTransitBalance, yxyjhTransitBalance);
if (refundAmount.compareTo(transitBalance) <= 0) {
// 若:退款金额 <= 业务钱包入账中余额,则无需佣金垫付记录记账
return null;
}
// 若:退款金额 > 业务钱包入账中余额,则添加佣金垫付记录记账
// 垫付金额 = (退款金额 - 业务入帐中金额) > 佣金入账中 ? 佣金入账中 : (退款金额 - 入帐中金额)
BigDecimal advanceAmount = MoneyUtil.subtract(refundAmount, transitBalance);
if (advanceAmount.compareTo(yxyjhTransitBalance) > 0) {
advanceAmount = yxyjhTransitBalance;
}
return advanceAmount;
}
public static void main(String[] args) {
// 1. 首次退款
// BigDecimal refundTransAmount = new BigDecimal("498");
// BigDecimal payTransAmount = new BigDecimal("500");
// BigDecimal payActualAmount = new BigDecimal("498");
// BigDecimal payFee = new BigDecimal("2");
// BigDecimal historyRefundActualAmount = new BigDecimal("0");
// 预计输出:1
// 实退金额:497, 实退手续费:1。剩余可退金额:2(剩余实退金额:1,剩余实退手续费:1),历史实退金额:497,历史实退手续费:1
// 2. 二次退款
// BigDecimal refundTransAmount = new BigDecimal("1");
// BigDecimal payTransAmount = new BigDecimal("500");
// BigDecimal payActualAmount = new BigDecimal("498");
// BigDecimal payFee = new BigDecimal("2");
// BigDecimal historyRefundActualAmount = new BigDecimal("497");
// 预计输出:0
// 实退金额:1,实退手续费:0。剩余可退金额:1(剩余实退金额:0,剩余实退手续费:1),历史实退金额:498,历史实退手续费:1
// 3. 三次退款
BigDecimal refundTransAmount = new BigDecimal("40");
BigDecimal payTransAmount = new BigDecimal("10000");
BigDecimal payActualAmount = new BigDecimal("9943");
BigDecimal payFee = new BigDecimal("57");
BigDecimal historyRefundActualAmount = new BigDecimal("9904");
BigDecimal refundTransAmount = new BigDecimal("100");
BigDecimal payTransAmount = new BigDecimal("100");
BigDecimal payActualAmount = new BigDecimal("99.43");
BigDecimal payFee = new BigDecimal("0.57");
BigDecimal historyRefundActualAmount = new BigDecimal("99.43");
// 预计输出:1
// 实退金额:0,实退手续费:1。剩余可退金额:0,历史实退金额:498,历史实退手续费:2
System.out.println(
FeeUtil.calculateRefundFee(refundTransAmount, payTransAmount, payActualAmount, payFee,
historyRefundActualAmount));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment