mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-09-02 10:36:57 +00:00
feat 云闪付对账处理
This commit is contained in:
@@ -4,13 +4,9 @@
|
||||
- [x] 第一排: (数字格式)显示今日收入、支出金额,支付总订单数量、退款总订单数, 时间分支分为: 今日金额/昨日金额/七天内金额
|
||||
- [x] 第二排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额
|
||||
- [x] 第三排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额
|
||||
- [ ] 报表功能
|
||||
- [ ] 各通道收入和支付情况
|
||||
- [ ] 增加转账功能
|
||||
- [ ] 支付宝
|
||||
- [ ] 微信
|
||||
- [ ] 云闪付
|
||||
- [ ] 增加账户余额查询功能
|
||||
- [ ] 云闪付支持对账功能
|
||||
- [x] 结算台DEMO增加云闪付示例
|
||||
- [x] 增加支付限额
|
||||
@@ -19,6 +15,7 @@
|
||||
2.0.x 版本内容
|
||||
- [ ] 微信新增V3版本接口
|
||||
- [ ] 付款码支付自动路由到V2接口
|
||||
- [ ] 资金流水优化
|
||||
- [ ] 统一关闭接口增加使用撤销关闭订单
|
||||
- [ ] 支持分账
|
||||
- [ ] 增加各类日志记录,例如钱包的各项操作
|
||||
|
@@ -53,9 +53,19 @@ public interface UnionPayCode {
|
||||
/** 总金额 */
|
||||
String TOTAL_FEE = "settleAmt";
|
||||
|
||||
|
||||
|
||||
/** 对账单下载类型编码 */
|
||||
String RECONCILE_BILL_TYPE = "00";
|
||||
|
||||
/* 对账单交易代码 */
|
||||
/** 消费 */
|
||||
String RECONCILE_TYPE_PAY = "S22";
|
||||
|
||||
/** 退款 */
|
||||
String RECONCILE_TYPE_REFUND = "S30 ";
|
||||
|
||||
|
||||
/** 对账各字段位数游标 */
|
||||
int[] RECONCILE_BILL_SPLIT = {3,11,11,6,10,19,12,4,2,21,2,32,2,6,10,13,13,4,15,2,2,6,2,4,32,1,21,15,1,15,32,13,13,8,32,13,13,12,2,1,32,13,2,1,12,67};
|
||||
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,44 @@
|
||||
package cn.bootx.platform.daxpay.service.code;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 云闪付对账单字段
|
||||
* @author xxm
|
||||
* @since 2024/3/24
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum UnionReconcileFieldEnum {
|
||||
|
||||
/** 交易代码 */
|
||||
TRADE_TYPE(0, "tradeType"),
|
||||
/** 交易传输时间 MMDDhhmmss */
|
||||
txnTime(4, "txnTime"),
|
||||
/** 交易金额 */
|
||||
TXN_AMT(6, "txnAmt"),
|
||||
/** 查询流水号 */
|
||||
QUERY_ID(9, "queryId"),
|
||||
/** 商户订单号 */
|
||||
ORDER_ID(11, "orderId");
|
||||
|
||||
|
||||
|
||||
/** 序号 */
|
||||
private final int no;
|
||||
/** 字段名 */
|
||||
private final String filed;
|
||||
|
||||
/**
|
||||
* 根据序号查询
|
||||
*/
|
||||
public static UnionReconcileFieldEnum findByNo(int no){
|
||||
return Arrays.stream(values())
|
||||
.filter(o->o.no == no)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package cn.bootx.platform.daxpay.service.core.channel.union.entity;
|
||||
|
||||
import cn.bootx.table.modify.annotation.DbColumn;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 云闪付业务明细对账单
|
||||
* @author xxm
|
||||
* @since 2024/3/24
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class UnionReconcileBillDetail {
|
||||
|
||||
/** 关联对账订单ID */
|
||||
@DbColumn(comment = "关联对账订单ID")
|
||||
private Long recordOrderId;
|
||||
/** 交易代码 */
|
||||
private String tradeType;
|
||||
/** 代理机构标识码 */
|
||||
/** 发送机构标识码 */
|
||||
/** 系统跟踪号 */
|
||||
/** 交易传输时间 */
|
||||
private String txnTime;
|
||||
/** 帐号 */
|
||||
/** 交易金额 */
|
||||
private String txnAmt;
|
||||
/** 商户类别 */
|
||||
/** 终端类型 */
|
||||
/** 查询流水号 */
|
||||
private String queryId;
|
||||
/** 支付方式(旧) */
|
||||
/** 商户订单号 */
|
||||
private String orderId;
|
||||
/** 支付卡类型 */
|
||||
/** 原始交易的系统跟踪号 */
|
||||
/** 原始交易日期时间 */
|
||||
/** 商户手续费 */
|
||||
/** 结算金额 */
|
||||
/** 支付方式 */
|
||||
/** 集团商户代码 */
|
||||
/** 交易类型 */
|
||||
/** 交易子类 */
|
||||
/** 业务类型 */
|
||||
/** 帐号类型 */
|
||||
/** 账单类型 */
|
||||
/** 账单号码 */
|
||||
/** 交互方式 */
|
||||
/** 商户代码 */
|
||||
/** 分账入账方式 */
|
||||
/** 二级商户代码 */
|
||||
/** 二级商户简称 */
|
||||
/** 二级商户分账入账金额 */
|
||||
/** 清算净额 */
|
||||
/** 终端号 */
|
||||
/** 商户自定义域 */
|
||||
/** 优惠金额 */
|
||||
/** 发票金额 */
|
||||
/** 分期付款附加手续费 */
|
||||
/** 分期付款期数 */
|
||||
/** 交易介质 */
|
||||
/** 原始交易订单号 */
|
||||
/** 清算金额 */
|
||||
/** 服务点输入方式码 */
|
||||
/** 移动支付产品标志 */
|
||||
/** 交易代码 */
|
||||
/** 检索参考号 */
|
||||
/** 保留使用 */
|
||||
}
|
@@ -1,13 +1,31 @@
|
||||
package cn.bootx.platform.daxpay.service.core.channel.union.service;
|
||||
|
||||
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
||||
import cn.bootx.platform.daxpay.code.ReconcileTradeEnum;
|
||||
import cn.bootx.platform.daxpay.service.code.UnionPayCode;
|
||||
import cn.bootx.platform.daxpay.service.code.UnionReconcileFieldEnum;
|
||||
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.entity.UnionReconcileBillDetail;
|
||||
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.ReconcileDetail;
|
||||
import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.compress.Deflate;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.io.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.bootx.platform.daxpay.service.code.UnionPayCode.RECONCILE_BILL_SPLIT;
|
||||
import static cn.bootx.platform.daxpay.service.code.UnionPayCode.RECONCILE_BILL_TYPE;
|
||||
|
||||
/**
|
||||
@@ -26,12 +44,127 @@ public class UnionPayReconcileService {
|
||||
public void downAndSave(Date date, Long recordOrderId, UnionPayKit unionPayKit){
|
||||
// 下载对账单
|
||||
Map<String, Object> stringObjectMap = unionPayKit.downloadBill(date, RECONCILE_BILL_TYPE);
|
||||
|
||||
String fileContent = stringObjectMap.get("fileContent").toString();
|
||||
try {
|
||||
|
||||
// 先解base64,再DEFLATE解压为zip流
|
||||
byte[] decode = Base64.decode(fileContent);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Deflate deflate = Deflate.of(new ByteArrayInputStream(decode), out, false);
|
||||
deflate.inflater();
|
||||
deflate.close();
|
||||
|
||||
// 读取zip文件, 解析出对账单内容
|
||||
byte[] zipBytes = out.toByteArray();
|
||||
ZipArchiveInputStream zipArchiveInputStream = new ZipArchiveInputStream(new ByteArrayInputStream(zipBytes),"GBK");
|
||||
ZipArchiveEntry entry;
|
||||
List<UnionReconcileBillDetail> billDetails = new ArrayList<>();
|
||||
while ((entry= zipArchiveInputStream.getNextZipEntry()) != null){
|
||||
System.out.println(StrUtil.startWith(entry.getName(), "INN"));
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(zipArchiveInputStream,"GBK"));
|
||||
if (StrUtil.startWith(entry.getName(), "INN")){
|
||||
// 明细解析
|
||||
List<String> strings = IoUtil.readLines(bufferedReader, new ArrayList<>());
|
||||
billDetails = this.parseDetail(strings);
|
||||
} else {
|
||||
// 汇总目前不进行处理
|
||||
}
|
||||
}
|
||||
// 保存原始对账记录
|
||||
this.save(billDetails, recordOrderId);
|
||||
|
||||
// 将原始交易明细对账记录转换通用结构并保存到上下文中
|
||||
this.convertAndSave(billDetails);
|
||||
// Reader bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(Paths.get("D:/data/INN24031100ZM_777290058206553.txt"))));
|
||||
// List<String> strings = IoUtil.readLines(bufferedReader, new ArrayList<>());
|
||||
// List<UnionReconcileBillDetail> unionReconcileBillDetails = this.parseDetail(strings);
|
||||
// System.out.println(unionReconcileBillDetails);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换和保存
|
||||
* 解析对账单明细
|
||||
*/
|
||||
public void convertAndSave(){
|
||||
|
||||
private List<UnionReconcileBillDetail> parseDetail(List<String> list){
|
||||
return list.stream()
|
||||
.map(this::convertDetail)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析明细条目
|
||||
*/
|
||||
private UnionReconcileBillDetail convertDetail(String line){
|
||||
//解析的结果MAP,key为对账文件列序号,value为解析的值
|
||||
Map<String,String> zmDataMap = new HashMap<>();
|
||||
//左侧游标
|
||||
int leftIndex = 0;
|
||||
//右侧游标
|
||||
int rightIndex = 0;
|
||||
for(int i=0;i<RECONCILE_BILL_SPLIT.length;i++){
|
||||
rightIndex = leftIndex + RECONCILE_BILL_SPLIT[i];
|
||||
String filed = StrUtil.sub(line, leftIndex, rightIndex);
|
||||
leftIndex = rightIndex+1;
|
||||
|
||||
UnionReconcileFieldEnum fieldEnum = UnionReconcileFieldEnum.findByNo(i);
|
||||
if (Objects.nonNull(fieldEnum)){
|
||||
zmDataMap.put(fieldEnum.getFiled(), filed.trim());
|
||||
}
|
||||
}
|
||||
return BeanUtil.toBean(zmDataMap, UnionReconcileBillDetail.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为通用对账记录对象
|
||||
*/
|
||||
private void convertAndSave(List<UnionReconcileBillDetail> billDetails){
|
||||
List<ReconcileDetail> collect = billDetails.stream()
|
||||
.map(this::convert)
|
||||
.collect(Collectors.toList());
|
||||
// 写入到上下文中
|
||||
PaymentContextLocal.get().getReconcileInfo().setReconcileDetails(collect);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 转换为通用对账记录对象
|
||||
*/
|
||||
private ReconcileDetail convert(UnionReconcileBillDetail billDetail){
|
||||
// 金额
|
||||
String orderAmount = billDetail.getTxnAmt();
|
||||
int amount = Integer.parseInt(orderAmount);
|
||||
|
||||
// 默认为支付对账记录
|
||||
ReconcileDetail reconcileDetail = new ReconcileDetail()
|
||||
.setRecordOrderId(billDetail.getRecordOrderId())
|
||||
.setOrderId(billDetail.getOrderId())
|
||||
.setType(ReconcileTradeEnum.PAY.getCode())
|
||||
.setAmount(amount)
|
||||
.setGatewayOrderNo(billDetail.getQueryId());
|
||||
|
||||
// 时间
|
||||
String txnTime = billDetail.getTxnTime();
|
||||
if (StrUtil.isNotBlank(txnTime)) {
|
||||
LocalDateTime time = LocalDateTimeUtil.parse(txnTime, DatePattern.NORM_DATETIME_PATTERN);
|
||||
reconcileDetail.setOrderTime(time);
|
||||
}
|
||||
|
||||
// 退款覆盖更新对应的字段
|
||||
if (Objects.equals(billDetail.getTradeType(), UnionPayCode.RECONCILE_TYPE_REFUND)){
|
||||
reconcileDetail.setType(ReconcileTradeEnum.REFUND.getCode());
|
||||
}
|
||||
return reconcileDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存原始对账记录
|
||||
*/
|
||||
private void save(List<UnionReconcileBillDetail> billDetails, Long recordOrderId){
|
||||
billDetails.forEach(o->o.setRecordOrderId(recordOrderId));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.reconcile.strategy;
|
||||
|
||||
import cn.bootx.platform.common.core.exception.BizException;
|
||||
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
||||
import cn.bootx.platform.common.sequence.func.Sequence;
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.convert.UnionPayConvert;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.dao.UnionPayRecordManager;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.entity.UnionPayConfig;
|
||||
@@ -68,6 +68,10 @@ public class UnionPayReconcileStrategy extends AbsReconcileStrategy {
|
||||
@Override
|
||||
public void doBeforeHandler() {
|
||||
UnionPayConfig config = configService.getConfig();
|
||||
// 测试环境使用测试号
|
||||
if (config.isSandbox()) {
|
||||
config.setMachId("700000000000001");
|
||||
}
|
||||
this.unionPayKit = configService.initPayService(config);
|
||||
}
|
||||
|
||||
@@ -76,12 +80,9 @@ public class UnionPayReconcileStrategy extends AbsReconcileStrategy {
|
||||
*/
|
||||
@Override
|
||||
public void downAndSave() {
|
||||
if (true){
|
||||
throw new PayFailureException("功能暂时未实现");
|
||||
}
|
||||
|
||||
Date date = DateUtil.date(this.getRecordOrder().getDate());
|
||||
reconcileService.downAndSave(date, this.getRecordOrder().getId(), this.unionPayKit);
|
||||
throw new BizException("123");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -720,15 +720,13 @@ public class UnionPayKit extends UnionPayService {
|
||||
this.setSign(params);
|
||||
String responseStr = getHttpRequestTemplate().postForObject(this.getFileTransUrl(), params, String.class);
|
||||
JSONObject response = UriVariables.getParametersToMap(responseStr);
|
||||
if (this.verify(response)) {
|
||||
if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
|
||||
// if (this.verify(response)) {
|
||||
// if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
|
||||
return response;
|
||||
|
||||
}
|
||||
throw new PayErrorException(new PayException(response.get(SDKConstants.param_respCode).toString(), response.get(SDKConstants.param_respMsg).toString(), response.toString()));
|
||||
|
||||
}
|
||||
throw new PayErrorException(new PayException("failure", "验证签名失败", response.toString()));
|
||||
// }
|
||||
// throw new PayErrorException(new PayException(response.get(SDKConstants.param_respCode).toString(), response.get(SDKConstants.param_respMsg).toString(), response.toString()));
|
||||
// }
|
||||
// throw new PayErrorException(new PayException("failure", "验证签名失败", response.toString()));
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user