Commit a7604f96 by 程裕兵

feat:settle

parent 0bcc5c10
......@@ -27,10 +27,6 @@
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.jiejing.common</groupId>
<artifactId>ding-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -4,7 +4,6 @@ import com.jiejing.common.model.JsonResult;
import com.jiejing.fitness.finance.api.task.FitFinanceTaskApi;
import com.jiejing.fitness.finance.api.task.request.CheckSettleRequest;
import com.jiejing.fitness.finance.service.pay.PayService;
import feign.hystrix.FallbackFactory;
import io.swagger.annotations.ApiOperation;
import javax.annotation.Resource;
import javax.validation.Valid;
......@@ -22,7 +21,6 @@ public class FitFinanceTaskController implements FitFinanceTaskApi {
@Resource
private PayService payService;
@ApiOperation(value = "对账", tags = {TAG})
@PostMapping(value = "/private/task/checkSettle")
@Override
......
......@@ -41,50 +41,42 @@ public class StudioCheckSettleRecord implements Serializable {
private static final long serialVersionUID = -3981429835869732959L;
/**
* 备注: 主键
* 是否允许为null: YES
* 备注: 主键 是否允许为null: YES
*/
private Long id;
/**
* 备注: 商户ID
* 是否允许为null: YES
* 备注: 商户ID 是否允许为null: YES
*/
private Long merchantId;
/**
* 备注: 商户号
* 是否允许为null: YES
* 备注: 商户号 是否允许为null: YES
*/
private String merchantNo;
/**
* 备注: 对账状态
* 是否允许为null: YES
* 备注: 对账状态 是否允许为null: YES
*/
private String checkState;
/**
* 备注: 对账失败原因
* 是否允许为null: YES
* 备注: 对账失败原因 是否允许为null: YES
*/
private String failMsg;
/**
* 备注: 对账日期
* 是否允许为null: YES
* 备注: 对账日期 是否允许为null: YES
*/
private Date settleDate;
/**
* 备注: 创建时间
* 是否允许为null: YES
* 备注: 创建时间 是否允许为null: YES
*/
private Date createTime;
/**
* 备注: 更新时间
* 是否允许为null: YES
* 备注: 更新时间 是否允许为null: YES
*/
private Date updateTime;
......
......@@ -39,66 +39,68 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class StudioSettleRecord implements Serializable {
private static final long serialVersionUID = -1566792034500029034L;
private static final long serialVersionUID = -6892499742826838298L;
/**
* 备注: 主键
* 是否允许为null: NO
* 备注: 主键 是否允许为null: NO
*/
@TableId(value = "id", type = IdType.ID_WORKER)
private Long id;
/**
* 备注: 场馆ID
* 是否允许为null: YES
* 备注: 场馆ID 是否允许为null: YES
*/
private Long studioId;
/**
* 备注: 商户ID
* 是否允许为null: YES
* 备注: 商户ID 是否允许为null: YES
*/
private Long merchantId;
/**
* 备注: 商户号
* 是否允许为null: YES
* 备注: 商户号 是否允许为null: YES
*/
private String merchantNo;
/**
* 备注: 状态
* 是否允许为null: YES
* 备注: 状态 是否允许为null: YES
*
* @see com.jiejing.paycenter.common.enums.common.TransStateEnums
*/
private String transState;
/**
* 备注: 结算金额(元)
* 是否允许为null: YES
* 备注: 结算金额(元) 是否允许为null: YES
*/
private BigDecimal transAmount;
/**
* 备注: 结算银行卡号
* 是否允许为null: YES
* 备注: 结算银行卡号 是否允许为null: YES
*/
private String cardNo;
/**
* 备注: 结算日期
* 是否允许为null: YES
* 备注: 结算银行 是否允许为null: YES
*/
private String bankName;
/**
* 备注: 盐 是否允许为null: YES
*/
private String salt;
/**
* 备注: 结算日期 是否允许为null: YES
*/
private Date settleDate;
/**
* 备注: 创建时间
* 是否允许为null: YES
* 备注: 创建时间 是否允许为null: YES
*/
private Date createTime;
/**
* 备注: 更新时间
* 是否允许为null: YES
* 备注: 更新时间 是否允许为null: YES
*/
private Date updateTime;
......@@ -117,6 +119,10 @@ public class StudioSettleRecord implements Serializable {
public static final String CARD_NO = "card_no";
public static final String BANK_NAME = "bank_name";
public static final String SALT = "salt";
public static final String SETTLE_DATE = "settle_date";
public static final String CREATE_TIME = "create_time";
......
......@@ -18,6 +18,7 @@ import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.mbp.inject.XBaseMapper;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
/**
......@@ -34,4 +35,8 @@ public interface StudioCashierRecordMapper extends XBaseMapper<StudioCashierReco
BigDecimal sumMerchantPaySuccess(@Param("merchantId") Long merchantId,
@Param("startTime") Date startTime, @Param("endTime") Date endTime);
List<StudioCashierRecord> sumMerchantPaySuccessGroupByStudioId(@Param("merchantId") Long merchantId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}
......@@ -31,9 +31,20 @@
select sum(trans_amount)
from studio_cashier_record
where merchant_id = #{merchantId}
and trans_state in ('PAY_SUCCESS', 'PAY_IN')
and trans_state = 'PAY_SUCCESS'
and success_time >= #{startTime}
and success_time &lt; #{endTime}
</select>
<select id="sumMerchantPaySuccessGroupByStudioId"
resultType="com.jiejing.fitness.finance.repository.entity.StudioCashierRecord">
select studio_id, sum(trans_amount) as trans_amount
from studio_cashier_record
where merchant_id = #{merchantId}
and trans_state = 'PAY_SUCCESS'
and success_time >= #{startTime}
and success_time &lt; #{endTime}
group by studio_id
</select>
</mapper>
......@@ -17,7 +17,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, studio_id, merchant_id, merchant_no, trans_state, trans_amount, card_no, settle_date, create_time, update_time
id, studio_id, merchant_id, merchant_no, trans_state, trans_amount, card_no, bank_name, salt, settle_date, create_time, update_time
</sql>
</mapper>
......@@ -83,4 +83,11 @@ public class PartyToMerchantRpService extends
wrapper.eq(PartyToMerchant.PARTY_TYPE, partyType);
return this.baseMapper.selectList(wrapper);
}
public List<PartyToMerchant> listByMerchantIdAndPartyType(Long merchantId, PartyTypeEnum partyType) {
QueryWrapper<PartyToMerchant> wrapper = new QueryWrapper<>();
wrapper.eq(PartyToMerchant.MERCHANT_ID, merchantId);
wrapper.eq(PartyToMerchant.PARTY_TYPE, partyType);
return this.baseMapper.selectList(wrapper);
}
}
......@@ -15,14 +15,20 @@
package com.jiejing.fitness.finance.repository.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jiejing.common.utils.collection.CollectionUtil;
import com.jiejing.common.utils.text.StringUtil;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.mapper.StudioCashierRecordMapper;
import com.jiejing.fitness.finance.repository.query.PageBrandCashierRecordQuery;
import com.jiejing.mbp.MapperRepoService;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
......@@ -101,6 +107,25 @@ public class StudioCashierRecordRpService extends
}
public BigDecimal sumMerchantPaySuccess(Long merchantId, Date startTime, Date endTime) {
return this.baseMapper.sumMerchantPaySuccess(merchantId, startTime, endTime);
return Optional.ofNullable(this.baseMapper.sumMerchantPaySuccess(merchantId, startTime, endTime))
.orElse(BigDecimal.ZERO);
}
public Map<Long, BigDecimal> sumMerchantPaySuccessGroupByStudioId(Long merchantId,
Date startTime, Date endTime) {
return Optional.ofNullable(
this.baseMapper.sumMerchantPaySuccessGroupByStudioId(merchantId, startTime, endTime))
.orElse(new ArrayList<>(1))
.stream()
.collect(Collectors.toMap(StudioCashierRecord::getStudioId, StudioCashierRecord::getActualAmount));
}
public Page<StudioCashierRecord> pageMerchantPaySuccess(Long merchantId, Date startTime, Date endTime,
Integer current, Integer size) {
QueryWrapper<StudioCashierRecord> wrapper = new QueryWrapper<>();
wrapper.eq(StudioCashierRecord.MERCHANT_ID, merchantId);
wrapper.ge(StudioCashierRecord.SUCCESS_TIME, startTime);
wrapper.lt(StudioCashierRecord.SUCCESS_TIME, endTime);
return this.findByWrapperPage(wrapper, current, size);
}
}
......@@ -41,6 +41,7 @@ public class StudioSettleRecordRpService extends
wrapper.eq(StudioSettleRecord.MERCHANT_ID, merchantId);
wrapper.eq(StudioSettleRecord.SETTLE_DATE, settleDate);
wrapper.eq(StudioSettleRecord.TRANS_STATE, TransStateEnums.SUCCESS.getCode());
wrapper.orderByDesc(StudioSettleRecord.ID);
List<StudioSettleRecord> list = this.baseMapper.selectList(wrapper);
return Optional.ofNullable(list).orElse(new ArrayList<>()).stream().findFirst().orElse(null);
}
......
......@@ -57,8 +57,8 @@ public class GeneratorServiceEntity {
// "brand_cashier_record"
// "bank",
// "branch_bank",
// "studio_settle_record",
"studio_check_settle_record"
"studio_settle_record",
// "studio_check_settle_record"
};
/**
......
......@@ -65,6 +65,10 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.jiejing.common</groupId>
<artifactId>ding-client</artifactId>
</dependency>
</dependencies>
</project>
......@@ -5,16 +5,22 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.collect.Lists;
import com.jiejing.common.exception.BizException;
import com.jiejing.common.utils.convert.BeanUtil;
import com.jiejing.common.utils.crypt.AesUtil;
import com.jiejing.common.utils.text.StringUtil;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum;
import com.jiejing.fitness.enums.finance.BrandCashierTransTypeEnum;
import com.jiejing.fitness.finance.repository.entity.PartyToMerchant;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
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.enums.FinanceErrorEnums;
import com.jiejing.fitness.finance.service.pay.params.BrandMerchantRefundParams;
import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams;
import com.jiejing.fitness.finance.service.utils.FeeUtil;
import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.paycenter.common.model.vo.MerchantVO;
import com.jiejing.paycenter.common.model.vo.SettleVO;
import com.jiejing.paycenter.common.model.vo.SubChannelVO;
import com.jiejing.paycenter.api.pay.request.PayRequest;
import com.jiejing.paycenter.api.pay.request.RefundPayRequest;
......@@ -31,7 +37,9 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author chengyubing
......@@ -227,4 +235,37 @@ public class PayConvert {
.build();
}
public static List<StudioSettleRecord> convertSettle(StudioMerchantApply apply,
Map<Long, BigDecimal> studioTransAmountMap, SettleVO vo) {
String salt = AesUtil.getSalt(8);
Date now = new Date();
return studioTransAmountMap.keySet().stream().map(studioId -> StudioSettleRecord.builder()
.id(IdWorker.getId())
.studioId(studioId)
.merchantId(apply.getMerchantId())
.merchantNo(apply.getMerchantNo())
.transState(vo.getTransState())
.transAmount(studioTransAmountMap.getOrDefault(studioId, BigDecimal.ZERO))
.settleDate(vo.getTransDate())
.cardNo(AesUtil.encrypt(vo.getCardNo(), salt))
.bankName(vo.getBankName())
.salt(salt)
.createTime(now)
.updateTime(now)
.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();
}
}
package com.jiejing.fitness.finance.service.pay.impl;
import static java.util.stream.Collectors.toList;
import com.jiejing.common.exception.BizException;
import com.jiejing.common.utils.collection.CollectionUtil;
import com.jiejing.common.utils.time.TimeUtil;
import com.jiejing.fitness.enums.finance.BrandCashierTransStateEnum;
import com.jiejing.fitness.enums.finance.PartyTypeEnum;
import com.jiejing.fitness.finance.repository.entity.StudioCashierRecord;
import com.jiejing.fitness.finance.repository.entity.PartyToMerchant;
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.repository.service.StudioCashierRecordRpService;
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.enums.FinanceErrorEnums;
......@@ -20,6 +25,7 @@ import com.jiejing.fitness.finance.service.pay.params.StudioMerchantPayParams;
import com.jiejing.fitness.finance.service.rpc.MerchantRpcService;
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.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.model.vo.MerchantVO;
import com.jiejing.paycenter.api.pay.request.PayRequest;
......@@ -34,6 +40,8 @@ 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;
......@@ -73,6 +81,9 @@ public class PayServiceImpl implements PayService {
@Resource
private StudioMerchantApplyRpService studioMerchantApplyRpService;
@Resource
private StudioCheckSettleRecordRpService studioCheckSettleRecordRpService;
@Override
public PayVO merchantPay(StudioMerchantPayParams params) {
StudioVO studio = studioRpcService.getStudio(params.getStudioId());
......@@ -137,6 +148,7 @@ public class PayServiceImpl implements PayService {
public void checkSettle(Long merchantId, Date settleDate) {
Date endDate = null == settleDate ? TimeUtil.local().startOfDay(new Date()) : settleDate;
Date startDate = TimeUtil.local().plus(endDate, -15, ChronoUnit.DAYS);
AtomicInteger failCount = new AtomicInteger(0);
this.pageAndConsumer(merchantId, 200, apply -> {
StudioSettleRecord exist = studioSettleRecordRpService.getByMerchantIdAndSettleDate(
apply.getMerchantId(), endDate);
......@@ -144,21 +156,66 @@ public class PayServiceImpl implements PayService {
return;
}
SettleVO vo = payRpcService.syncSettle(apply.getMerchantId(), endDate);
// TODO 对账
switch (TransStateEnums.getByCode(vo.getTransState())) {
case SUCCESS:
// 对账
BigDecimal totalAmount = studioCashierRecordRpService.sumMerchantPaySuccess(apply.getMerchantId(),
startDate, endDate);
if (vo.getTransAmount().compareTo(totalAmount) != 0) {
if (BigDecimal.ZERO.compareTo(vo.getTransAmount()) == 0
&& BigDecimal.ZERO.compareTo(totalAmount) == 0) {
// 没有交易
return;
}
if (vo.getTransAmount().compareTo(totalAmount) == 0) {
// 对账成功
Map<Long, BigDecimal> studioTransAmountMap = studioCashierRecordRpService.sumMerchantPaySuccessGroupByStudioId(
apply.getMerchantId(), startDate, endDate);
studioSettleRecordRpService.insertAll(PayConvert.convertSettle(apply, studioTransAmountMap, vo));
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(failCount.get());
} else {
DingUtil.sendCheckSettleSuccess();
}
}
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;
case FAIL:
break;
default:
}
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);
}
......
......@@ -10,7 +10,9 @@ import com.jiejing.paycenter.api.pay.vo.RefundVO;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.enums.pay.PayStateEnums;
import com.jiejing.paycenter.common.model.vo.SettleVO;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Optional;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
......@@ -50,6 +52,7 @@ public class PayRpcService {
.settleDate(settleDate)
.build());
result.assertSuccess();
return result.getResult();
return Optional.ofNullable(result.getResult())
.orElse(SettleVO.builder().transAmount(BigDecimal.ZERO).build());
}
}
package com.jiejing.fitness.finance.service.utils;
import com.jiejing.common.dingtalk.chatbot.DingtalkChatbotClient;
import com.jiejing.common.dingtalk.chatbot.message.MarkdownMessage;
import com.jiejing.common.dingtalk.chatbot.message.Message;
import com.jiejing.common.dingtalk.chatbot.message.TextMessage;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
/**
* @author chengyubing
* @since 2024/4/7 17:13
*/
@Slf4j
public class DingUtil {
public static final String URL = "https://oapi.dingtalk.com/robot/send?access_token=1d52fb498b3d23687dec5aebf5907fb73acde3f29709fc672386d67451c9dfb7";
private static final String CHECK_SETTLE_SUCCESS = "【告警】【乐动收银自动结算记录对账】全部对账成功";
private static final String CHECK_SETTLE_FAIL = "【告警】【乐动收银自动结算记录对账】有%s家对账失败,请人工跟进";
public static void sendCheckSettleFail(Integer total) {
Message message = new TextMessage(String.format(CHECK_SETTLE_FAIL, total));
try {
DingtalkChatbotClient.send(URL, message);
} catch (IOException e) {
log.error("发送对账失败告警失败", e);
}
}
public static void sendCheckSettleSuccess() {
Message message = new TextMessage(CHECK_SETTLE_SUCCESS);
try {
DingtalkChatbotClient.send(URL, message);
} catch (IOException e) {
log.error("发送对账成功告警失败", e);
}
}
}
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