feat 云闪付支持对账,支持对账文件手动导入

This commit is contained in:
xxm1995
2024-03-25 22:48:52 +08:00
committed by 喵呀
parent 2f884136fd
commit 4b9df729f5
10 changed files with 192 additions and 23 deletions

View File

@@ -5,7 +5,7 @@
- [x] 第二排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额
- [x] 第三排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额
- [x] 云闪付支持对账功能
- [ ] 对账文件支持手动导入
- [x] 对账文件支持手动导入
- [x] 结算台DEMO增加云闪付示例
- [x] 增加支付限额
- [x] 各通道(异步支付)支持单独的限额

View File

@@ -19,6 +19,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 支付对账
@@ -49,6 +50,13 @@ public class ReconcileOrderController {
return Res.ok();
}
@Operation(summary = "手动上传对账单文件")
@PostMapping("/upload")
public ResResult<Void> upload(Long id, MultipartFile file){
reconcileService.upload(id,file);
return Res.ok();
}
@Operation(summary = "手动触发对账单比对")
@PostMapping("/compare")
public ResResult<Void> compare(Long id){

View File

@@ -55,11 +55,10 @@ public class AliPayReconcileService {
* 下载对账单, 并进行解析进行保存
*
* @param date 对账日期 yyyy-MM-dd 格式
* @param recordOrderId 对账订单ID
*/
@SneakyThrows
@Transactional(rollbackFor = Exception.class)
public void downAndSave(String date, Long recordOrderId){
public void downAndSave(String date){
try {
AlipayDataDataserviceBillDownloadurlQueryModel model = new AlipayDataDataserviceBillDownloadurlQueryModel();
@@ -93,7 +92,7 @@ public class AliPayReconcileService {
}
}
// 保存原始对账记录
this.save(billDetails, billTotals, recordOrderId);
this.save(billDetails, billTotals);
// 将原始交易明细对账记录转换通用结构并保存到上下文中
this.convertAndSave(billDetails);
@@ -103,13 +102,42 @@ public class AliPayReconcileService {
throw new RuntimeException(e);
}
}
/**
* 上传对账单解析并保存
*/
@SneakyThrows
public void upload(byte[] bytes) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes),"GBK"));
List<String> strings = IoUtil.readLines(bufferedReader, new ArrayList<>());
List<AliReconcileBillDetail> billDetails = this.parseDetail(strings);
// 保存原始对账记录
this.saveBillDetail(billDetails);
// 将原始交易明细对账记录转换通用结构并保存到上下文中
this.convertAndSave(billDetails);
}
/**
* 保存原始对账记录
*/
private void save(List<AliReconcileBillDetail> billDetails, List<AliReconcileBillTotal> billTotals, Long recordOrderId){
private void save(List<AliReconcileBillDetail> billDetails, List<AliReconcileBillTotal> billTotals){
this.saveBillDetail(billDetails);
this.saveBillTotal(billTotals);
}
/**
* 保存原始对账明细记录
*/
private void saveBillDetail(List<AliReconcileBillDetail> billDetails){
Long recordOrderId = PaymentContextLocal.get().getReconcileInfo().getReconcileOrder().getId();
billDetails.forEach(o->o.setRecordOrderId(recordOrderId));
reconcileBillDetailManager.saveAll(billDetails);
}
/**
* 保存原始对账汇总记录
*/
private void saveBillTotal(List<AliReconcileBillTotal> billTotals){
Long recordOrderId = PaymentContextLocal.get().getReconcileInfo().getReconcileOrder().getId();
billTotals.forEach(o->o.setRecordOrderId(recordOrderId));
reconcileBillTotalManager.saveAll(billTotals);
}

View File

@@ -19,6 +19,7 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.egzosn.pay.union.bean.SDKConstants;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
@@ -47,7 +48,7 @@ public class UnionPayReconcileService {
/**
* 下载对账单
*/
public void downAndSave(Date date, Long recordOrderId, UnionPayKit unionPayKit){
public void downAndSave(Date date, UnionPayKit unionPayKit){
// 下载对账单
Map<String, Object> map = unionPayKit.downloadBill(date, RECONCILE_BILL_TYPE);
String fileContent = map.get(FILE_CONTENT).toString();
@@ -81,7 +82,7 @@ public class UnionPayReconcileService {
}
}
// 保存原始对账记录
this.save(billDetails, recordOrderId);
this.save(billDetails);
// 转换为通用对账记录对象
this.convertAndSave(billDetails);
} catch (IOException e) {
@@ -89,6 +90,21 @@ public class UnionPayReconcileService {
}
}
/**
* 解析上传的对账单
*/
@SneakyThrows
public void upload(byte[] bytes){
// 明细解析
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes),"GBK"));
List<String> strings = IoUtil.readLines(bufferedReader, new ArrayList<>());
List<UnionReconcileBillDetail> billDetails = this.parseDetail(strings);
// 保存原始对账记录
this.save(billDetails);
// 转换为通用对账记录对象
this.convertAndSave(billDetails);
}
/**
* 解析对账单明细
*/
@@ -146,6 +162,7 @@ public class UnionPayReconcileService {
// 默认为支付对账记录
ReconcileDetail reconcileDetail = new ReconcileDetail()
.setTitle("未知")
.setRecordOrderId(billDetail.getRecordOrderId())
.setOrderId(billDetail.getOrderId())
.setType(ReconcileTradeEnum.PAY.getCode())
@@ -172,7 +189,8 @@ public class UnionPayReconcileService {
/**
* 保存原始对账记录
*/
private void save(List<UnionReconcileBillDetail> billDetails, Long recordOrderId){
private void save(List<UnionReconcileBillDetail> billDetails){
Long recordOrderId = PaymentContextLocal.get().getReconcileInfo().getReconcileOrder().getId();
billDetails.forEach(o->o.setRecordOrderId(recordOrderId));
unionReconcileBillDetailManager.saveAll(billDetails);
}

View File

@@ -13,8 +13,10 @@ import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileBi
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileFundFlowDetail;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.ReconcileDetail;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.ReconcileOrder;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.csv.CsvReader;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.util.StrUtil;
@@ -24,11 +26,15 @@ import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.DownloadBillModel;
import com.ijpay.wxpay.model.DownloadFundFlowModel;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@@ -56,8 +62,8 @@ public class WechatPayReconcileService{
* @param date 对账日期 yyyyMMdd 格式
*/
@Transactional(rollbackFor = Exception.class)
public void downAndSave(String date, Long recordOrderId, WeChatPayConfig config) {
this.downBill(date, recordOrderId, config);
public void downAndSave(String date, WeChatPayConfig config) {
this.downBill(date, config);
// 资金对账单先不启用
// this.downFundFlow(date, recordOrderId, config);
}
@@ -66,9 +72,8 @@ public class WechatPayReconcileService{
* 下载交易账单
*
* @param date 对账日期 yyyyMMdd 格式
* @param recordOrderId 对账订单ID
*/
public void downBill(String date, Long recordOrderId, WeChatPayConfig config){
public void downBill(String date, WeChatPayConfig config){
// 下载交易账单
Map<String, String> params = DownloadBillModel.builder()
.mch_id(config.getWxMchId())
@@ -92,6 +97,9 @@ public class WechatPayReconcileService{
List<WxReconcileBillDetail> billDetails = reader.read(billDetail, WxReconcileBillDetail.class).stream()
.filter(o->Objects.equals(o.getAppId(), config.getWxAppId()))
.collect(Collectors.toList());
Long recordOrderId = PaymentContextLocal.get().getReconcileInfo().getReconcileOrder().getId();
billDetails.forEach(o->o.setRecordOrderId(recordOrderId));
reconcileBillDetailManager.saveAll(billDetails);
@@ -103,7 +111,42 @@ public class WechatPayReconcileService{
// 将原始交易明细对账记录转换通用结构并保存到上下文中
this.convertAndSave(billDetails);
}
/**
* 上传对账单
*/
@SneakyThrows
public void uploadBill(byte[] bytes, WeChatPayConfig config){
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), "GBK"));
String result = IoUtil.read(reader);
// 过滤特殊字符
result = result.replaceAll("`", "").replaceAll("\uFEFF", "");
CsvReader csvRows = CsvUtil.getReader();
// 对账订单
ReconcileOrder reconcileOrder = PaymentContextLocal.get()
.getReconcileInfo()
.getReconcileOrder();
// 获取交易记录并保存 同时过滤出当前应用的交易记录
String billDetail = StrUtil.subBefore(result, "总交易单数", false);
List<WxReconcileBillDetail> billDetails = csvRows.read(billDetail, WxReconcileBillDetail.class).stream()
// 只读取当前商户的订单
.filter(o->Objects.equals(o.getAppId(), config.getWxAppId()))
// 只读取对账日的记录
.filter(o->{
String transactionTime = o.getTransactionTime();
LocalDateTime time = LocalDateTimeUtil.parse(transactionTime, DatePattern.NORM_DATETIME_PATTERN);
return Objects.equals(reconcileOrder.getDate(), LocalDate.from(time));
})
.collect(Collectors.toList());
billDetails.forEach(o->o.setRecordOrderId(reconcileOrder.getId()));
reconcileBillDetailManager.saveAll(billDetails);
// 将原始交易明细对账记录转换通用结构并保存到上下文中
this.convertAndSave(billDetails);
}
/**

View File

@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -85,13 +86,47 @@ public class ReconcileService {
this.downAndSave(reconcileOrder);
}
/**
* 手动传输对账单
* @param id 对账单ID
* @param file 文件
*/
public void upload(Long id, MultipartFile file) {
ReconcileOrder reconcileOrder = reconcileOrderService.findById(id)
.orElseThrow(() -> new DataNotExistException("未找到对账订单"));
// 将对账订单写入到上下文中
PaymentContextLocal.get().getReconcileInfo().setReconcileOrder(reconcileOrder);
AbsReconcileStrategy reconcileStrategy = ReconcileStrategyFactory.create(reconcileOrder.getChannel());
reconcileStrategy.setRecordOrder(reconcileOrder);
reconcileStrategy.doBeforeHandler();
try {
reconcileStrategy.upload(file);
reconcileOrder.setDown(true)
.setErrorMsg(null);
reconcileOrderService.update(reconcileOrder);
} catch (Exception e) {
log.error("上传对账单异常", e);
reconcileOrder.setErrorMsg("原因: " + e.getMessage());
reconcileOrderService.update(reconcileOrder);
throw new RuntimeException(e);
}
// 保存转换后的通用结构对账单
List<ReconcileDetail> reconcileDetails = PaymentContextLocal.get()
.getReconcileInfo()
.getReconcileDetails();
reconcileDetailManager.saveAll(reconcileDetails);
}
/**
* 下载对账单并进行保存
*/
public void downAndSave(ReconcileOrder reconcileOrder) {
// 如果对账单已经存在
if (reconcileOrder.isDown()){
throw new PayFailureException("对账单文件已经下载或上传");
}
// 将对账订单写入到上下文中
PaymentContextLocal.get().getReconcileInfo().setReconcileOrder(reconcileOrder);
// 构建对账策略
AbsReconcileStrategy reconcileStrategy = ReconcileStrategyFactory.create(reconcileOrder.getChannel());
reconcileStrategy.setRecordOrder(reconcileOrder);
@@ -131,6 +166,10 @@ public class ReconcileService {
if (!reconcileOrder.isDown()){
throw new PayFailureException("请先下载对账单");
}
// 是否对比完成
if (reconcileOrder.isCompare()){
throw new PayFailureException("对账单比对已经完成");
}
// 查询对账单
List<ReconcileDetail> reconcileDetails = reconcileDetailManager.findAllByOrderId(reconcileOrder.getId());
@@ -269,4 +308,5 @@ public class ReconcileService {
}
return diffs;
}
}

View File

@@ -14,9 +14,11 @@ import cn.bootx.platform.daxpay.service.func.AbsReconcileStrategy;
import cn.bootx.platform.daxpay.service.handler.sequence.DaxPaySequenceHandler;
import cn.hutool.core.date.DatePattern;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -81,13 +83,22 @@ public class AlipayReconcileStrategy extends AbsReconcileStrategy {
configService.initConfig(this.config);
}
/**
* 上传对账单解析并保存
*/
@SneakyThrows
@Override
public void upload(MultipartFile file) {
reconcileService.upload(file.getBytes());
}
/**
* 下载和保存对账单
*/
@Override
public void downAndSave() {
String date = LocalDateTimeUtil.format(this.getRecordOrder().getDate(), DatePattern.NORM_DATE_PATTERN);
reconcileService.downAndSave(date,this.getRecordOrder().getId());
reconcileService.downAndSave(date);
}
/**

View File

@@ -16,9 +16,11 @@ import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -74,13 +76,22 @@ public class UnionPayReconcileStrategy extends AbsReconcileStrategy {
this.unionPayKit = configService.initPayService(config);
}
/**
* 上传对账单解析并保存
*/
@SneakyThrows
@Override
public void upload(MultipartFile file) {
reconcileService.upload(file.getBytes());
}
/**
* 下载对账单到本地进行保存
*/
@Override
public void downAndSave() {
Date date = DateUtil.date(this.getRecordOrder().getDate());
reconcileService.downAndSave(date, this.getRecordOrder().getId(), this.unionPayKit);
reconcileService.downAndSave(date, this.unionPayKit);
}
/**

View File

@@ -14,9 +14,11 @@ import cn.bootx.platform.daxpay.service.func.AbsReconcileStrategy;
import cn.bootx.platform.daxpay.service.handler.sequence.DaxPaySequenceHandler;
import cn.hutool.core.date.DatePattern;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -79,22 +81,24 @@ public class WechatPayReconcileStrategy extends AbsReconcileStrategy {
this.config = weChatPayConfigService.getConfig();
}
/**
* 上传对账单解析并保存
*/
@SneakyThrows
@Override
public void upload(MultipartFile file) {
reconcileService.uploadBill(file.getBytes(),this.config);
}
/**
* 下载对账单
*/
@Override
public void downAndSave() {
String format = LocalDateTimeUtil.format(this.getRecordOrder().getDate(), DatePattern.PURE_DATE_PATTERN);
reconcileService.downAndSave(format,this.getRecordOrder().getId(), this.config);
reconcileService.downAndSave(format, this.config);
}
/**
* 比对生成对账差异单
* 1. 远程有, 本地无 补单(追加回订单/记录差异表)
* 2. 远程无, 本地有 记录差错表
* 3. 远程有, 本地有, 但状态不一致 记录差错表
*/
/**
* 获取通用对账对象, 将流水记录转换为对账对象
*/

View File

@@ -5,6 +5,7 @@ import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.ReconcileOrd
import cn.bootx.platform.daxpay.service.core.payment.reconcile.domain.GeneralReconcileRecord;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.util.List;
@@ -36,6 +37,11 @@ public abstract class AbsReconcileStrategy implements PayStrategy {
public void doBeforeHandler() {
}
/**
* 上传对账单解析并保存
*/
public abstract void upload(MultipartFile file);
/**
* 下载对账单到本地进行保存
*/