feat 支付对账文件解析

This commit is contained in:
xxm1995
2024-01-18 16:53:32 +08:00
parent 51dc719007
commit 7db30f5ccd
26 changed files with 456 additions and 152 deletions

View File

@@ -2,8 +2,8 @@ package cn.bootx.platform.daxpay.admin.controller.system;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.daxpay.service.core.system.payinfo.service.PayChannelInfoService;
import cn.bootx.platform.daxpay.service.dto.system.payinfo.PayChannelInfoDto;
import cn.bootx.platform.daxpay.service.core.system.config.service.PayChannelConfigService;
import cn.bootx.platform.daxpay.service.dto.system.config.PayChannelConfigDto;
import cn.bootx.platform.daxpay.service.param.system.payinfo.PayChannelInfoParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -19,28 +19,28 @@ import java.util.List;
*/
@Tag(name = "支付通道信息")
@RestController
@RequestMapping("/pay/channel/info")
@RequestMapping("/pay/channel/config")
@RequiredArgsConstructor
public class PayChannelInfoController {
private final PayChannelInfoService payChannelInfoService;
public class PayChannelConfigController {
private final PayChannelConfigService payChannelConfigService;
@Operation(summary = "查询全部")
@GetMapping("/findAll")
public ResResult<List<PayChannelInfoDto>> findAll(){
List<PayChannelInfoDto> all = payChannelInfoService.findAll();
public ResResult<List<PayChannelConfigDto>> findAll(){
List<PayChannelConfigDto> all = payChannelConfigService.findAll();
return Res.ok(all);
}
@Operation(summary = "根据ID获取")
@GetMapping("/findById")
public ResResult<PayChannelInfoDto> findById(Long id){
return Res.ok(payChannelInfoService.findById(id));
public ResResult<PayChannelConfigDto> findById(Long id){
return Res.ok(payChannelConfigService.findById(id));
}
@Operation(summary = "更新")
@PostMapping("/update")
public ResResult<Void> update(@RequestBody PayChannelInfoParam param){
payChannelInfoService.update(param);
payChannelConfigService.update(param);
return Res.ok();
}
}

View File

@@ -19,12 +19,13 @@ import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 包括支付成功/退款/转账等一系列类型的回调处理
* @author xxm
* @since 2021/2/27
*/
@IgnoreAuth
@Slf4j
@Tag(name = "支付回调")
@Tag(name = "支付通道信息回调")
@RestController
@RequestMapping("/pay/callback")
@AllArgsConstructor
@@ -35,21 +36,20 @@ public class PayCallbackController {
private final WeChatPayCallbackService weChatPayCallbackService;
@SneakyThrows
@Operation(summary = "支付宝回调")
@Operation(summary = "支付宝信息回调")
@PostMapping("/alipay")
public String aliPay(HttpServletRequest request) {
public String aliPayNotify(HttpServletRequest request) {
Map<String, String> stringStringMap = AliPayApi.toMap(request);
return aliPayCallbackService.payCallback(stringStringMap);
}
@SneakyThrows
@Operation(summary = "微信支付回调")
@Operation(summary = "微信支付信息回调")
@PostMapping("/wechat")
public String wechat(HttpServletRequest request) {
public String wechatPayNotify(HttpServletRequest request) {
String xmlMsg = HttpKit.readData(request);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
return weChatPayCallbackService.payCallback(params);
}
}

View File

@@ -73,7 +73,8 @@ public class TestController {
@Operation(summary = "下载支付宝对账单")
@GetMapping("/aliDownReconcile")
public ResResult<String> aliDownReconcile(String date){
return Res.ok(alipayReconcileService.downAndSave(date));
alipayReconcileService.downAndSave(date);
return Res.ok();
}
@Operation(summary = "下载微信对账单")
@GetMapping("/wxDownReconcile")

View File

@@ -36,6 +36,9 @@ public interface WeChatPayCode {
/** 返回错误代码(例如付款码返回的支付中状态就在这里面) */
String ERR_CODE = "err_code";
/** 返回错误代码(如对账错误) */
String ERROR_CODE = "error_code";
/** 返回错误信息 */
String ERR_CODE_DES = "err_code_des";
@@ -97,7 +100,7 @@ public interface WeChatPayCode {
/** 订单总退款次数 */
String TOTAL_REFUND_COUNT = "total_refund_count";
/** */
String account_type_basic = "Basic";
/** 资金账单 - 基本账户 */
String ACCOUNT_TYPE_BASIC = "Basic";
}

View File

@@ -1,12 +0,0 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.domain;
import lombok.Data;
/**
* 支付宝对账单
* @author xxm
* @since 2024/1/17
*/
@Data
public class AliPayReconcileBill {
}

View File

@@ -0,0 +1,61 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.domain;
import cn.hutool.core.annotation.Alias;
import lombok.Data;
/**
* 支付宝业务明细对账单
* @author xxm
* @since 2024/1/18
*/
@Data
public class AliReconcileBillDetail {
@Alias("支付宝交易号")
private String tradeNo;
@Alias("商户订单号")
private String outTradeNo;
@Alias("业务类型")
private String tradeType;
@Alias("商品名称")
private String subject;
@Alias("完成时间")
private String endTime;
@Alias("门店编号")
private String storeId;
@Alias("门店名称")
private String storeName;
@Alias("操作员")
private String operator;
@Alias("终端号")
private String terminalId;
@Alias("对方账户")
private String otherAccount;
@Alias("订单金额(元)")
private String orderAmount;
@Alias("商家实收(元)")
private String realAmount;
@Alias("支付宝红包(元)")
private String alipayAmount;
@Alias("集分宝(元)")
private String jfbAmount;
@Alias("支付宝优惠(元)")
private String alipayDiscountAmount;
@Alias("商家优惠(元)")
private String discountAmount;
@Alias("券核销金额(元)")
private String couponDiscountAmount;
@Alias("券名称")
private String couponName;
@Alias("商家红包消费金额(元)")
private String couponAmount;
@Alias("卡消费金额(元)")
private String cardAmount;
@Alias("退款批次号/请求号")
private String batchNo;
@Alias("服务费(元)")
private String serviceAmount;
@Alias("分润(元)")
private String splitAmount;
@Alias("备注")
private String remark;
}

View File

@@ -0,0 +1,38 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.domain;
import cn.hutool.core.annotation.Alias;
import lombok.Data;
/**
* 支付宝业务汇总对账单
* @author xxm
* @since 2024/1/17
*/
@Data
public class AliReconcileBillTotal {
@Alias("门店编号")
private String storeId;
@Alias("门店名称")
private String storeName;
@Alias("交易订单总笔数")
private String totalNum;
@Alias("退款订单总笔数")
private String totalRefundNum;
@Alias("订单金额(元)")
private String totalOrderAmount;
@Alias("商家实收(元)")
private String totalAmount;
@Alias("支付宝优惠(元)")
private String totalDiscountAmount;
@Alias("商家优惠(元)")
private String totalCouponAmount;
// 卡消费金额(元) 服务费(元) 分润(元) 实收净额(元)
@Alias("卡消费金额(元)")
private String totalConsumeAmount;
@Alias("服务费(元)")
private String totalServiceAmount;
@Alias("分润(元)")
private String totalShareAmount;
@Alias("实收净额(元)")
private String totalNetAmount;
}

View File

@@ -8,7 +8,7 @@ import cn.bootx.platform.daxpay.service.code.AliPayCode;
import cn.bootx.platform.daxpay.service.code.AliPayWay;
import cn.bootx.platform.daxpay.service.core.channel.alipay.dao.AliPayConfigManager;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.core.system.payinfo.service.PayChannelInfoService;
import cn.bootx.platform.daxpay.service.core.system.config.service.PayChannelConfigService;
import cn.bootx.platform.daxpay.service.param.channel.alipay.AliPayConfigParam;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
@@ -38,7 +38,7 @@ public class AliPayConfigService {
/** 默认支付宝配置的主键ID */
private final static Long ID = 0L;
private final AliPayConfigManager alipayConfigManager;
private final PayChannelInfoService payChannelInfoService;
private final PayChannelConfigService payChannelConfigService;
/**
* 修改
@@ -48,7 +48,7 @@ public class AliPayConfigService {
AliPayConfig alipayConfig = alipayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在"));
// 启用或停用
if (!Objects.equals(param.getEnable(), alipayConfig.getEnable())){
payChannelInfoService.setEnable(PayChannelEnum.ALI.getCode(), param.getEnable());
payChannelConfigService.setEnable(PayChannelEnum.ALI.getCode(), param.getEnable());
}
BeanUtil.copyProperties(param, alipayConfig, CopyOptions.create().ignoreNullValue());

View File

@@ -1,6 +1,14 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.service;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.code.AliPayCode;
import cn.bootx.platform.daxpay.service.core.channel.alipay.domain.AliReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.alipay.domain.AliReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
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;
import cn.hutool.http.HttpUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayDataDataserviceBillDownloadurlQueryModel;
@@ -8,6 +16,7 @@ import com.ijpay.alipay.AliPayApi;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.springframework.stereotype.Service;
@@ -17,6 +26,8 @@ import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 支付宝对账服务
@@ -27,36 +38,80 @@ import java.util.List;
@Service
@RequiredArgsConstructor
public class AlipayReconcileService {
private final AliPayConfigService configService;
/**
* 下载对账单, 并进行解析进行保存
* @param date 对账日期 yyyy-MM-dd 格式
*/
@SneakyThrows
public String downAndSave(String date){
public void downAndSave(String date){
AliPayConfig config = configService.getConfig();
configService.initConfig(config);
try {
AlipayDataDataserviceBillDownloadurlQueryModel model = new AlipayDataDataserviceBillDownloadurlQueryModel();
model.setBillDate(date);
model.setBillType("trade");
val response = AliPayApi.billDownloadUrlQueryToResponse(model);
// 判断返回结果
if (!Objects.equals(AliPayCode.SUCCESS, response.getCode())) {
log.error("获取支付宝对账单失败: {}", response.getSubMsg());
throw new PayFailureException(response.getSubMsg());
}
// 获取对账单下载地址并下载
String url = AliPayApi.billDownloadUrlQuery(model);
String url = response.getBillDownloadUrl();
byte[] bytes = HttpUtil.downloadBytes(url);
// 使用 Apache commons-compress 包装流,
ZipArchiveInputStream zipArchiveInputStream = new ZipArchiveInputStream(new ByteArrayInputStream(bytes),"GBK");
ZipArchiveEntry entry;
List<AliReconcileBillDetail> billDetails;
List<AliReconcileBillTotal> billTotals;
while ((entry= zipArchiveInputStream.getNextZipEntry()) != null){
String name = entry.getName();
System.out.println(name);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(zipArchiveInputStream,"GBK"));
List<String> strings = IoUtil.readLines(bufferedReader, new ArrayList<>());
strings.forEach(System.out::println);
String name = entry.getName();
if (StrUtil.endWith(name,"_业务明细(汇总).csv")){
billTotals = this.parseTotal(strings);
} else {
billDetails = this.parseDetail(strings);
}
}
return url;
// 保存
System.out.println();
} catch (AlipayApiException e) {
log.error("下载对账单失败",e);
throw new RuntimeException(e);
}
}
/**
* 解析明细
*/
public List<AliReconcileBillDetail> parseDetail(List<String> list){
// 去除前 4 行和后 2 行 然后合并是个一个字符串
String billDetail = list.subList(4, list.size() - 2)
.stream()
.collect(Collectors.joining(System.lineSeparator()));
billDetail = billDetail.replaceAll("\t", "");
CsvReader reader = CsvUtil.getReader();
List<AliReconcileBillDetail> billDetails = reader.read(billDetail, AliReconcileBillDetail.class);
return billDetails;
}
/**
* 解析汇总
*/
public List<AliReconcileBillTotal> parseTotal(List<String> list){
// 去除前 4 行和后 2 行 然后合并是个一个字符串
String billTotal = list.subList(4, list.size() - 2)
.stream()
.collect(Collectors.joining(System.lineSeparator()));
billTotal = billTotal.replaceAll("\t", "");
CsvReader reader = CsvUtil.getReader();
List<AliReconcileBillTotal> billTotals = reader.read(billTotal, AliReconcileBillTotal.class);
return billTotals;
}
}

View File

@@ -9,7 +9,7 @@ import lombok.Data;
* @since 2024/1/17
*/
@Data
public class WechatPayReconcileBillDetail {
public class WxReconcileBillDetail {
@Alias("交易时间")
private String transactionTime;
@@ -95,6 +95,4 @@ public class WechatPayReconcileBillDetail {
@Alias("费率备注")
private String packet8;
}

View File

@@ -9,7 +9,7 @@ import lombok.Data;
* @since 2024/1/17
*/
@Data
public class WechatPayReconcileBillTotal {
public class WxReconcileBillTotal {
@Alias("总交易单数")
private String totalNum;

View File

@@ -0,0 +1,35 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.domain;
import cn.hutool.core.annotation.Alias;
import lombok.Data;
/**
* 微信资金账单明细
* @author xxm
* @since 2024/1/18
*/
@Data
public class WxReconcileFundFlowDetail {
@Alias("记账时间")
private String time;
@Alias("微信支付业务单号")
private String outTradeNo;
@Alias("资金流水单号")
private String id;
@Alias("业务名称")
private String packet1;
@Alias("业务类型")
private String packet2;
@Alias("收支类型")
private String packet3;
@Alias("收支金额(元)")
private String amount;
@Alias("账户结余(元)")
private String balance;
@Alias("资金变更提交申请人")
private String packet4;
@Alias("备注")
private String packet5;
@Alias("业务凭证号")
private String packet6;
}

View File

@@ -0,0 +1,27 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.domain;
import cn.hutool.core.annotation.Alias;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 微信资金账单汇总
* @author xxm
* @since 2024/1/18
*/
@Data
@Accessors(chain = true)
public class WxReconcileFundFlowTotal {
// 资金流水总笔数,收入笔数,收入金额,支出笔数,支出金额
@Alias("资金流水总笔数")
private String totalNum;
@Alias("收入笔数")
private String incomeNum;
@Alias("收入金额")
private String incomeAmount;
@Alias("支出笔数")
private String expenditureNum;
@Alias("支出金额")
private String expenditureAmount;
}

View File

@@ -7,7 +7,7 @@ import cn.bootx.platform.daxpay.service.code.WeChatPayWay;
import cn.bootx.platform.daxpay.service.core.channel.wechat.dao.WeChatPayConfigManager;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.core.system.payinfo.service.PayChannelInfoService;
import cn.bootx.platform.daxpay.service.core.system.config.service.PayChannelConfigService;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayConfigParam;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
@@ -33,7 +33,7 @@ public class WeChatPayConfigService {
/** 默认微信支付配置的主键ID */
private final static Long ID = 0L;
private final WeChatPayConfigManager weChatPayConfigManager;
private final PayChannelInfoService payChannelInfoService;
private final PayChannelConfigService payChannelConfigService;
/**
* 修改
@@ -43,7 +43,7 @@ public class WeChatPayConfigService {
WeChatPayConfig weChatPayConfig = weChatPayConfigManager.findById(ID).orElseThrow(() -> new PayFailureException("微信支付配置不存在"));
// 启用或停用
if (!Objects.equals(param.getEnable(), weChatPayConfig.getEnable())){
payChannelInfoService.setEnable(PayChannelEnum.WECHAT.getCode(), param.getEnable());
payChannelConfigService.setEnable(PayChannelEnum.WECHAT.getCode(), param.getEnable());
}
BeanUtil.copyProperties(param, weChatPayConfig, CopyOptions.create().ignoreNullValue());
weChatPayConfigManager.updateById(weChatPayConfig);

View File

@@ -41,7 +41,6 @@ public class WeChatPayOrderService {
* 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建微信支付单
*/
public void updatePaySuccess(PayOrder payOrder, PayChannelParam payChannelParam) {
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();;
payOrder.setAsyncPay(true).setAsyncChannel(PayChannelEnum.WECHAT.getCode());
payOrderChannelService.updateChannel(payChannelParam,payOrder);

View File

@@ -1,7 +1,10 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.service;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WechatPayReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WechatPayReconcileBillTotal;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WxReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WxReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WxReconcileFundFlowDetail;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.text.csv.CsvReader;
@@ -19,6 +22,9 @@ import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.bootx.platform.daxpay.service.code.WeChatPayCode.ACCOUNT_TYPE_BASIC;
/**
* 微信支付对账
@@ -33,7 +39,7 @@ public class WechatPayReconcileService {
/**
* 下载对账单并保存
* @param date 下载日期
* @param date 对账日期 yyyyMMdd 格式
*/
public void downAndSave(String date, WeChatPayConfig config) {
config = weChatPayConfigService.getConfig();
@@ -43,6 +49,7 @@ public class WechatPayReconcileService {
/**
* 下载交易账单
* @param date 对账日期 yyyyMMdd 格式
*/
public void downBill(String date, WeChatPayConfig config){
// 下载交易账单
@@ -54,18 +61,32 @@ public class WechatPayReconcileService {
.bill_type("ALL")
.build()
.createSign(config.getApiKeyV2(), SignType.HMACSHA256);
String res = WxPayApi.downloadBill(config.isSandbox(), params);
String result = WxPayApi.downloadBill(config.isSandbox(), params);
// 验证返回数据是否成功
this.verifyBillMsg(result);
// 过滤特殊字符
result = result.replaceAll("`", "").replaceAll("\uFEFF", "");
CsvReader reader = CsvUtil.getReader();
// 获取交易记录
String billDetail = StrUtil.subBefore(result, "总交易单数", false);
List<WxReconcileBillDetail> billDetails = reader.read(billDetail, WxReconcileBillDetail.class);
// 获取汇总数据
System.out.println(res);
CsvReader reader = CsvUtil.getReader();
List<WechatPayReconcileBillDetail> read = reader.read(res, WechatPayReconcileBillDetail.class);
String billTotal = result.substring(result.indexOf("总交易单数"));
List<WxReconcileBillTotal> billTotals = reader.read(billTotal, WxReconcileBillTotal.class);
}
/**
* 下载资金账单, 需要双向证书
* 下载资金账单, 需要双向证书,
* 如果出现 No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
* 更换一下最新的JDK即可, 例如 corretto Jdk
*
* @param date 对账日期 yyyyMMdd 格式
*/
public void downFundFlow(String date, WeChatPayConfig config){
if (StrUtil.isBlank(config.getP12())){
@@ -76,32 +97,83 @@ public class WechatPayReconcileService {
.appid(config.getWxAppId())
.nonce_str(WxPayKit.generateStr())
.bill_date(date)
.account_type("Basic")
.account_type(ACCOUNT_TYPE_BASIC)
.build()
.createSign(config.getApiKeyV2(), SignType.HMACSHA256);
byte[] fileBytes = Base64.decode(config.getP12());
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes);
// 证书密码为 微信商户号
String s = WxPayApi.downloadFundFlow(fundFlow, inputStream, config.getWxMchId());
// String url = WxPayApi.getReqUrl(PayApiEnum.DOWNLOAD_BILL, null, config.isSandbox());
// String post = HttpUtil.post(url, WxPayKit.toXml(fundFlow));
System.out.println(s);
}
String result = WxPayApi.downloadFundFlow(fundFlow, inputStream, config.getWxMchId());
// 验证返回数据是否成功
this.verifyFundFlowMsg(result);
public static void main(String[] args){
String s = "\uFEFF交易时间,公众账号ID,商户号,特约商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,应结订单金额,代金券金额,微信退款单号,商户退款单号,退款金额,充值券退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率,订单金额,申请退款金额,费率备注\n" +
"`2024-01-16 22:43:59,`wx9dfe38480f030e85,`1627859777,`0,`,`4200002086202401160745964084,`1747268229775183872,`o2RW45oGxo1Z8Fhcf6Oy9rMM7aT8,`NATIVE,`SUCCESS,`OTHERS,`CNY,`0.02,`0.00,`0,`0,`0.00,`0.00,`,`,`测试支付4,`,`0.00000,`0.90%,`0.02,`0.00,`\n" +
"`2024-01-16 22:45:16,`wx9dfe38480f030e85,`1627859777,`0,`,`4200002086202401160745964084,`1747268229775183872,`o2RW45oGxo1Z8Fhcf6Oy9rMM7aT8,`NATIVE,`REFUND,`OTHERS,`CNY,`0.00,`0.00,`50303108562024011601487282563,`R1747268742516264960,`0.01,`0.00,`ORIGINAL,`SUCCESS,`测试支付4,`,`0.00000,`0.90%,`0.00,`0.01,`\n" +
"`2024-01-16 23:10:18,`wx9dfe38480f030e85,`1627859777,`0,`,`4200002086202401160745964084,`1747268229775183872,`o2RW45oGxo1Z8Fhcf6Oy9rMM7aT8,`NATIVE,`REFUND,`OTHERS,`CNY,`0.00,`0.00,`50303108562024011683774350275,`R1747275041228390400,`0.01,`0.00,`ORIGINAL,`SUCCESS,`测试支付4,`,`0.00000,`0.90%,`0.00,`0.01,`\n" +
"总交易单数,应结订单总金额,退款总金额,充值券退款总金额,手续费总金额,订单总金额,申请退款总金额\n" +
"`3,`0.02,`0.02,`0.00,`0.00000,`0.02,`0.02";
s = s.replaceAll("`", "").replaceAll("\uFEFF", "");
String s1 = StrUtil.subBefore(s, "总交易单数", false);
String s2 = s.substring(s.indexOf("总交易单数"));
// 过滤特殊字符
result = result.replaceAll("`", "").replaceAll("\uFEFF", "");
CsvReader reader = CsvUtil.getReader();
List<WechatPayReconcileBillDetail> read = reader.read(s1, WechatPayReconcileBillDetail.class);
List<WechatPayReconcileBillTotal> read1 = reader.read(s2, WechatPayReconcileBillTotal.class);
// 获取交易记录
String billDetail = StrUtil.subBefore(result, "资金流水总笔数", false);
List<WxReconcileFundFlowDetail> billDetails = reader.read(billDetail, WxReconcileFundFlowDetail.class);
// 获取汇总数据
String billTotal = result.substring(result.indexOf("资金流水总笔数"));
List<WxReconcileFundFlowDetail> billTotals = reader.read(billTotal, WxReconcileFundFlowDetail.class);
}
/**
* 验证交易对账单错误信息
*/
private void verifyBillMsg(String res) {
// 判断是否能是对账单还是特殊数据
if (StrUtil.startWith(res, "\uFEFF")){
return;
}
// 出现xml返回说明一定是错误了
Map<String, String> result = WxPayKit.xmlToMap(res);
// 返回码
String errCode = result.get(WeChatPayCode.ERROR_CODE);
// 原因
String resultMsg = result.get(WeChatPayCode.RETURN_MSG);
// 处理20002 分别是 账单不存在 账单未生成
if (Objects.equals(errCode, "20002")){
if (Objects.equals("No Bill Exist",resultMsg)){
log.warn("交易账单不存在, 请检查当前商户号在指定日期内是否有成功的交易");
throw new PayFailureException("交易账单不存在");
}
if (Objects.equals("Bill Creating",resultMsg)){
log.warn("交易账单未生成, 请在上午10点以后再试");
throw new PayFailureException("交易账单未生成, 请在上午10点以后再试");
}
} else {
log.error("微信交易对账失败, 原因: {}, 错误码: {}, 错误信息: {}", resultMsg, errCode, res);
throw new PayFailureException("微信支付交易对账失败");
}
}
/**
* 验证资金对账单
*/
private void verifyFundFlowMsg(String res) {
// 判断是否能是对账单还是特殊数据
if (StrUtil.startWith(res, "\uFEFF")){
return;
}
// 出现xml返回说明一定是错误了
Map<String, String> result = WxPayKit.xmlToMap(res);
String returnCode = result.get(WeChatPayCode.RETURN_CODE);
String errorMsg = result.get(WeChatPayCode.ERR_CODE_DES);
if (StrUtil.isBlank(errorMsg)) {
errorMsg = result.get(WeChatPayCode.RETURN_MSG);
}
log.error("微信资金对账失败, 原因: {}, 错误码: {}, 错误信息: {}", errorMsg, returnCode, res);
throw new PayFailureException("微信资金对账失败: " + errorMsg);
}
}

View File

@@ -0,0 +1,21 @@
package cn.bootx.platform.daxpay.service.core.record.reconcile.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付对账单记录
* @author xxm
* @since 2024/1/18
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@DbTable(comment = "支付对账单记录")
@TableName("pay_reconcile_record")
public class PayReconcileRecord extends MpCreateEntity {
}

View File

@@ -0,0 +1,21 @@
package cn.bootx.platform.daxpay.service.core.system.config.convert;
import cn.bootx.platform.daxpay.service.core.system.config.entity.PayChannelConfig;
import cn.bootx.platform.daxpay.service.dto.system.config.PayChannelConfigDto;
import cn.bootx.platform.daxpay.service.param.system.payinfo.PayChannelInfoParam;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*
* @author xxm
* @since 2024/1/8
*/
@Mapper
public interface PayChannelConfigConvert {
PayChannelConfigConvert CONVERT = Mappers.getMapper(PayChannelConfigConvert.class);
PayChannelConfig convert(PayChannelInfoParam in);
PayChannelConfigDto convert(PayChannelConfig in);
}

View File

@@ -1,8 +1,8 @@
package cn.bootx.platform.daxpay.service.core.system.payinfo.dao;
package cn.bootx.platform.daxpay.service.core.system.config.dao;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.service.core.system.payinfo.entity.PayChannelInfo;
import cn.bootx.platform.daxpay.service.core.system.config.entity.PayChannelConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
@@ -18,20 +18,20 @@ import java.util.Optional;
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayChannelInfoManager extends BaseManager<PayChannelInfoMapper, PayChannelInfo> {
public class PayChannelConfigManager extends BaseManager<PayChannelConfigMapper, PayChannelConfig> {
/**
* 根据code查询
*/
public Optional<PayChannelInfo> findByCode(String code){
return findByField(PayChannelInfo::getCode, code);
public Optional<PayChannelConfig> findByCode(String code){
return findByField(PayChannelConfig::getCode, code);
}
/**
* 查询全部
*/
@Override
public List<PayChannelInfo> findAll() {
public List<PayChannelConfig> findAll() {
return lambdaQuery().orderByAsc(MpIdEntity::getId).list();
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.service.core.system.config.dao;
import cn.bootx.platform.daxpay.service.core.system.config.entity.PayChannelConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/1/8
*/
@Mapper
public interface PayChannelConfigMapper extends BaseMapper<PayChannelConfig> {
}

View File

@@ -1,9 +1,9 @@
package cn.bootx.platform.daxpay.service.core.system.payinfo.entity;
package cn.bootx.platform.daxpay.service.core.system.config.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.service.core.system.payinfo.convert.PayChannelInfoConvert;
import cn.bootx.platform.daxpay.service.dto.system.payinfo.PayChannelInfoDto;
import cn.bootx.platform.daxpay.service.core.system.config.convert.PayChannelConfigConvert;
import cn.bootx.platform.daxpay.service.dto.system.config.PayChannelConfigDto;
import cn.bootx.platform.daxpay.service.param.system.payinfo.PayChannelInfoParam;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
@@ -15,17 +15,17 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付通道
* 都是在代码中定义好的界面上只做展示和部分信息的修改用不可以进行增删相关的操作
* 支付通道配置
* 有哪些通道都是在代码中定义好的界面上只做展示和部分信息的修改用不可以进行增删相关的操作
* @author xxm
* @since 2024/1/8
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@DbTable(comment = "支付通道信息")
@TableName("pay_channel_info")
public class PayChannelInfo extends MpBaseEntity implements EntityBaseFunction<PayChannelInfoDto> {
@DbTable(comment = "支付通道配置")
@TableName("pay_channel_config")
public class PayChannelConfig extends MpBaseEntity implements EntityBaseFunction<PayChannelConfigDto> {
/** 需要与系统中配置的枚举一致 */
@DbColumn(comment = "代码")
@@ -37,8 +37,8 @@ public class PayChannelInfo extends MpBaseEntity implements EntityBaseFunction<P
@TableField(updateStrategy = FieldStrategy.NEVER)
private String name;
/** logo图片 */
@DbColumn(comment = "ICON")
/** ICON图片 */
@DbColumn(comment = "ICON图片")
private Long iconId;
/** 卡牌背景色 */
@@ -47,22 +47,22 @@ public class PayChannelInfo extends MpBaseEntity implements EntityBaseFunction<P
/** 是否启用 */
@DbColumn(comment = "是否启用")
private Boolean enabled;
private Boolean enable;
/** 备注 */
@DbColumn(comment = "备注")
private String remark;
public static PayChannelInfo init(PayChannelInfoParam in){
return PayChannelInfoConvert.CONVERT.convert(in);
public static PayChannelConfig init(PayChannelInfoParam in){
return PayChannelConfigConvert.CONVERT.convert(in);
}
/**
* 转换
*/
@Override
public PayChannelInfoDto toDto() {
return PayChannelInfoConvert.CONVERT.convert(this);
public PayChannelConfigDto toDto() {
return PayChannelConfigConvert.CONVERT.convert(this);
}
}

View File

@@ -1,9 +1,9 @@
package cn.bootx.platform.daxpay.service.core.system.payinfo.service;
package cn.bootx.platform.daxpay.service.core.system.config.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.service.core.system.payinfo.dao.PayChannelInfoManager;
import cn.bootx.platform.daxpay.service.core.system.payinfo.entity.PayChannelInfo;
import cn.bootx.platform.daxpay.service.dto.system.payinfo.PayChannelInfoDto;
import cn.bootx.platform.daxpay.service.core.system.config.dao.PayChannelConfigManager;
import cn.bootx.platform.daxpay.service.core.system.config.entity.PayChannelConfig;
import cn.bootx.platform.daxpay.service.dto.system.config.PayChannelConfigDto;
import cn.bootx.platform.daxpay.service.param.system.payinfo.PayChannelInfoParam;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
@@ -22,32 +22,32 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class PayChannelInfoService {
private final PayChannelInfoManager manager;
public class PayChannelConfigService {
private final PayChannelConfigManager manager;
/**
* 列表
*/
public List<PayChannelInfoDto> findAll(){
public List<PayChannelConfigDto> findAll(){
return manager.findAll().stream()
.map(PayChannelInfo::toDto)
.map(PayChannelConfig::toDto)
.collect(Collectors.toList());
}
/**
* 单条
*/
public PayChannelInfoDto findById(Long id){
return manager.findById(id).map(PayChannelInfo::toDto).orElseThrow(DataNotExistException::new);
public PayChannelConfigDto findById(Long id){
return manager.findById(id).map(PayChannelConfig::toDto).orElseThrow(DataNotExistException::new);
}
/**
* 设置是否启用
*/
public void setEnable(String code,boolean enable){
PayChannelInfo info = manager.findByCode(code)
PayChannelConfig info = manager.findByCode(code)
.orElseThrow(DataNotExistException::new);
info.setEnabled(enable);
info.setEnable(enable);
manager.updateById(info);
}
@@ -55,7 +55,7 @@ public class PayChannelInfoService {
* 更新
*/
public void update(PayChannelInfoParam param){
PayChannelInfo info = manager.findById(param.getId()).orElseThrow(DataNotExistException::new);
PayChannelConfig info = manager.findById(param.getId()).orElseThrow(DataNotExistException::new);
BeanUtil.copyProperties(param,info, CopyOptions.create().ignoreNullValue());
manager.updateById(info);
}

View File

@@ -1,21 +0,0 @@
package cn.bootx.platform.daxpay.service.core.system.payinfo.convert;
import cn.bootx.platform.daxpay.service.core.system.payinfo.entity.PayChannelInfo;
import cn.bootx.platform.daxpay.service.dto.system.payinfo.PayChannelInfoDto;
import cn.bootx.platform.daxpay.service.param.system.payinfo.PayChannelInfoParam;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*
* @author xxm
* @since 2024/1/8
*/
@Mapper
public interface PayChannelInfoConvert {
PayChannelInfoConvert CONVERT = Mappers.getMapper(PayChannelInfoConvert.class);
PayChannelInfo convert(PayChannelInfoParam in);
PayChannelInfoDto convert(PayChannelInfo in);
}

View File

@@ -1,14 +0,0 @@
package cn.bootx.platform.daxpay.service.core.system.payinfo.dao;
import cn.bootx.platform.daxpay.service.core.system.payinfo.entity.PayChannelInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/1/8
*/
@Mapper
public interface PayChannelInfoMapper extends BaseMapper<PayChannelInfo> {
}

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.dto.system.payinfo;
package cn.bootx.platform.daxpay.service.dto.system.config;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.table.modify.annotation.DbColumn;
@@ -16,7 +16,7 @@ import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@Schema(title = "支付通道信息")
public class PayChannelInfoDto extends BaseDto {
public class PayChannelConfigDto extends BaseDto {
/** 需要与系统中配置的枚举一致 */
@Schema(description = "代码")
@@ -28,7 +28,7 @@ public class PayChannelInfoDto extends BaseDto {
/** 是否启用 */
@DbColumn(comment = "是否启用")
private Boolean enabled;
private Boolean enable;
/** logo图片 */
@Schema(description = "logo图片")