Commit a25711b3 by 程裕兵

feat:refund

parent 8fcbc3b2
...@@ -19,6 +19,7 @@ import com.jiejing.fitness.finance.api.merchant.request.UnbindStudioMerchantRequ ...@@ -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.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO; 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.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO; import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.paycenter.common.model.vo.PayVO; import com.jiejing.paycenter.common.model.vo.PayVO;
import com.jiejing.paycenter.common.model.vo.RefundVO; import com.jiejing.paycenter.common.model.vo.RefundVO;
...@@ -98,6 +99,6 @@ public interface StudioMerchantApi { ...@@ -98,6 +99,6 @@ public interface StudioMerchantApi {
@ApiOperation(value = "退款", tags = {TAG}) @ApiOperation(value = "退款", tags = {TAG})
@PostMapping(value = "/private/studioMerchant/refund") @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 ...@@ -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.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO; 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.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO; import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.paycenter.common.model.vo.PayVO; import com.jiejing.paycenter.common.model.vo.PayVO;
import com.jiejing.paycenter.common.model.vo.RefundVO; import com.jiejing.paycenter.common.model.vo.RefundVO;
...@@ -116,7 +117,7 @@ public class StudioMerchantApiFallback implements FallbackFactory<StudioMerchant ...@@ -116,7 +117,7 @@ public class StudioMerchantApiFallback implements FallbackFactory<StudioMerchant
} }
@Override @Override
public JsonResult<RefundVO> refund(StudioMerchantRefundRequest request) { public JsonResult<StudioMerchantRefundVO> refund(StudioMerchantRefundRequest request) {
return JsonResult.rpcError(); 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 ...@@ -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.StudioMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantAuthSubChannelVO; 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.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantRefundVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO; import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO;
import com.jiejing.fitness.finance.service.merchant.StudioMerchantService; import com.jiejing.fitness.finance.service.merchant.StudioMerchantService;
import com.jiejing.fitness.finance.service.merchant.params.ApplyStudioMerchantParams; import com.jiejing.fitness.finance.service.merchant.params.ApplyStudioMerchantParams;
...@@ -181,7 +182,7 @@ public class StudioMerchantController implements StudioMerchantApi { ...@@ -181,7 +182,7 @@ public class StudioMerchantController implements StudioMerchantApi {
@ApiOperation(value = "退款", tags = {TAG}) @ApiOperation(value = "退款", tags = {TAG})
@PostMapping(value = "/private/studioMerchant/refund") @PostMapping(value = "/private/studioMerchant/refund")
@Override @Override
public JsonResult<RefundVO> refund(@RequestBody @Valid StudioMerchantRefundRequest request) { public JsonResult<StudioMerchantRefundVO> refund(@RequestBody @Valid StudioMerchantRefundRequest request) {
StudioMerchantRefundParams params = BeanUtil.map(request, StudioMerchantRefundParams.class); StudioMerchantRefundParams params = BeanUtil.map(request, StudioMerchantRefundParams.class);
return JsonResult.success(refundService.merchantRefund(params)); return JsonResult.success(refundService.merchantRefund(params));
} }
......
...@@ -49,4 +49,6 @@ public interface StudioCashierRecordMapper extends XBaseMapper<StudioCashierReco ...@@ -49,4 +49,6 @@ public interface StudioCashierRecordMapper extends XBaseMapper<StudioCashierReco
StudioCashierStatisticVO statistic(@Param("query") PageBrandCashierRecordQuery query); StudioCashierStatisticVO statistic(@Param("query") PageBrandCashierRecordQuery query);
BigDecimal sumRefundTransAmountByPayTransNo(@Param("payTransNo") String payTransNo);
} }
...@@ -25,7 +25,15 @@ ...@@ -25,7 +25,15 @@
from studio_cashier_record from studio_cashier_record
where related_trans_no = #{payTransNo} where related_trans_no = #{payTransNo}
and trans_type = 'REFUND' 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>
<select id="sumMerchantPaySuccess" resultType="java.math.BigDecimal"> <select id="sumMerchantPaySuccess" resultType="java.math.BigDecimal">
......
...@@ -47,7 +47,13 @@ public class StudioCashierRecordRpService extends ...@@ -47,7 +47,13 @@ public class StudioCashierRecordRpService extends
MapperRepoService<Long, StudioCashierRecord, StudioCashierRecordMapper> { MapperRepoService<Long, StudioCashierRecord, StudioCashierRecordMapper> {
public BigDecimal sumRefundActualAmountByPayTransNo(String payTransNo) { 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) { public List<StudioCashierRecord> listByOrderNo(String orderNo) {
......
...@@ -18,6 +18,7 @@ public enum GlobalConfigEnums { ...@@ -18,6 +18,7 @@ public enum GlobalConfigEnums {
BRAND_MERCHANT_SUB_CHANNELS("BRAND_MERCHANT_SUB_CHANNELS", "品牌商户默认开通的子渠道以及对应费率"), BRAND_MERCHANT_SUB_CHANNELS("BRAND_MERCHANT_SUB_CHANNELS", "品牌商户默认开通的子渠道以及对应费率"),
CASHIER_APP_IDS("CASHIER_APP_IDS", "收银所需的appId"), CASHIER_APP_IDS("CASHIER_APP_IDS", "收银所需的appId"),
CASHIER_ALI_INFO("CASHIER_ALI_INFO", "收银所需支付宝信息"), CASHIER_ALI_INFO("CASHIER_ALI_INFO", "收银所需支付宝信息"),
CASHIER_REFUND_CONFIG("CASHIER_REFUND_CONFIG", "退款所需配置"),
; ;
@EnumValue @EnumValue
......
package com.jiejing.fitness.finance.service.global; 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; import com.jiejing.fitness.finance.service.global.dto.SubChannelInfoDTO;
/** /**
...@@ -11,5 +12,6 @@ public interface ConfigService { ...@@ -11,5 +12,6 @@ public interface ConfigService {
SubChannelInfoDTO getDefaultBrandSubChannelInfo(); 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; ...@@ -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.FinanceErrorEnums;
import com.jiejing.fitness.finance.service.enums.GlobalConfigEnums; import com.jiejing.fitness.finance.service.enums.GlobalConfigEnums;
import com.jiejing.fitness.finance.service.global.ConfigService; 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 com.jiejing.fitness.finance.service.global.dto.SubChannelInfoDTO;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -29,4 +30,11 @@ public class ConfigServiceImpl implements ConfigService { ...@@ -29,4 +30,11 @@ public class ConfigServiceImpl implements ConfigService {
return JSON.parseObject(config.getConfigValue(), SubChannelInfoDTO.class); 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; ...@@ -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.StudioMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantBindXcxAppIdVO; import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.StudioMerchantVO; 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.ApplyStudioMerchantParams;
import com.jiejing.fitness.finance.service.merchant.params.PageStudioMerchantApplyParams; import com.jiejing.fitness.finance.service.merchant.params.PageStudioMerchantApplyParams;
import com.jiejing.paycenter.common.enums.merchant.SubChannelAuthTypeEnums; import com.jiejing.paycenter.common.enums.merchant.SubChannelAuthTypeEnums;
...@@ -106,6 +107,14 @@ public interface StudioMerchantService { ...@@ -106,6 +107,14 @@ public interface StudioMerchantService {
StudioMerchantBindXcxAppIdVO bindXcxAppId(Long studioId, String appId); StudioMerchantBindXcxAppIdVO bindXcxAppId(Long studioId, String appId);
/** /**
* 获取关联关系
*
* @param studioId 场馆ID
* @return 关联关系
*/
PartyToMerchant getRelation(Long studioId);
/**
* 授权子渠道 * 授权子渠道
* *
* @param studioId 场馆ID * @param studioId 场馆ID
...@@ -138,7 +147,7 @@ public interface StudioMerchantService { ...@@ -138,7 +147,7 @@ public interface StudioMerchantService {
/** /**
* 解绑前置校验 * 解绑前置校验
* *
* @param studioId 场馆ID * @param studioId 场馆ID
* @return true:绑定过其他场馆;false-没有绑定过 * @return true:绑定过其他场馆;false-没有绑定过
*/ */
List<String> checkUnbind(Long studioId); List<String> checkUnbind(Long studioId);
......
...@@ -242,7 +242,8 @@ public class StudioMerchantServiceImpl implements StudioMerchantService { ...@@ -242,7 +242,8 @@ public class StudioMerchantServiceImpl implements StudioMerchantService {
.build(); .build();
} }
private PartyToMerchant getRelation(Long studioId) { @Override
public PartyToMerchant getRelation(Long studioId) {
List<PartyToMerchant> relations = partyToMerchantRpService.listByParty(studioId, PartyTypeEnum.STUDIO, List<PartyToMerchant> relations = partyToMerchantRpService.listByParty(studioId, PartyTypeEnum.STUDIO,
config.getCashier()); config.getCashier());
if (CollectionUtil.isEmpty(relations)) { if (CollectionUtil.isEmpty(relations)) {
......
package com.jiejing.fitness.finance.service.pay; 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.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.paycenter.common.event.RefundEvent; import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO; import com.jiejing.paycenter.common.model.vo.RefundVO;
...@@ -11,12 +12,20 @@ import com.jiejing.paycenter.common.model.vo.RefundVO; ...@@ -11,12 +12,20 @@ import com.jiejing.paycenter.common.model.vo.RefundVO;
public interface RefundService { public interface RefundService {
/** /**
* 退款前检查
*
* @param params 参数
* @return 结果
*/
StudioMerchantRefundVO checkBeforeMerchantRefund(StudioMerchantRefundParams params);
/**
* 商户退款 * 商户退款
* *
* @param params 参数 * @param params 参数
* @return 结果 * @return 结果
*/ */
RefundVO merchantRefund(StudioMerchantRefundParams params); StudioMerchantRefundVO merchantRefund(StudioMerchantRefundParams params);
/** /**
* 退款回调 * 退款回调
......
...@@ -3,6 +3,8 @@ package com.jiejing.fitness.finance.service.pay.convert; ...@@ -3,6 +3,8 @@ package com.jiejing.fitness.finance.service.pay.convert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.jiejing.common.utils.convert.BeanUtil; import com.jiejing.common.utils.convert.BeanUtil;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum; 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.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.utils.FeeUtil; import com.jiejing.fitness.finance.service.utils.FeeUtil;
...@@ -82,4 +84,31 @@ public class RefundConvert { ...@@ -82,4 +84,31 @@ public class RefundConvert {
return null; 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; ...@@ -4,18 +4,27 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.jiejing.common.exception.BizException; import com.jiejing.common.exception.BizException;
import com.jiejing.common.utils.collection.CollectionUtil; 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.auth.AuthDomainEnum;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum; 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.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService; import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService;
import com.jiejing.fitness.finance.service.config.AppUrlProperties; import com.jiejing.fitness.finance.service.config.AppUrlProperties;
import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums; 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.RefundService;
import com.jiejing.fitness.finance.service.pay.convert.RefundConvert; import com.jiejing.fitness.finance.service.pay.convert.RefundConvert;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.rpc.PayRpcService; import com.jiejing.fitness.finance.service.rpc.PayRpcService;
import com.jiejing.fitness.finance.service.rpc.PermissionRpcService; import com.jiejing.fitness.finance.service.rpc.PermissionRpcService;
import com.jiejing.fitness.finance.service.rpc.StudioRpcService; 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.enums.MsgChannelEnum;
import com.jiejing.message.event.SendCommonMsgEvent; import com.jiejing.message.event.SendCommonMsgEvent;
import com.jiejing.paycenter.api.pay.request.RefundPayRequest; import com.jiejing.paycenter.api.pay.request.RefundPayRequest;
...@@ -24,9 +33,13 @@ import com.jiejing.paycenter.common.event.RefundEvent; ...@@ -24,9 +33,13 @@ import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO; import com.jiejing.paycenter.common.model.vo.RefundVO;
import com.xiaomai.event.EventAgent; import com.xiaomai.event.EventAgent;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Resource; import javax.annotation.Resource;
...@@ -46,6 +59,9 @@ public class RefundServiceImpl implements RefundService { ...@@ -46,6 +59,9 @@ public class RefundServiceImpl implements RefundService {
private PayRpcService payRpcService; private PayRpcService payRpcService;
@Resource @Resource
private StudioMerchantService studioMerchantService;
@Resource
private StudioCashierRecordRpService studioCashierRecordRpService; private StudioCashierRecordRpService studioCashierRecordRpService;
@Resource @Resource
...@@ -60,30 +76,55 @@ public class RefundServiceImpl implements RefundService { ...@@ -60,30 +76,55 @@ public class RefundServiceImpl implements RefundService {
@Resource @Resource
private AppUrlProperties appUrlProperties; private AppUrlProperties appUrlProperties;
@Resource
private ConfigService configService;
@Resource(name = "financeThreadPool") @Resource(name = "financeThreadPool")
private Executor executor; private Executor executor;
@Override @Override
public RefundVO merchantRefund(StudioMerchantRefundParams params) { public StudioMerchantRefundVO checkBeforeMerchantRefund(StudioMerchantRefundParams params) {
StudioCashierRecord refund = transactionTemplate.execute(action -> { Long payId = Long.parseLong(params.getPayTransNo());
Long payId = Long.parseLong(params.getPayTransNo()); StudioCashierRecord pay = studioCashierRecordRpService.getById(payId)
StudioCashierRecord pay = studioCashierRecordRpService.getById(payId) .orElseThrow(() -> new BizException(FinanceErrorEnums.NOT_EXIST));
.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( @Override
params.getPayTransNo()); public StudioMerchantRefundVO merchantRefund(StudioMerchantRefundParams params) {
StudioCashierRecord record = RefundConvert.convertRefundInit(params, pay, historyRefundActualAmount); StudioCashierRecord refund = this.initRefundRecord(params);
studioCashierRecordRpService.insert(record);
return record; 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); RefundPayRequest request = RefundConvert.convert(params, refund);
RefundVO vo = payRpcService.refund(request); RefundVO vo = payRpcService.refund(request);
StudioCashierRecord toModify = RefundConvert.convertRefund(refund, vo); StudioCashierRecord toModify = RefundConvert.convertRefund(refund, vo);
studioCashierRecordRpService.updateById(toModify); studioCashierRecordRpService.updateById(toModify);
return vo; return RefundConvert.convertRefundVO(refund, vo);
} }
@Override @Override
...@@ -154,4 +195,44 @@ public class RefundServiceImpl implements RefundService { ...@@ -154,4 +195,44 @@ public class RefundServiceImpl implements RefundService {
Lists.newArrayList("FitSeeXmPay", "FitManageXmPay"), false); 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; ...@@ -2,7 +2,6 @@ package com.jiejing.fitness.finance.service.utils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.Objects;
/** /**
* @author chengyubing * @author chengyubing
...@@ -14,7 +13,7 @@ public class FeeUtil { ...@@ -14,7 +13,7 @@ public class FeeUtil {
* 【支付】计算支付手续费,保留小数点后两位,四舍五入 * 【支付】计算支付手续费,保留小数点后两位,四舍五入
* *
* @param feeRate 费率(%) * @param feeRate 费率(%)
* @param amount 支付金额 * @param amount 支付金额(元)
* @return 手续费 * @return 手续费
*/ */
public static BigDecimal calPayFee(BigDecimal feeRate, BigDecimal amount) { public static BigDecimal calPayFee(BigDecimal feeRate, BigDecimal amount) {
...@@ -24,23 +23,23 @@ public class FeeUtil { ...@@ -24,23 +23,23 @@ public class FeeUtil {
} }
/** /**
* 【退款】计算退款手续费 * 【退款】计算退款手续费(元)
* *
* <p>实退手续费计算公式:退款手续费=向下取整(退款金额*原交易手续费金额/原交易金额)</p> * <p>实退手续费计算公式:退款手续费=向下取整(退款金额*原交易手续费金额/原交易金额)</p>
* *
* @param refundTransAmount 退款申请金额 * @param refundTransAmount 退款申请金额(元)
* @param payTransAmount 原支付交易金额 * @param payTransAmount 原支付交易金额(元)
* @param payActualAmount 用户实收金额 * @param payActualAmount 用户实收金额(元)
* @param payFee 平台实收手续费 * @param payFee 平台实收手续费(元)
* @param historyRefundActualAmount 历史实退金额 * @param historyRefundActualAmount 历史实退金额(元)
* @return 本次退款应退手续费 * @return 本次退款应退手续费(元)
*/ */
public static BigDecimal calculateRefundFee(BigDecimal refundTransAmount, BigDecimal payTransAmount, public static BigDecimal calculateRefundFee(BigDecimal refundTransAmount, BigDecimal payTransAmount,
BigDecimal payActualAmount, BigDecimal payFee, BigDecimal historyRefundActualAmount) { BigDecimal payActualAmount, BigDecimal payFee, BigDecimal historyRefundActualAmount) {
// 机构剩余实收金额 // 机构剩余实收金额
BigDecimal leftPayActualAmount = MoneyUtil.subtract(payActualAmount, 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); RoundingMode.FLOOR);
// 试算实退金额 // 试算实退金额
BigDecimal trialActualAmount = MoneyUtil.subtract(refundTransAmount, trialFee); BigDecimal trialActualAmount = MoneyUtil.subtract(refundTransAmount, trialFee);
...@@ -49,77 +48,17 @@ public class FeeUtil { ...@@ -49,77 +48,17 @@ public class FeeUtil {
: MoneyUtil.add(trialFee, MoneyUtil.subtract(trialActualAmount, leftPayActualAmount)); : 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) { public static void main(String[] args) {
// 1. 首次退款 BigDecimal refundTransAmount = new BigDecimal("100");
// BigDecimal refundTransAmount = new BigDecimal("498"); BigDecimal payTransAmount = new BigDecimal("100");
// BigDecimal payTransAmount = new BigDecimal("500"); BigDecimal payActualAmount = new BigDecimal("99.43");
// BigDecimal payActualAmount = new BigDecimal("498"); BigDecimal payFee = new BigDecimal("0.57");
// BigDecimal payFee = new BigDecimal("2"); BigDecimal historyRefundActualAmount = new BigDecimal("99.43");
// 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");
// 预计输出:1 // 预计输出:1
// 实退金额:0,实退手续费:1。剩余可退金额:0,历史实退金额:498,历史实退手续费:2 // 实退金额:0,实退手续费:1。剩余可退金额:0,历史实退金额:498,历史实退手续费:2
System.out.println( System.out.println(
FeeUtil.calculateRefundFee(refundTransAmount, payTransAmount, payActualAmount, payFee, FeeUtil.calculateRefundFee(refundTransAmount, payTransAmount, payActualAmount, payFee,
historyRefundActualAmount)); 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