mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-09-02 18:46:29 +00:00
feat 云闪付支持对账,支持对账文件手动导入
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
- [x] 第二排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额
|
||||
- [x] 第三排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额
|
||||
- [x] 云闪付支持对账功能
|
||||
- [ ] 对账文件支持手动导入
|
||||
- [x] 对账文件支持手动导入
|
||||
- [x] 结算台DEMO增加云闪付示例
|
||||
- [x] 增加支付限额
|
||||
- [x] 各通道(异步支付)支持单独的限额
|
||||
|
@@ -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){
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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. 远程有, 本地有, 但状态不一致 记录差错表
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取通用对账对象, 将流水记录转换为对账对象
|
||||
*/
|
||||
|
@@ -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);
|
||||
|
||||
/**
|
||||
* 下载对账单到本地进行保存
|
||||
*/
|
||||
|
Reference in New Issue
Block a user