Commit 1751aced by 程裕兵

feat:pay refund and settle

parent 7e63c19d
...@@ -26,6 +26,7 @@ import com.jiejing.fitness.finance.service.merchant.StudioMerchantService; ...@@ -26,6 +26,7 @@ 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;
import com.jiejing.fitness.finance.service.merchant.params.PageStudioMerchantApplyParams; import com.jiejing.fitness.finance.service.merchant.params.PageStudioMerchantApplyParams;
import com.jiejing.fitness.finance.service.pay.PayService; import com.jiejing.fitness.finance.service.pay.PayService;
import com.jiejing.fitness.finance.service.pay.RefundService;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.paycenter.common.model.vo.PayVO; import com.jiejing.paycenter.common.model.vo.PayVO;
...@@ -51,6 +52,9 @@ public class StudioMerchantController implements StudioMerchantApi { ...@@ -51,6 +52,9 @@ public class StudioMerchantController implements StudioMerchantApi {
@Resource @Resource
private PayService payService; private PayService payService;
@Resource
private RefundService refundService;
@ApiOperation(value = "场馆入驻商户", tags = {TAG}) @ApiOperation(value = "场馆入驻商户", tags = {TAG})
@PostMapping(value = "/private/studioMerchant/getMerchant") @PostMapping(value = "/private/studioMerchant/getMerchant")
@Override @Override
...@@ -179,7 +183,7 @@ public class StudioMerchantController implements StudioMerchantApi { ...@@ -179,7 +183,7 @@ public class StudioMerchantController implements StudioMerchantApi {
@Override @Override
public JsonResult<RefundVO> refund(@RequestBody @Valid StudioMerchantRefundRequest request) { public JsonResult<RefundVO> refund(@RequestBody @Valid StudioMerchantRefundRequest request) {
StudioMerchantRefundParams params = BeanUtil.map(request, StudioMerchantRefundParams.class); StudioMerchantRefundParams params = BeanUtil.map(request, StudioMerchantRefundParams.class);
return JsonResult.success(payService.merchantRefund(params)); return JsonResult.success(refundService.merchantRefund(params));
} }
} }
package com.jiejing.fitness.finance.app.controller.task; package com.jiejing.fitness.finance.app.controller.task;
import com.jiejing.common.model.JsonResult; import com.jiejing.common.model.JsonResult;
import com.jiejing.common.utils.time.TimeUtil;
import com.jiejing.fitness.finance.api.task.FitFinanceTaskApi; import com.jiejing.fitness.finance.api.task.FitFinanceTaskApi;
import com.jiejing.fitness.finance.api.task.request.CheckSettleRequest; import com.jiejing.fitness.finance.api.task.request.CheckSettleRequest;
import com.jiejing.fitness.finance.service.pay.PayService; import com.jiejing.fitness.finance.service.pay.SettleService;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import java.util.Date;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
...@@ -18,14 +20,16 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -18,14 +20,16 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class FitFinanceTaskController implements FitFinanceTaskApi { public class FitFinanceTaskController implements FitFinanceTaskApi {
@Resource @Resource
private PayService payService; private SettleService settleService;
@ApiOperation(value = "对账", tags = {TAG}) @ApiOperation(value = "对账", tags = {TAG})
@PostMapping(value = "/private/task/checkSettle") @PostMapping(value = "/private/task/checkSettle")
@Override @Override
public JsonResult<Void> checkSettle(@Valid @RequestBody CheckSettleRequest request) { public JsonResult<Void> checkSettle(@Valid @RequestBody CheckSettleRequest request) {
payService.checkSettle(request.getMerchantId(), request.getSettleDate()); settleService.checkSettle(request.getMerchantId(), request.getSettleDate());
return JsonResult.success(); return JsonResult.success();
} }
...@@ -33,7 +37,7 @@ public class FitFinanceTaskController implements FitFinanceTaskApi { ...@@ -33,7 +37,7 @@ public class FitFinanceTaskController implements FitFinanceTaskApi {
@PostMapping(value = "/private/task/syncSettle") @PostMapping(value = "/private/task/syncSettle")
@Override @Override
public JsonResult<Void> syncSettle(@Valid @RequestBody CheckSettleRequest request) { public JsonResult<Void> syncSettle(@Valid @RequestBody CheckSettleRequest request) {
payService.syncSettle(request.getMerchantId(), request.getSettleDate()); settleService.syncSettle(request.getMerchantId(), request.getSettleDate());
return JsonResult.success(); return JsonResult.success();
} }
......
...@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON; ...@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON;
import com.jiejing.fitness.enums.finance.PartyTypeEnum; import com.jiejing.fitness.enums.finance.PartyTypeEnum;
import com.jiejing.fitness.finance.service.merchant.StudioMerchantService; import com.jiejing.fitness.finance.service.merchant.StudioMerchantService;
import com.jiejing.fitness.finance.service.pay.PayService; import com.jiejing.fitness.finance.service.pay.PayService;
import com.jiejing.fitness.finance.service.pay.RefundService;
import com.jiejing.paycenter.common.event.MerchantEvent; import com.jiejing.paycenter.common.event.MerchantEvent;
import com.jiejing.paycenter.common.event.PayEvent; import com.jiejing.paycenter.common.event.PayEvent;
import com.jiejing.paycenter.common.event.RefundEvent; import com.jiejing.paycenter.common.event.RefundEvent;
...@@ -33,6 +34,8 @@ public class ListenerService { ...@@ -33,6 +34,8 @@ public class ListenerService {
@Resource @Resource
private PayService payService; private PayService payService;
@Resource
private RefundService refundService;
@EventHandler(value = MerchantEvent.class, binder = "biz-kafka", maxAttempts = MAX_RETRY) @EventHandler(value = MerchantEvent.class, binder = "biz-kafka", maxAttempts = MAX_RETRY)
public void handleMerchantEvent(MerchantEvent event, @Header(DELIVERY_ATTEMPT) int retryNum) { public void handleMerchantEvent(MerchantEvent event, @Header(DELIVERY_ATTEMPT) int retryNum) {
...@@ -69,7 +72,7 @@ public class ListenerService { ...@@ -69,7 +72,7 @@ public class ListenerService {
public void refundEventCallback(RefundEvent event, @Header(DELIVERY_ATTEMPT) int retryNum) { public void refundEventCallback(RefundEvent event, @Header(DELIVERY_ATTEMPT) int retryNum) {
try { try {
log.info("start process refund event {}", JSON.toJSONString(event)); log.info("start process refund event {}", JSON.toJSONString(event));
payService.refundCallback(event); refundService.refundCallback(event);
} catch (Exception e) { } catch (Exception e) {
log.info("process refund event fail {}", event.getTransNo(), e); log.info("process refund event fail {}", event.getTransNo(), e);
} }
......
...@@ -3,13 +3,9 @@ package com.jiejing.fitness.finance.service.pay; ...@@ -3,13 +3,9 @@ package com.jiejing.fitness.finance.service.pay;
import com.jiejing.fitness.finance.service.pay.params.AppPayParams; import com.jiejing.fitness.finance.service.pay.params.AppPayParams;
import com.jiejing.fitness.finance.service.pay.params.NativePayParams; import com.jiejing.fitness.finance.service.pay.params.NativePayParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.paycenter.common.enums.common.PayChannelEnums; import com.jiejing.paycenter.common.enums.common.PayChannelEnums;
import com.jiejing.paycenter.common.model.vo.PayVO; import com.jiejing.paycenter.common.model.vo.PayVO;
import com.jiejing.paycenter.common.event.PayEvent; import com.jiejing.paycenter.common.event.PayEvent;
import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO;
import java.util.Date;
/** /**
* @author chengyubing * @author chengyubing
...@@ -44,7 +40,7 @@ public interface PayService { ...@@ -44,7 +40,7 @@ public interface PayService {
PayVO appPay(AppPayParams params); PayVO appPay(AppPayParams params);
/** /**
* 支付 * 商户版支付
* *
* @param params 请求参数 * @param params 请求参数
* @return 结果 * @return 结果
...@@ -58,35 +54,4 @@ public interface PayService { ...@@ -58,35 +54,4 @@ public interface PayService {
*/ */
void payCallback(PayEvent event); void payCallback(PayEvent event);
/**
* 商户退款
*
* @param params 参数
* @return 结果
*/
RefundVO merchantRefund(StudioMerchantRefundParams params);
/**
* 退款回调
*
* @param event 事件
*/
void refundCallback(RefundEvent event);
/**
* 对账
*
* @param merchantId 商户ID
* @param settleDate 结算日
*/
void checkSettle(Long merchantId, Date settleDate);
/**
* 同步结算状态
*
* @param merchantId 商户ID
* @param settleDate 结算日
*/
void syncSettle(Long merchantId, Date settleDate);
} }
package com.jiejing.fitness.finance.service.pay;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO;
/**
* @author chengyubing
* @since 2024/5/7 16:37
*/
public interface RefundService {
/**
* 商户退款
*
* @param params 参数
* @return 结果
*/
RefundVO merchantRefund(StudioMerchantRefundParams params);
/**
* 退款回调
*
* @param event 事件
*/
void refundCallback(RefundEvent event);
}
package com.jiejing.fitness.finance.service.pay;
import java.util.Date;
/**
* @author chengyubing
* @since 2024/5/7 16:32
*/
public interface SettleService {
/**
* 对账
*
* @param merchantId 商户ID
* @param settleDate 结算日
*/
void checkSettle(Long merchantId, Date settleDate);
/**
* 同步结算状态
*
* @param merchantId 商户ID
* @param settleDate 结算日
*/
void syncSettle(Long merchantId, Date settleDate);
}
...@@ -2,6 +2,7 @@ package com.jiejing.fitness.finance.service.pay.convert; ...@@ -2,6 +2,7 @@ package com.jiejing.fitness.finance.service.pay.convert;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; 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;
...@@ -15,11 +16,16 @@ import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord; ...@@ -15,11 +16,16 @@ import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.entity.StudioCheckSettleRecord; import com.jiejing.fitness.finance.repository.entity.StudioCheckSettleRecord;
import com.jiejing.fitness.finance.repository.entity.StudioMerchantApply; import com.jiejing.fitness.finance.repository.entity.StudioMerchantApply;
import com.jiejing.fitness.finance.repository.entity.StudioSettleRecord; import com.jiejing.fitness.finance.repository.entity.StudioSettleRecord;
import com.jiejing.fitness.finance.service.config.PayChannelProperties;
import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums; import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums;
import com.jiejing.fitness.finance.service.pay.enums.PayFailMessageReplaceEnums;
import com.jiejing.fitness.finance.service.pay.params.AppPayParams;
import com.jiejing.fitness.finance.service.pay.params.NativePayParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams;
import com.jiejing.fitness.finance.service.utils.FeeUtil; import com.jiejing.fitness.finance.service.utils.FeeUtil;
import com.jiejing.fitness.finance.service.utils.MoneyUtil; import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.paycenter.common.enums.common.PayChannelEnums;
import com.jiejing.paycenter.common.model.vo.MerchantVO; import com.jiejing.paycenter.common.model.vo.MerchantVO;
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;
...@@ -184,45 +190,7 @@ public class PayConvert { ...@@ -184,45 +190,7 @@ public class PayConvert {
return convertTransState(PayStateEnums.getByCode(state)); return convertTransState(PayStateEnums.getByCode(state));
} }
public static StudioCashierRecord convertRefundInit(StudioMerchantRefundParams params, public static BrandCashierTransStateEnum convertTransState(TransStateEnums state) {
StudioCashierRecord pay, BigDecimal historyRefundActualAmount) {
StudioCashierRecord record = BeanUtil.map(pay, StudioCashierRecord.class);
record.setId(IdWorker.getId());
record.setTransNo(record.getId().toString());
record.setOrderNo(params.getOrderNo());
record.setRelatedTransNo(params.getPayTransNo());
record.setTransAmount(params.getTransAmount());
record.setFeeRate(pay.getFeeRate());
record.setFee(
FeeUtil.calculateRefundFee(params.getTransAmount(), pay.getTransAmount(), pay.getActualAmount(),
pay.getFee(), historyRefundActualAmount));
record.setActualAmount(MoneyUtil.subtract(record.getTransAmount(), record.getFee()));
record.setRemark(params.getRefundReason());
record.setStudioId(params.getStudioId());
record.setTransState(BrandCashierTransStateEnum.REFUND_INIT.getCode());
record.setTradingTime(new Date());
record.setCreateTime(new Date());
record.setUpdateTime(new Date());
return record;
}
public static RefundPayRequest convert(StudioMerchantRefundParams params, StudioCashierRecord record) {
RefundPayRequest request = BeanUtil.map(params, RefundPayRequest.class);
request.setTransNo(record.getTransNo());
return request;
}
public static StudioCashierRecord convertRefund(StudioCashierRecord record, RefundVO vo) {
return StudioCashierRecord.builder()
.id(record.getId())
.transState(convertTransState(TransStateEnums.getByCode(vo.getRefundState())).getCode())
.failMessage(vo.getFailMessage())
.successTime(vo.getSuccessTime())
.updateTime(new Date())
.build();
}
private static BrandCashierTransStateEnum convertTransState(TransStateEnums state) {
switch (state) { switch (state) {
case SUCCESS: case SUCCESS:
return BrandCashierTransStateEnum.REFUND_SUCCESS; return BrandCashierTransStateEnum.REFUND_SUCCESS;
...@@ -235,55 +203,6 @@ public class PayConvert { ...@@ -235,55 +203,6 @@ public class PayConvert {
} }
} }
public static StudioCashierRecord convertRefund(StudioCashierRecord record, RefundEvent event) {
return StudioCashierRecord.builder()
.id(record.getId())
.transState(convertTransState(TransStateEnums.getByCode(event.getRefundState())).getCode())
.failMessage(event.getFailMessage())
.successTime(event.getSuccessTime())
.updateTime(new Date())
.build();
}
public static List<StudioSettleRecord> convertStudioSettle(MerchantSettleRecord record,
Map<Long, BigDecimal> studioPayAmountMap, Map<Long, BigDecimal> studioRefundAmountMap,
Map<Long, StudioVO> studioMap) {
// A场馆支付了100,B场馆支付了0,B场馆发起退款-》成功。结算记录-》
// A场馆支付了0,B场馆支付了0,B场馆发起退款-》失败
// A场馆支付了100,B场馆支付了0,B场馆发起退款-》成功,A场馆发起退款100-》失败
return studioPayAmountMap.keySet().stream().map(studioId -> StudioSettleRecord.builder()
.id(IdWorker.getId())
.parentId(record.getId())
.studioId(studioId)
.studioName(Optional.ofNullable(studioMap.get(studioId)).map(StudioVO::getName).orElse("-"))
.merchantId(record.getMerchantId())
.merchantNo(record.getMerchantNo())
.transState(record.getTransState())
.transAmount(MoneyUtil.subtract(studioPayAmountMap.getOrDefault(studioId, BigDecimal.ZERO),
studioRefundAmountMap.getOrDefault(studioId, BigDecimal.ZERO)))
.settleDate(record.getSettleDate())
.cardNo(record.getCardNo())
.bankName(record.getBankName())
.failMsg(record.getFailMsg())
.salt(record.getSalt())
.createTime(record.getCreateTime())
.updateTime(record.getUpdateTime())
.build()).collect(Collectors.toList());
}
public static StudioCheckSettleRecord convertCheckSettle(StudioMerchantApply apply, SettleVO vo,
BigDecimal totalAmount) {
return StudioCheckSettleRecord.builder()
.id(IdWorker.getId())
.merchantId(apply.getMerchantId())
.merchantNo(apply.getMerchantNo())
.checkState(TransStateEnums.FAIL.getCode())
.failMsg("对账失败,乐动收银入账中金额【" + totalAmount + "】,汇付结算金额【" + vo.getTransAmount() + "】")
.settleDate(vo.getTransDate())
.createTime(new Date())
.updateTime(new Date())
.build();
}
public static PayEvent convertEvent(PayRequest request, PayVO vo) { public static PayEvent convertEvent(PayRequest request, PayVO vo) {
return PayEvent.builder() return PayEvent.builder()
...@@ -300,38 +219,56 @@ public class PayConvert { ...@@ -300,38 +219,56 @@ public class PayConvert {
.build(); .build();
} }
public static MerchantSettleRecord convertMerchantSettle(StudioMerchantApply apply, SettleVO vo) { public static PayRequest convertNativePay(NativePayParams params, PayChannelProperties config) {
String salt = AesUtil.getSalt(8); PayRequest req = new PayRequest();
Date now = new Date(); req.setChannelNo(getChannelNo(params.getChannel(), config));
return MerchantSettleRecord.builder() req.setTransNo(IdWorker.getIdStr());
.id(IdWorker.getId()) req.setAmount(params.getAmount());
.merchantId(apply.getMerchantId()) req.setPayType(PayTypeEnums.NATIVE);
.merchantNo(apply.getMerchantNo()) req.setGoods(params.getGoods());
.transState(vo.getTransState()) req.setOrderNo(params.getOrderNo());
.transAmount(vo.getTransAmount()) req.setOrderType(params.getOrderType().getCode());
.settleDate(vo.getTransDate()) req.setTimeExpire(params.getTimeExpire());
.cardNo(AesUtil.encrypt(salt, vo.getCardNo())) req.setTradingTime(new Date());
.bankName(vo.getBankName()) req.setExtra(params.getExtra());
.failMsg(vo.getFailMsg()) return req;
.salt(salt)
.createTime(now)
.updateTime(now).build();
} }
public static MerchantSettleRecord convertMerchantSettle(MerchantSettleRecord history, SettleVO vo) { public static PayRequest convertAppPay(AppPayParams params, PayChannelProperties config) {
return MerchantSettleRecord.builder() PayRequest req = new PayRequest();
.id(history.getId()) req.setChannelNo(getChannelNo(params.getChannel(), config));
.transState(vo.getTransState()) req.setTransNo(IdWorker.getIdStr());
.failMsg(vo.getFailMsg()) req.setAmount(params.getAmount());
.updateTime(new Date()).build(); req.setPayType(PayTypeEnums.APP);
req.setGoods(params.getGoods());
req.setOrderNo(params.getOrderNo());
req.setOrderType(params.getOrderType().getCode());
req.setTimeExpire(params.getTimeExpire());
req.setTradingTime(new Date());
req.setExtra(params.getExtra());
return req;
} }
public static StudioSettleRecord convertStudioSettle(MerchantSettleRecord history, SettleVO vo) { private static String getChannelNo(PayChannelEnums channel, PayChannelProperties config) {
return StudioSettleRecord.builder() switch (channel) {
.parentId(history.getId()) case WX:
.transState(vo.getTransState()) return config.getWxApp();
.failMsg(vo.getFailMsg()) case ALI:
.updateTime(new Date()).build(); return config.getAliApp();
default:
throw new BizException(FinanceErrorEnums.NOT_SUPPORT_TYPE);
}
}
public static AlipaySystemOauthTokenRequest convertAlipaySystemOauthTokenRequest(String authCode) {
AlipaySystemOauthTokenRequest aliRequest = new AlipaySystemOauthTokenRequest();
aliRequest.setCode(authCode);
aliRequest.setGrantType("authorization_code");
return aliRequest;
}
public static FinanceErrorEnums replaceFailMessage(String failMsg) {
return PayFailMessageReplaceEnums.convertBySource(failMsg);
} }
} }
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.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.utils.FeeUtil;
import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.paycenter.api.pay.request.RefundPayRequest;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author chengyubing
* @since 2024/5/7 16:48
*/
public class RefundConvert {
public static StudioCashierRecord convertRefundInit(StudioMerchantRefundParams params,
StudioCashierRecord pay, BigDecimal historyRefundActualAmount) {
StudioCashierRecord record = BeanUtil.map(pay, StudioCashierRecord.class);
record.setId(IdWorker.getId());
record.setTransNo(record.getId().toString());
record.setOrderNo(params.getOrderNo());
record.setRelatedTransNo(params.getPayTransNo());
record.setTransAmount(params.getTransAmount());
record.setFeeRate(pay.getFeeRate());
record.setFee(
FeeUtil.calculateRefundFee(params.getTransAmount(), pay.getTransAmount(), pay.getActualAmount(),
pay.getFee(), historyRefundActualAmount));
record.setActualAmount(MoneyUtil.subtract(record.getTransAmount(), record.getFee()));
record.setRemark(params.getRefundReason());
record.setStudioId(params.getStudioId());
record.setTransState(BrandCashierTransStateEnum.REFUND_INIT.getCode());
record.setTradingTime(new Date());
record.setCreateTime(new Date());
record.setUpdateTime(new Date());
return record;
}
public static RefundPayRequest convert(StudioMerchantRefundParams params, StudioCashierRecord record) {
RefundPayRequest request = BeanUtil.map(params, RefundPayRequest.class);
request.setTransNo(record.getTransNo());
return request;
}
public static StudioCashierRecord convertRefund(StudioCashierRecord record, RefundVO vo) {
return StudioCashierRecord.builder()
.id(record.getId())
.transState(PayConvert.convertTransState(TransStateEnums.getByCode(vo.getRefundState())).getCode())
.failMessage(vo.getFailMessage())
.successTime(vo.getSuccessTime())
.updateTime(new Date())
.build();
}
public static StudioCashierRecord convertRefund(StudioCashierRecord record, RefundEvent event) {
return StudioCashierRecord.builder()
.id(record.getId())
.transState(PayConvert.convertTransState(TransStateEnums.getByCode(event.getRefundState())).getCode())
.failMessage(event.getFailMessage())
.successTime(event.getSuccessTime())
.updateTime(new Date())
.build();
}
}
package com.jiejing.fitness.finance.service.pay.convert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.jiejing.common.utils.crypt.AesUtil;
import com.jiejing.fitness.finance.repository.entity.MerchantSettleRecord;
import com.jiejing.fitness.finance.repository.entity.StudioCheckSettleRecord;
import com.jiejing.fitness.finance.repository.entity.StudioMerchantApply;
import com.jiejing.fitness.finance.repository.entity.StudioSettleRecord;
import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.model.vo.SettleVO;
import com.jiejing.studio.api.studio.vo.StudioVO;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author chengyubing
* @since 2024/5/7 16:48
*/
public class SettleConvert {
public static MerchantSettleRecord convertMerchantSettle(StudioMerchantApply apply, SettleVO vo) {
String salt = AesUtil.getSalt(8);
Date now = new Date();
return MerchantSettleRecord.builder()
.id(IdWorker.getId())
.merchantId(apply.getMerchantId())
.merchantNo(apply.getMerchantNo())
.transState(vo.getTransState())
.transAmount(vo.getTransAmount())
.settleDate(vo.getTransDate())
.cardNo(AesUtil.encrypt(salt, vo.getCardNo()))
.bankName(vo.getBankName())
.failMsg(vo.getFailMsg())
.salt(salt)
.createTime(now)
.updateTime(now).build();
}
public static MerchantSettleRecord convertMerchantSettle(MerchantSettleRecord history, SettleVO vo) {
return MerchantSettleRecord.builder()
.id(history.getId())
.transState(vo.getTransState())
.failMsg(vo.getFailMsg())
.updateTime(new Date()).build();
}
public static StudioSettleRecord convertStudioSettle(MerchantSettleRecord history, SettleVO vo) {
return StudioSettleRecord.builder()
.parentId(history.getId())
.transState(vo.getTransState())
.failMsg(vo.getFailMsg())
.updateTime(new Date()).build();
}
public static List<StudioSettleRecord> convertStudioSettle(MerchantSettleRecord record,
Map<Long, BigDecimal> studioPayAmountMap, Map<Long, BigDecimal> studioRefundAmountMap,
Map<Long, StudioVO> studioMap) {
// A场馆支付了100,B场馆支付了0,B场馆发起退款-》成功。结算记录-》
// A场馆支付了0,B场馆支付了0,B场馆发起退款-》失败
// A场馆支付了100,B场馆支付了0,B场馆发起退款-》成功,A场馆发起退款100-》失败
return studioPayAmountMap.keySet().stream().map(studioId -> StudioSettleRecord.builder()
.id(IdWorker.getId())
.parentId(record.getId())
.studioId(studioId)
.studioName(Optional.ofNullable(studioMap.get(studioId)).map(StudioVO::getName).orElse("-"))
.merchantId(record.getMerchantId())
.merchantNo(record.getMerchantNo())
.transState(record.getTransState())
.transAmount(MoneyUtil.subtract(studioPayAmountMap.getOrDefault(studioId, BigDecimal.ZERO),
studioRefundAmountMap.getOrDefault(studioId, BigDecimal.ZERO)))
.settleDate(record.getSettleDate())
.cardNo(record.getCardNo())
.bankName(record.getBankName())
.failMsg(record.getFailMsg())
.salt(record.getSalt())
.createTime(record.getCreateTime())
.updateTime(record.getUpdateTime())
.build()).collect(Collectors.toList());
}
public static StudioCheckSettleRecord convertCheckSettle(StudioMerchantApply apply, SettleVO vo,
BigDecimal totalAmount) {
return StudioCheckSettleRecord.builder()
.id(IdWorker.getId())
.merchantId(apply.getMerchantId())
.merchantNo(apply.getMerchantNo())
.checkState(TransStateEnums.FAIL.getCode())
.failMsg("对账失败,乐动收银入账中金额【" + totalAmount + "】,汇付结算金额【" + vo.getTransAmount() + "】")
.settleDate(vo.getTransDate())
.createTime(new Date())
.updateTime(new Date())
.build();
}
}
...@@ -5,83 +5,60 @@ import static java.util.stream.Collectors.toList; ...@@ -5,83 +5,60 @@ import static java.util.stream.Collectors.toList;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
import com.alipay.api.DefaultAlipayClient; import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse; import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; 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.enums.tenant.TenantTypeEnum; import com.jiejing.fitness.enums.tenant.TenantTypeEnum;
import com.jiejing.fitness.finance.repository.entity.GlobalConfig; import com.jiejing.fitness.finance.repository.entity.GlobalConfig;
import com.jiejing.fitness.finance.repository.entity.MerchantSettleRecord;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord; import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.entity.PartyToMerchant; import com.jiejing.fitness.finance.repository.entity.PartyToMerchant;
import com.jiejing.fitness.finance.repository.entity.StudioMerchantApply;
import com.jiejing.fitness.finance.repository.service.GlobalConfigRpService; import com.jiejing.fitness.finance.repository.service.GlobalConfigRpService;
import com.jiejing.fitness.finance.repository.service.MerchantSettleRecordRpService;
import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService; import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService;
import com.jiejing.fitness.finance.repository.service.PartyToMerchantRpService; import com.jiejing.fitness.finance.repository.service.PartyToMerchantRpService;
import com.jiejing.fitness.finance.repository.service.StudioCheckSettleRecordRpService;
import com.jiejing.fitness.finance.repository.service.StudioMerchantApplyRpService;
import com.jiejing.fitness.finance.repository.service.StudioSettleRecordRpService;
import com.jiejing.fitness.finance.service.config.AppUrlProperties; import com.jiejing.fitness.finance.service.config.AppUrlProperties;
import com.jiejing.fitness.finance.service.config.PayChannelProperties; import com.jiejing.fitness.finance.service.config.PayChannelProperties;
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.pay.PayService; import com.jiejing.fitness.finance.service.pay.PayService;
import com.jiejing.fitness.finance.service.pay.convert.PayConvert; import com.jiejing.fitness.finance.service.pay.convert.PayConvert;
import com.jiejing.fitness.finance.service.pay.enums.PayFailMessageReplaceEnums;
import com.jiejing.fitness.finance.service.pay.params.AppPayParams; import com.jiejing.fitness.finance.service.pay.params.AppPayParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantRefundParams;
import com.jiejing.fitness.finance.service.pay.params.NativePayParams; import com.jiejing.fitness.finance.service.pay.params.NativePayParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams; import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams;
import com.jiejing.fitness.finance.service.rpc.MerchantRpcService; import com.jiejing.fitness.finance.service.rpc.MerchantRpcService;
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.DingUtil;
import com.jiejing.fitness.finance.service.utils.FeeUtil; import com.jiejing.fitness.finance.service.utils.FeeUtil;
import com.jiejing.fitness.finance.service.utils.MoneyUtil; 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.common.enums.common.PayChannelEnums; import com.jiejing.paycenter.common.enums.common.PayChannelEnums;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.enums.pay.PayStateEnums; import com.jiejing.paycenter.common.enums.pay.PayStateEnums;
import com.jiejing.paycenter.common.enums.pay.PayTypeEnums;
import com.jiejing.paycenter.common.model.vo.MerchantVO; import com.jiejing.paycenter.common.model.vo.MerchantVO;
import com.jiejing.paycenter.api.pay.request.PayRequest; import com.jiejing.paycenter.api.pay.request.PayRequest;
import com.jiejing.paycenter.api.pay.request.RefundPayRequest;
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.event.PayEvent; import com.jiejing.paycenter.common.event.PayEvent;
import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.SettleVO;
import com.jiejing.studio.api.studio.vo.StudioVO; import com.jiejing.studio.api.studio.vo.StudioVO;
import com.jiejing.wechat.WeChatAuthService; import com.jiejing.wechat.WeChatAuthService;
import com.jiejing.wechat.vo.weChat.BaseAuthInfoVO; import com.jiejing.wechat.vo.weChat.BaseAuthInfoVO;
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.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.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
...@@ -95,9 +72,6 @@ public class PayServiceImpl implements PayService { ...@@ -95,9 +72,6 @@ public class PayServiceImpl implements PayService {
private static final BigDecimal MAX_AMOUNT = new BigDecimal("10000000"); private static final BigDecimal MAX_AMOUNT = new BigDecimal("10000000");
@Value("${spring.profiles.active}")
private String env;
@Resource @Resource
private PayChannelProperties config; private PayChannelProperties config;
...@@ -120,15 +94,6 @@ public class PayServiceImpl implements PayService { ...@@ -120,15 +94,6 @@ public class PayServiceImpl implements PayService {
private MerchantRpcService merchantRpcService; private MerchantRpcService merchantRpcService;
@Resource @Resource
private StudioSettleRecordRpService studioSettleRecordRpService;
@Resource
private StudioMerchantApplyRpService studioMerchantApplyRpService;
@Resource
private StudioCheckSettleRecordRpService studioCheckSettleRecordRpService;
@Resource
private WeChatAuthService weChatAuthService; private WeChatAuthService weChatAuthService;
@Resource @Resource
...@@ -140,9 +105,6 @@ public class PayServiceImpl implements PayService { ...@@ -140,9 +105,6 @@ public class PayServiceImpl implements PayService {
@Resource @Resource
private PermissionRpcService permissionRpcService; private PermissionRpcService permissionRpcService;
@Resource
private MerchantSettleRecordRpService merchantSettleRecordRpService;
@Resource(name = "financeThreadPool") @Resource(name = "financeThreadPool")
private Executor executor; private Executor executor;
...@@ -166,7 +128,8 @@ public class PayServiceImpl implements PayService { ...@@ -166,7 +128,8 @@ public class PayServiceImpl implements PayService {
.map(BaseAuthInfoVO::getOpenId).orElse(null); .map(BaseAuthInfoVO::getOpenId).orElse(null);
case ALI: case ALI:
try { try {
AlipaySystemOauthTokenResponse response = aliClientMap.get(appId).execute(convert(authCode)); AlipaySystemOauthTokenResponse response = aliClientMap.get(appId)
.execute(PayConvert.convertAlipaySystemOauthTokenRequest(authCode));
log.info("get ali openId response {}, {}, {}", appId, authCode, JSON.toJSONString(response)); log.info("get ali openId response {}, {}, {}", appId, authCode, JSON.toJSONString(response));
return response.getOpenId(); return response.getOpenId();
} catch (AlipayApiException e) { } catch (AlipayApiException e) {
...@@ -177,22 +140,14 @@ public class PayServiceImpl implements PayService { ...@@ -177,22 +140,14 @@ public class PayServiceImpl implements PayService {
} }
} }
public AlipaySystemOauthTokenRequest convert(String authCode) {
AlipaySystemOauthTokenRequest aliRequest = new AlipaySystemOauthTokenRequest();
aliRequest.setCode(authCode);
aliRequest.setGrantType("authorization_code");
return aliRequest;
}
@Override @Override
public PayVO nativePay(NativePayParams params) { public PayVO nativePay(NativePayParams params) {
return payRpcService.pay(convertNativePay(params)); return payRpcService.pay(PayConvert.convertNativePay(params, config));
} }
@Override @Override
public PayVO appPay(AppPayParams params) { public PayVO appPay(AppPayParams params) {
return payRpcService.pay(convertAppPay(params)); return payRpcService.pay(PayConvert.convertAppPay(params, config));
} }
@Override @Override
...@@ -211,7 +166,7 @@ public class PayServiceImpl implements PayService { ...@@ -211,7 +166,7 @@ public class PayServiceImpl implements PayService {
this.payCallback(PayConvert.convertEvent(request, vo)); this.payCallback(PayConvert.convertEvent(request, vo));
if (PayStateEnums.FAILED == PayStateEnums.getByCode(vo.getPayState())) { if (PayStateEnums.FAILED == PayStateEnums.getByCode(vo.getPayState())) {
throw new BizException(replaceFailMessage(vo.getFailMsg())); throw new BizException(PayConvert.replaceFailMessage(vo.getFailMsg()));
} }
return vo; return vo;
} }
...@@ -252,262 +207,6 @@ public class PayServiceImpl implements PayService { ...@@ -252,262 +207,6 @@ public class PayServiceImpl implements PayService {
} }
@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));
BigDecimal historyRefundActualAmount = studioCashierRecordRpService.sumRefundActualAmountByPayTransNo(
params.getPayTransNo());
StudioCashierRecord record = PayConvert.convertRefundInit(params, pay, historyRefundActualAmount);
studioCashierRecordRpService.insert(record);
return record;
});
RefundPayRequest request = PayConvert.convert(params, refund);
RefundVO vo = payRpcService.refund(request);
StudioCashierRecord toModify = PayConvert.convertRefund(refund, vo);
studioCashierRecordRpService.updateById(toModify);
return vo;
}
@Override
public void refundCallback(RefundEvent event) {
StudioCashierRecord record = studioCashierRecordRpService.getById(Long.parseLong(event.getTransNo()))
.orElse(null);
if (null == record) {
return;
}
StudioCashierRecord toModify = PayConvert.convertRefund(record, event);
studioCashierRecordRpService.updateById(toModify);
}
@Override
public void checkSettle(Long merchantId, Date settleDate) {
Date endDate = null == settleDate ? TimeUtil.local().startOfDay(new Date()) : settleDate;
AtomicInteger failCount = new AtomicInteger(0);
this.pageAndConsumer(merchantId, 200, apply -> {
// 同步历史处理中的结算记录
Date startDate = this.syncHistorySettle(apply.getMerchantId(), endDate);
MerchantSettleRecord exist = merchantSettleRecordRpService.getByMerchantIdAndSettleDate(
apply.getMerchantId(), endDate);
if (null != exist) {
return;
}
// 对账
SettleVO vo = payRpcService.syncSettle(apply.getMerchantId(), endDate);
BigDecimal totalPayAmount = studioCashierRecordRpService.sumMerchantPaySuccess(apply.getMerchantId(),
startDate, endDate);
BigDecimal totalRefundAmount = studioCashierRecordRpService.sumMerchantRefundSuccess(apply.getMerchantId(), startDate, endDate);
// 没有钱包,退款金额只能从入账中扣,因此收款的钱必须大于等于退款的钱
BigDecimal totalAmount = MoneyUtil.subtract(totalPayAmount, totalRefundAmount);
log.info("settle vo is {}, local total amount is {}, total pay amount is {}, total refund amount is {}",
JSON.toJSONString(vo), totalAmount, totalPayAmount, totalRefundAmount);
if (BigDecimal.ZERO.compareTo(vo.getTransAmount()) == 0
&& BigDecimal.ZERO.compareTo(totalAmount) == 0) {
// 没有交易
return;
}
if (vo.getTransAmount().compareTo(totalAmount) == 0) {
// 对账成功
Map<Long, BigDecimal> studioPayAmountMap = studioCashierRecordRpService.sumMerchantPaySuccessGroupByStudioId(
apply.getMerchantId(), startDate, endDate);
Map<Long, BigDecimal> studioRefundAmountMap = studioCashierRecordRpService.sumMerchantRefundSuccessGroupByStudioId(
apply.getMerchantId(), startDate, endDate);
Map<Long, StudioVO> studioMap = studioRpcService.mapStudio(
Lists.newArrayList(studioPayAmountMap.keySet()));
transactionTemplate.executeWithoutResult(action -> {
MerchantSettleRecord record = PayConvert.convertMerchantSettle(apply, vo);
merchantSettleRecordRpService.insert(record);
studioSettleRecordRpService.insertAll(
PayConvert.convertStudioSettle(record, studioPayAmountMap, studioRefundAmountMap, studioMap));
});
if (TransStateEnums.SUCCESS == TransStateEnums.getByCode(vo.getTransState())) {
// 结算成功,则更新收银流水状态为记录为入账成功
this.updatePayIn(apply.getMerchantId(), startDate, endDate);
}
} else {
// 对账失败
failCount.incrementAndGet();
studioCheckSettleRecordRpService.insert(PayConvert.convertCheckSettle(apply, vo, totalAmount));
}
});
// 钉钉消息
if (failCount.get() > 0) {
DingUtil.sendCheckSettleFail(env, failCount.get());
} else {
DingUtil.sendCheckSettleSuccess(env);
}
}
@Override
public void syncSettle(Long merchantId, Date settleDate) {
Date endDate = null == settleDate ? TimeUtil.local().startOfDay(new Date()) : settleDate;
this.pageAndConsumer(merchantId, 200,
apply -> this.syncHistorySettle(apply.getMerchantId(), endDate));
}
private Date syncHistorySettle(Long merchantId, Date endDate) {
Date startDate = TimeUtil.local().plus(endDate, -15, ChronoUnit.DAYS);
List<MerchantSettleRecord> list = merchantSettleRecordRpService.listInitAndProcessByMerchantIdAndBeforeOrEqualEndDate(
merchantId, endDate);
if (CollectionUtil.isEmpty(list)) {
return startDate;
}
for (int i = 0; i < list.size(); i++) {
MerchantSettleRecord history = list.get(i);
SettleVO vo = payRpcService.syncSettle(merchantId, history.getSettleDate());
if (vo == null) {
if (i == 0) {
return TimeUtil.local().plus(history.getSettleDate(), -15, ChronoUnit.DAYS);
} else {
return list.get(i - 1).getSettleDate();
}
}
TransStateEnums state = TransStateEnums.getByCode(vo.getTransState());
if (TransStateEnums.INIT == state || TransStateEnums.PROCESS == state) {
if (i == 0) {
return TimeUtil.local().plus(history.getSettleDate(), -15, ChronoUnit.DAYS);
} else {
return list.get(i - 1).getSettleDate();
}
}
transactionTemplate.executeWithoutResult(action -> {
merchantSettleRecordRpService.updateById(PayConvert.convertMerchantSettle(history, vo));
studioSettleRecordRpService.updateByParentId(PayConvert.convertStudioSettle(history, vo));
});
if (TransStateEnums.SUCCESS == state) {
this.updatePayIn(merchantId, null, history.getSettleDate());
}
startDate = history.getSettleDate();
}
return startDate;
}
private void updatePayIn(Long merchantId, Date startTime, Date endTime) {
Integer current = 0;
Integer size = 500;
do {
Page<StudioCashierRecord> page = studioCashierRecordRpService.pageMerchantPaySuccess(merchantId,
startTime, endTime, current, size);
if (CollectionUtil.isEmpty(page.getContent())) {
break;
}
try {
List<Long> ids = page.getContent().stream().map(StudioCashierRecord::getId).collect(toList());
studioCashierRecordRpService.updateByIds(
StudioCashierRecord.builder().transState(BrandCashierTransStateEnum.PAY_IN.getCode())
.inTime(endTime)
.updateTime(new Date()).build(), ids);
} finally {
current++;
}
if (!page.hasNext()) {
break;
}
} while (true);
}
public void pageAndConsumer(Long merchantId, Integer batch, Consumer<StudioMerchantApply> consumer) {
Long minId = 0L;
log.info("consumer merchant apply start");
do {
List<StudioMerchantApply> list = studioMerchantApplyRpService.listByMinId(minId, merchantId, batch);
if (CollectionUtil.isEmpty(list)) {
break;
}
try {
list.forEach(apply -> {
try {
log.info("start consumer merchant apply {}, {}", apply.getMerchantId(), apply.getMerchantNo());
consumer.accept(apply);
log.info("end consumer merchant apply {}, {}", apply.getMerchantId(), apply.getMerchantNo());
} catch (Exception e) {
log.error("consumer apply fail apply {}, {}", apply.getMerchantId(), apply.getMerchantNo(), e);
}
});
} finally {
minId = list.get(list.size() - 1).getId();
}
} while (true);
log.info("consumer merchant apply finished");
}
private String getChannelNo(NativePayParams params) {
switch (params.getChannel()) {
case WX:
return config.getWxNative();
case ALI:
return config.getAliNative();
default:
throw new BizException(FinanceErrorEnums.NOT_SUPPORT_TYPE);
}
}
private String getChannelNo(AppPayParams params) {
switch (params.getChannel()) {
case WX:
return config.getWxApp();
case ALI:
return config.getAliApp();
default:
throw new BizException(FinanceErrorEnums.NOT_SUPPORT_TYPE);
}
}
private PayRequest convertNativePay(NativePayParams params) {
PayRequest req = new PayRequest();
req.setChannelNo(getChannelNo(params));
req.setTransNo(IdWorker.getIdStr());
req.setAmount(params.getAmount());
req.setPayType(PayTypeEnums.NATIVE);
req.setGoods(params.getGoods());
req.setOrderNo(params.getOrderNo());
req.setOrderType(params.getOrderType().getCode());
req.setTimeExpire(params.getTimeExpire());
req.setTradingTime(new Date());
req.setExtra(params.getExtra());
return req;
}
private PayRequest convertAppPay(AppPayParams params) {
PayRequest req = new PayRequest();
req.setChannelNo(getChannelNo(params));
req.setTransNo(IdWorker.getIdStr());
req.setAmount(params.getAmount());
req.setPayType(PayTypeEnums.APP);
req.setGoods(params.getGoods());
req.setOrderNo(params.getOrderNo());
req.setOrderType(params.getOrderType().getCode());
req.setTimeExpire(params.getTimeExpire());
req.setTradingTime(new Date());
req.setExtra(params.getExtra());
return req;
}
private List<Long> getAdminIds(Long studioId) { private List<Long> getAdminIds(Long studioId) {
List<Long> userIds = this.getUserIds(studioId); List<Long> userIds = this.getUserIds(studioId);
if (CollectionUtil.isEmpty(userIds)) { if (CollectionUtil.isEmpty(userIds)) {
...@@ -586,9 +285,6 @@ public class PayServiceImpl implements PayService { ...@@ -586,9 +285,6 @@ public class PayServiceImpl implements PayService {
return Pair.of(relation, studio); return Pair.of(relation, studio);
} }
private FinanceErrorEnums replaceFailMessage(String failMsg) {
return PayFailMessageReplaceEnums.convertBySource(failMsg);
}
@Data @Data
private static class AliInfo { private static class AliInfo {
......
package com.jiejing.fitness.finance.service.pay.impl;
import com.jiejing.common.exception.BizException;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService;
import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums;
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.paycenter.api.pay.request.RefundPayRequest;
import com.jiejing.paycenter.common.event.RefundEvent;
import com.jiejing.paycenter.common.model.vo.RefundVO;
import java.math.BigDecimal;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @author chengyubing
* @since 2024/5/7 16:37
*/
@Slf4j
@Service
public class RefundServiceImpl implements RefundService {
@Resource
private PayRpcService payRpcService;
@Resource
private StudioCashierRecordRpService studioCashierRecordRpService;
@Resource
private TransactionTemplate transactionTemplate;
@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));
BigDecimal historyRefundActualAmount = studioCashierRecordRpService.sumRefundActualAmountByPayTransNo(
params.getPayTransNo());
StudioCashierRecord record = RefundConvert.convertRefundInit(params, pay, historyRefundActualAmount);
studioCashierRecordRpService.insert(record);
return record;
});
RefundPayRequest request = RefundConvert.convert(params, refund);
RefundVO vo = payRpcService.refund(request);
StudioCashierRecord toModify = RefundConvert.convertRefund(refund, vo);
studioCashierRecordRpService.updateById(toModify);
return vo;
}
@Override
public void refundCallback(RefundEvent event) {
StudioCashierRecord record = studioCashierRecordRpService.getById(Long.parseLong(event.getTransNo()))
.orElse(null);
if (null == record) {
return;
}
StudioCashierRecord toModify = RefundConvert.convertRefund(record, event);
studioCashierRecordRpService.updateById(toModify);
}
}
package com.jiejing.fitness.finance.service.pay.impl;
import static java.math.BigDecimal.ZERO;
import static java.util.stream.Collectors.toList;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.jiejing.common.utils.collection.CollectionUtil;
import com.jiejing.common.utils.time.TimeUtil;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum;
import com.jiejing.fitness.finance.repository.entity.MerchantSettleRecord;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.entity.StudioMerchantApply;
import com.jiejing.fitness.finance.repository.service.MerchantSettleRecordRpService;
import com.jiejing.fitness.finance.repository.service.StudioCashierRecordRpService;
import com.jiejing.fitness.finance.repository.service.StudioCheckSettleRecordRpService;
import com.jiejing.fitness.finance.repository.service.StudioMerchantApplyRpService;
import com.jiejing.fitness.finance.repository.service.StudioSettleRecordRpService;
import com.jiejing.fitness.finance.service.pay.SettleService;
import com.jiejing.fitness.finance.service.pay.convert.SettleConvert;
import com.jiejing.fitness.finance.service.rpc.PayRpcService;
import com.jiejing.fitness.finance.service.rpc.StudioRpcService;
import com.jiejing.fitness.finance.service.utils.DingUtil;
import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.model.vo.SettleVO;
import com.jiejing.studio.api.studio.vo.StudioVO;
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @author chengyubing
* @since 2024/5/7 16:33
*/
@Slf4j
@Service
public class SettleServiceImpl implements SettleService {
@Value("${spring.profiles.active}")
private String env;
@Resource
private PayRpcService payRpcService;
@Resource
private StudioRpcService studioRpcService;
@Resource
private StudioCashierRecordRpService studioCashierRecordRpService;
@Resource
private StudioSettleRecordRpService studioSettleRecordRpService;
@Resource
private StudioMerchantApplyRpService studioMerchantApplyRpService;
@Resource
private StudioCheckSettleRecordRpService studioCheckSettleRecordRpService;
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private MerchantSettleRecordRpService merchantSettleRecordRpService;
@Override
public void checkSettle(Long merchantId, Date settleDate) {
Date endDate = null == settleDate ? TimeUtil.local().startOfDay(new Date()) : settleDate;
AtomicInteger failCount = new AtomicInteger(0);
this.pageAndConsumer(merchantId, 200, apply -> {
// 同步历史处理中的结算记录
Date startDate = this.syncHistorySettle(apply.getMerchantId(), endDate);
// 重复处理,直接return
if (this.repeat(apply, endDate)) {
return;
}
// 获取三方结算记录
SettleVO vo = payRpcService.syncSettle(apply.getMerchantId(), endDate);
// 获取本地待结算金额
BigDecimal totalAmount = this.getWaitSettleAmount(apply, startDate, endDate);
if (ZERO.compareTo(vo.getTransAmount()) == 0 && ZERO.compareTo(totalAmount) == 0) {
return;
}
if (vo.getTransAmount().compareTo(totalAmount) == 0) {
// 对账成功
this.doCheckSettleSuccess(vo, apply, startDate, endDate);
} else {
// 对账失败
this.doCheckSettleFail(failCount, vo, apply, totalAmount);
}
});
// 钉钉消息
this.doAfterCheckSettle(failCount);
}
@Override
public void syncSettle(Long merchantId, Date settleDate) {
Date endDate = null == settleDate ? TimeUtil.local().startOfDay(new Date()) : settleDate;
this.pageAndConsumer(merchantId, 200, apply -> this.syncHistorySettle(apply.getMerchantId(), endDate));
}
private Date syncHistorySettle(Long merchantId, Date endDate) {
Date startDate = TimeUtil.local().plus(endDate, -15, ChronoUnit.DAYS);
List<MerchantSettleRecord> list = merchantSettleRecordRpService.listInitAndProcessByMerchantIdAndBeforeOrEqualEndDate(
merchantId, endDate);
if (CollectionUtil.isEmpty(list)) {
return startDate;
}
for (int i = 0; i < list.size(); i++) {
MerchantSettleRecord history = list.get(i);
SettleVO vo = payRpcService.syncSettle(merchantId, history.getSettleDate());
if (vo == null) {
if (i == 0) {
return TimeUtil.local().plus(history.getSettleDate(), -15, ChronoUnit.DAYS);
} else {
return list.get(i - 1).getSettleDate();
}
}
TransStateEnums state = TransStateEnums.getByCode(vo.getTransState());
if (TransStateEnums.INIT == state || TransStateEnums.PROCESS == state) {
if (i == 0) {
return TimeUtil.local().plus(history.getSettleDate(), -15, ChronoUnit.DAYS);
} else {
return list.get(i - 1).getSettleDate();
}
}
transactionTemplate.executeWithoutResult(action -> {
merchantSettleRecordRpService.updateById(SettleConvert.convertMerchantSettle(history, vo));
studioSettleRecordRpService.updateByParentId(SettleConvert.convertStudioSettle(history, vo));
});
if (TransStateEnums.SUCCESS == state) {
this.updatePayIn(merchantId, null, history.getSettleDate());
}
startDate = history.getSettleDate();
}
return startDate;
}
private void updatePayIn(Long merchantId, Date startTime, Date endTime) {
Integer current = 0;
Integer size = 500;
do {
Page<StudioCashierRecord> page = studioCashierRecordRpService.pageMerchantPaySuccess(merchantId,
startTime, endTime, current, size);
if (CollectionUtil.isEmpty(page.getContent())) {
break;
}
try {
List<Long> ids = page.getContent().stream().map(StudioCashierRecord::getId).collect(toList());
studioCashierRecordRpService.updateByIds(
StudioCashierRecord.builder().transState(BrandCashierTransStateEnum.PAY_IN.getCode())
.inTime(endTime).updateTime(new Date()).build(), ids);
} finally {
current++;
}
if (!page.hasNext()) {
break;
}
} while (true);
}
public void pageAndConsumer(Long merchantId, Integer batch, Consumer<StudioMerchantApply> consumer) {
Long minId = 0L;
log.info("consumer merchant apply start");
do {
List<StudioMerchantApply> list = studioMerchantApplyRpService.listByMinId(minId, merchantId, batch);
if (CollectionUtil.isEmpty(list)) {
break;
}
try {
list.forEach(apply -> {
try {
log.info("start consumer merchant apply {}, {}", apply.getMerchantId(), apply.getMerchantNo());
consumer.accept(apply);
log.info("end consumer merchant apply {}, {}", apply.getMerchantId(), apply.getMerchantNo());
} catch (Exception e) {
log.error("consumer apply fail apply {}, {}", apply.getMerchantId(), apply.getMerchantNo(), e);
}
});
} finally {
minId = list.get(list.size() - 1).getId();
}
} while (true);
log.info("consumer merchant apply finished");
}
/**
* 计算待结算金额。对于给定的商户申请,在指定日期范围内,计算其成功支付金额减去成功退款金额的差额作为待结算金额。
*
* @param apply 商户申请信息,用于获取商户ID。
* @param startDate 计算范围的开始日期。
* @param endDate 计算范围的结束日期。
* @return BigDecimal类型的待结算金额。
*/
private BigDecimal getWaitSettleAmount(StudioMerchantApply apply, Date startDate, Date endDate) {
// 计算指定日期范围内商户的成功支付总额
BigDecimal totalPayAmount = studioCashierRecordRpService.sumMerchantPaySuccess(apply.getMerchantId(),
startDate, endDate);
// 计算指定日期范围内商户的成功退款总额
BigDecimal totalRefundAmount = studioCashierRecordRpService.sumMerchantRefundSuccess(
apply.getMerchantId(), startDate, endDate);
// 没有钱包,退款金额只能从入账中扣,因此收款的钱必须大于等于退款的钱
BigDecimal totalAmount = MoneyUtil.subtract(totalPayAmount, totalRefundAmount);
log.info("merchant {} local total amount is {}, total pay amount is {}, total refund amount is {}",
apply.getMerchantId(), totalAmount, totalPayAmount, totalRefundAmount);
return totalAmount;
}
private void doCheckSettleSuccess(SettleVO vo, StudioMerchantApply apply, Date startDate, Date endDate) {
// 对账成功
Map<Long, BigDecimal> studioPayAmountMap = studioCashierRecordRpService.sumMerchantPaySuccessGroupByStudioId(
apply.getMerchantId(), startDate, endDate);
Map<Long, BigDecimal> studioRefundAmountMap = studioCashierRecordRpService.sumMerchantRefundSuccessGroupByStudioId(
apply.getMerchantId(), startDate, endDate);
Map<Long, StudioVO> studioMap = studioRpcService.mapStudio(
Lists.newArrayList(studioPayAmountMap.keySet()));
transactionTemplate.executeWithoutResult(action -> {
MerchantSettleRecord record = SettleConvert.convertMerchantSettle(apply, vo);
merchantSettleRecordRpService.insert(record);
studioSettleRecordRpService.insertAll(
SettleConvert.convertStudioSettle(record, studioPayAmountMap, studioRefundAmountMap,
studioMap));
});
if (TransStateEnums.SUCCESS == TransStateEnums.getByCode(vo.getTransState())) {
// 结算成功,则更新收银流水状态为记录为入账成功
this.updatePayIn(apply.getMerchantId(), startDate, endDate);
}
}
private boolean repeat(StudioMerchantApply apply, Date endDate) {
MerchantSettleRecord exist = merchantSettleRecordRpService.getByMerchantIdAndSettleDate(
apply.getMerchantId(), endDate);
if (null != exist) {
return true;
}
return false;
}
/**
* 对账失败处理
*/
private void doCheckSettleFail(AtomicInteger failCount, SettleVO vo, StudioMerchantApply apply, BigDecimal totalAmount) {
failCount.incrementAndGet();
studioCheckSettleRecordRpService.insert(SettleConvert.convertCheckSettle(apply, vo, totalAmount));
}
private void doAfterCheckSettle(AtomicInteger failCount) {
if (failCount.get() > 0) {
DingUtil.sendCheckSettleFail(env, failCount.get());
} else {
DingUtil.sendCheckSettleSuccess(env);
}
}
}
...@@ -57,8 +57,10 @@ public class PayRpcService { ...@@ -57,8 +57,10 @@ public class PayRpcService {
.settleDate(settleDate) .settleDate(settleDate)
.build()); .build());
result.assertSuccess(); result.assertSuccess();
return Optional.ofNullable(result.getResult()) SettleVO vo = Optional.ofNullable(result.getResult())
.orElse(SettleVO.builder().transAmount(BigDecimal.ZERO).build()); .orElse(SettleVO.builder().transAmount(BigDecimal.ZERO).build());
log.info("merchant {} settle vo is {}", merchantId, JSON.toJSONString(vo));
return vo;
} }
public BigDecimal getFeeRate(PayRequest request) { public BigDecimal getFeeRate(PayRequest request) {
......
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