From c9065c07391ea07605df4c4342c990b8b46e875f Mon Sep 17 00:00:00 2001 From: xxm1995 Date: Wed, 17 Jan 2024 16:48:07 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E5=AF=B9=E8=B4=A6=E5=92=8C=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E5=90=8C=E6=AD=A5=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _doc/Task.md | 8 +- .../system/PayChannelInfoController.java | 3 +- .../order/RefundOrderChannelResult.java | 29 +++++++ .../result/order/RefundOrderResult.java | 8 +- .../bootx/platform/daxpay/util/PayUtil.java | 12 +++ .../gateway/controller/TestController.java | 16 ++++ .../daxpay/service/code/WeChatPayCode.java | 6 ++ .../channel/alipay/entity/AliPayOrder.java | 2 +- .../alipay/service/AliPaySyncService.java | 3 +- .../service/AlipayReconcileService.java | 62 +++++++++++++++ .../wechat/dao/WeChatPayConfigManager.java | 16 ---- .../wechat/service/WeChatPaySyncService.java | 30 +++++-- .../service/WechatPayReconcileService.java | 79 +++++++++++++++++++ .../order/pay/service/PayOrderService.java | 8 +- ...onvert.java => PayRefundOrderConvert.java} | 5 +- .../convert/RefundOrderChannelConvert.java | 23 ++++++ .../dao/PayRefundOrderChannelManager.java | 39 +++++++++ .../dao/PayRefundOrderChannelMapper.java | 14 ++++ .../order/refund/entity/PayRefundOrder.java | 4 +- .../refund/entity/PayRefundOrderChannel.java | 39 +++++++++ .../refund/service/PayRefundOrderService.java | 21 ++++- .../service/PayReconcileService.java | 23 ++++++ .../strategy/AlipayReconcileStrategy.java | 20 +++++ .../strategy/WechatPayReconcileStrategy.java | 20 +++++ .../sync/result/GatewaySyncResult.java | 12 ++- .../payment/sync/service/PaySyncService.java | 4 +- .../config/dao/PayApiConfigManager.java | 2 +- .../payinfo/dao/PayChannelInfoManager.java | 2 +- .../system/payinfo/entity/PayChannelInfo.java | 2 +- .../channel/config/PayChannelConfigDto.java | 33 -------- .../order/refund/RefundOrderChannelDto.java | 19 +++++ .../dto/system/payinfo/PayChannelInfoDto.java | 2 +- 32 files changed, 478 insertions(+), 88 deletions(-) create mode 100644 daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderChannelResult.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AlipayReconcileService.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatPayReconcileService.java rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/{PayRefundConvert.java => PayRefundOrderConvert.java} (80%) create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/RefundOrderChannelConvert.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelManager.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelMapper.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrderChannel.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/service/PayReconcileService.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/AlipayReconcileStrategy.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/WechatPayReconcileStrategy.java delete mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/dto/channel/config/PayChannelConfigDto.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/dto/order/refund/RefundOrderChannelDto.java diff --git a/_doc/Task.md b/_doc/Task.md index 2bfb84b61..cf283b744 100644 --- a/_doc/Task.md +++ b/_doc/Task.md @@ -59,10 +59,13 @@ - [x] (前端) 平台配置 - 2024-01-15: - [x] 优化支付相关订单和记录的查询条件 - - [ ] 开放接口新增查询类接口 + - [x] 开放接口新增查询类接口 - 2024-01-16: - - [ ] 支付单涉及退款状态同步逻辑 + - [x] 支付单涉及退款状态同步逻辑 +- 2024-01-17: + - [ ] 微信退款状态不一致补偿 - [ ] 接入支付网关的演示项目 + - [ ] 支付宝和微信支付对账 - **任务池** - [ ] 支付宝关闭支付时支持撤销方式, - [ ] 支持转账操作, 通过支付通道专有参数进行实现, 转账时只能单个通道进行操作 @@ -80,3 +83,4 @@ - [ ] 微信支付接口更新为V3接口 - [ ] 增加验签调试等功能的页面 - [ ] 请求IP参数增加正则校验 + - [ ] 增加账户金额表, 记录每天每个通道的账户金额, 并且提供查询接口 diff --git a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/system/PayChannelInfoController.java b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/system/PayChannelInfoController.java index a00e317c4..93338bbed 100644 --- a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/system/PayChannelInfoController.java +++ b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/system/PayChannelInfoController.java @@ -27,7 +27,8 @@ public class PayChannelInfoController { @Operation(summary = "查询全部") @GetMapping("/findAll") public ResResult> findAll(){ - return Res.ok(payChannelInfoService.findAll()); + List all = payChannelInfoService.findAll(); + return Res.ok(all); } @Operation(summary = "根据ID获取") diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderChannelResult.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderChannelResult.java new file mode 100644 index 000000000..8c9267acd --- /dev/null +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderChannelResult.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.result.order; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 支付退款通道明细数据 + * @author xxm + * @since 2024/1/17 + */ +@Data +@Accessors(chain = true) +@Schema(title = "支付退款通道明细数据") +public class RefundOrderChannelResult { + + @Schema(description = "通道") + private String channel; + + /** 关联支付网关退款请求号 */ + @Schema(description = "异步方式关联退款请求号(部分退款情况)") + private String refundRequestNo; + + @Schema(description = "异步支付方式") + private boolean async; + + @Schema(description = "退款金额") + private Integer amount; +} diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderResult.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderResult.java index 817bed6db..3ee3b9d28 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderResult.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/order/RefundOrderResult.java @@ -1,7 +1,6 @@ package cn.bootx.platform.daxpay.result.order; import cn.bootx.platform.daxpay.code.PayRefundStatusEnum; -import cn.bootx.platform.daxpay.entity.RefundableInfo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.experimental.Accessors; @@ -28,9 +27,6 @@ public class RefundOrderResult { @Schema(description = "退款号") private String refundNo; - @Schema(description = "异步方式关联退款请求号(部分退款情况)") - private String refundRequestNo; - @Schema(description = "标题") private String title; @@ -46,8 +42,8 @@ public class RefundOrderResult { @Schema(description = "退款时间") private LocalDateTime refundTime; - @Schema(description = "退款信息列表") - private List refundableInfo; + @Schema(description = "支付退款通道明细数据") + private List channels; /** * @see PayRefundStatusEnum diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PayUtil.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PayUtil.java index 844330e76..0d81f40c9 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PayUtil.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PayUtil.java @@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.util; import cn.bootx.platform.common.core.util.LocalDateTimeUtil; import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.entity.RefundableInfo; import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException; import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.param.pay.PayChannelParam; @@ -13,6 +14,7 @@ import lombok.experimental.UtilityClass; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Objects; /** * 支付工具类 @@ -72,6 +74,16 @@ public class PayUtil { return LocalDateTimeUtil.format(dateTime, DatePattern.PURE_DATETIME_PATTERN); } + /** + * 过滤出需要的可退款数据 + */ + public RefundableInfo refundableInfoFilter(List refundableInfos, PayChannelEnum payChannelEnum){ + return refundableInfos.stream() + .filter(o -> Objects.equals(o.getChannel(), payChannelEnum.getCode())) + .findFirst() + .orElseThrow(() -> new PayFailureException("退款数据不存在")); + } + /** * 获取支付单的超时时间 */ diff --git a/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/TestController.java b/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/TestController.java index 2222517cc..4e623082a 100644 --- a/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/TestController.java +++ b/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/TestController.java @@ -3,6 +3,8 @@ package cn.bootx.platform.daxpay.gateway.controller; import cn.bootx.platform.common.core.exception.BizException; import cn.bootx.platform.common.core.rest.Res; import cn.bootx.platform.common.core.rest.ResResult; +import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AlipayReconcileService; +import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WechatPayReconcileService; import cn.bootx.platform.daxpay.service.core.timeout.task.PayExpiredTimeTask; import cn.bootx.platform.daxpay.service.core.timeout.task.PayWaitOrderSyncTask; import cn.hutool.core.thread.ThreadUtil; @@ -29,6 +31,8 @@ import java.util.Objects; public class TestController { private final PayExpiredTimeTask expiredTimeTask;; private final PayWaitOrderSyncTask waitOrderSyncTask; + private final AlipayReconcileService alipayReconcileService; + private final WechatPayReconcileService wechatPayReconcileService; private final LockTemplate lockTemplate; @Operation(summary = "同步") @@ -66,4 +70,16 @@ public class TestController { return Res.ok(name); } + @Operation(summary = "下载支付宝对账单") + @GetMapping("/aliDownReconcile") + public ResResult aliDownReconcile(String date){ + return Res.ok(alipayReconcileService.downAndSave(date)); + } + @Operation(summary = "下载微信对账单") + @GetMapping("/wxDownReconcile") + public ResResult wxDownReconcile(String date){ + wechatPayReconcileService.downAndSave(date,null); + return Res.ok(); + } + } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/WeChatPayCode.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/WeChatPayCode.java index 651ff0b01..0316a4bd5 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/WeChatPayCode.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/WeChatPayCode.java @@ -91,4 +91,10 @@ public interface WeChatPayCode { /** 退款总金额(各退款单的退款金额累加) */ String REFUND_FEE = "refund_fee"; + /** 当前返回退款笔数 */ + String REFUND_COUNT = "refund_count"; + + /** 订单总退款次数 */ + String TOTAL_REFUND_COUNT = "total_refund_count"; + } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/entity/AliPayOrder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/entity/AliPayOrder.java index 8c598d560..73211d9b5 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/entity/AliPayOrder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/entity/AliPayOrder.java @@ -21,7 +21,7 @@ import lombok.experimental.Accessors; @Data @Accessors(chain = true) @DbTable(comment = "支付宝支付记录") -@TableName("pay_ali_pay_order") +@TableName("pay_alipay_order") public class AliPayOrder extends BasePayOrder implements EntityBaseFunction { /** 支付宝交易号 */ diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java index a1d7bf6ed..8aaa9037b 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java @@ -48,10 +48,9 @@ public class AliPaySyncService { // 查询参数 AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel); String tradeStatus = response.getTradeStatus(); - syncResult.setSyncInfo(JSONUtil.toJsonStr(response)); + syncResult.setSyncPayInfo(JSONUtil.toJsonStr(response)); // 支付完成 TODO 部分退款也在这个地方, 但无法进行区分, 需要借助对账进行处理 if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_SUCCESS) || Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_FINISHED)) { - this.syncRefundStatus(payOrder); PaymentContextLocal.get().getAsyncPayInfo().setTradeNo(response.getTradeNo()); // 支付完成时间 LocalDateTime payTime = LocalDateTimeUtil.of(response.getSendPayDate()); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AlipayReconcileService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AlipayReconcileService.java new file mode 100644 index 000000000..6cfb521b2 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AlipayReconcileService.java @@ -0,0 +1,62 @@ +package cn.bootx.platform.daxpay.service.core.channel.alipay.service; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.http.HttpUtil; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayDataDataserviceBillDownloadurlQueryModel; +import com.ijpay.alipay.AliPayApi; +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; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * 支付宝对账服务 + * @author xxm + * @since 2024/1/17 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AlipayReconcileService { + + /** + * 下载对账单, 并进行解析进行保存 + */ + @SneakyThrows + public String downAndSave(String date){ + try { + AlipayDataDataserviceBillDownloadurlQueryModel model = new AlipayDataDataserviceBillDownloadurlQueryModel(); + model.setBillDate(date); + model.setBillType("trade"); + // 获取对账单下载地址并下载 + String url = AliPayApi.billDownloadUrlQuery(model); + byte[] bytes = HttpUtil.downloadBytes(url); + // 使用 Apache commons-compress 包装流, + ZipArchiveInputStream zipArchiveInputStream = new ZipArchiveInputStream(new ByteArrayInputStream(bytes),"GBK"); + + ZipArchiveEntry entry; + while ((entry= zipArchiveInputStream.getNextZipEntry()) != null){ + String name = entry.getName(); + System.out.println(name); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(zipArchiveInputStream,"GBK")); + List strings = IoUtil.readLines(bufferedReader, new ArrayList<>()); + strings.forEach(System.out::println); + + } + + return url; + } catch (AlipayApiException e) { + log.error("下载对账单失败",e); + throw new RuntimeException(e); + } + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/dao/WeChatPayConfigManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/dao/WeChatPayConfigManager.java index a2f6a5bae..b8aec5e41 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/dao/WeChatPayConfigManager.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/dao/WeChatPayConfigManager.java @@ -1,14 +1,7 @@ package cn.bootx.platform.daxpay.service.core.channel.wechat.dao; -import cn.bootx.platform.common.core.rest.param.PageParam; -import cn.bootx.platform.common.mybatisplus.base.MpIdEntity; import cn.bootx.platform.common.mybatisplus.impl.BaseManager; -import cn.bootx.platform.common.mybatisplus.util.MpUtil; -import cn.bootx.platform.common.query.generator.QueryGenerator; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; -import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayConfigParam; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -22,13 +15,4 @@ import org.springframework.stereotype.Repository; @RequiredArgsConstructor public class WeChatPayConfigManager extends BaseManager { - /** - * 分页 - */ - public Page page(PageParam pageParam, WeChatPayConfigParam param) { - Page mpPage = MpUtil.getMpPage(pageParam, WeChatPayConfig.class); - QueryWrapper wrapper = QueryGenerator.generator(param); - wrapper.orderByDesc(MpIdEntity.Id.id); - return this.page(mpPage,wrapper); - } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java index 0e32c2899..5d1e97365 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java @@ -1,17 +1,22 @@ package cn.bootx.platform.daxpay.service.core.channel.wechat.service; import cn.bootx.platform.common.core.util.LocalDateTimeUtil; +import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum; +import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.service.code.WeChatPayCode; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; +import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderChannelManager; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; +import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderChannel; import cn.bootx.platform.daxpay.service.core.payment.sync.result.GatewaySyncResult; import cn.hutool.core.date.DatePattern; import cn.hutool.json.JSONUtil; import com.ijpay.core.enums.SignType; import com.ijpay.core.kit.WxPayKit; import com.ijpay.wxpay.WxPayApi; +import com.ijpay.wxpay.model.OrderQueryModel; import com.ijpay.wxpay.model.UnifiedOrderModel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,13 +36,14 @@ import java.util.Objects; @Service @RequiredArgsConstructor public class WeChatPaySyncService { + private final PayOrderChannelManager payOrderChannelManager; /** * 同步查询 */ public GatewaySyncResult syncPayStatus(PayOrder order, WeChatPayConfig weChatPayConfig) { GatewaySyncResult syncResult = new GatewaySyncResult().setSyncStatus(PaySyncStatusEnum.FAIL); - Map params = UnifiedOrderModel.builder() + Map params = OrderQueryModel.builder() .appid(weChatPayConfig.getWxAppId()) .mch_id(weChatPayConfig.getWxMchId()) .nonce_str(WxPayKit.generateStr()) @@ -47,7 +53,7 @@ public class WeChatPaySyncService { try { String xmlResult = WxPayApi.orderQuery(params); Map result = WxPayKit.xmlToMap(xmlResult); - syncResult.setSyncInfo(JSONUtil.toJsonStr(result)); + syncResult.setSyncPayInfo(JSONUtil.toJsonStr(result)); // 查询失败 if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RETURN_CODE))) { log.warn("查询微信订单失败:{}", result); @@ -76,7 +82,7 @@ public class WeChatPaySyncService { // 已退款/退款中 触发一下退款记录的查询 if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_REFUND)) { - this.syncRefundStatus(order.getId(), weChatPayConfig); + this.syncRefundStatus(order, weChatPayConfig); } // 已关闭 if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_CLOSED) @@ -95,16 +101,28 @@ public class WeChatPaySyncService { /** * 退款查询 */ - private GatewaySyncResult syncRefundStatus(Long paymentId, WeChatPayConfig weChatPayConfig){ + private GatewaySyncResult syncRefundStatus(PayOrder order, WeChatPayConfig weChatPayConfig){ + PayOrderChannel orderChannel = payOrderChannelManager.findByPaymentIdAndChannel(order.getId(), PayChannelEnum.WECHAT.getCode()) + .orElseThrow(() -> new PayFailureException("支付订单通道信息不存在")); + Map params = UnifiedOrderModel.builder() .appid(weChatPayConfig.getWxAppId()) .mch_id(weChatPayConfig.getWxMchId()) .nonce_str(WxPayKit.generateStr()) - .out_trade_no(String.valueOf(paymentId)) + .out_trade_no(String.valueOf(order.getId())) .build() .createSign(weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256); String xmlResult = WxPayApi.orderRefundQuery(false, params); Map result = WxPayKit.xmlToMap(xmlResult); - return new GatewaySyncResult().setSyncInfo(JSONUtil.toJsonStr(result)); + // 获取 + + + + // 判断是否全部退款 + Integer refundFee = Integer.valueOf(result.get(WeChatPayCode.REFUND_FEE)); + if (Objects.equals(refundFee, orderChannel.getAmount())){ + return new GatewaySyncResult().setSyncStatus(PaySyncStatusEnum.REFUND); + } + return new GatewaySyncResult().setSyncPayInfo(JSONUtil.toJsonStr(result)); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatPayReconcileService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatPayReconcileService.java new file mode 100644 index 000000000..d54a0bcf9 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatPayReconcileService.java @@ -0,0 +1,79 @@ +package cn.bootx.platform.daxpay.service.core.channel.wechat.service; + +import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.StrUtil; +import com.ijpay.core.enums.SignType; +import com.ijpay.core.kit.WxPayKit; +import com.ijpay.wxpay.WxPayApi; +import com.ijpay.wxpay.model.DownloadBillModel; +import com.ijpay.wxpay.model.DownloadFundFlowModel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayInputStream; +import java.util.Map; + +/** + * 微信支付对账 + * @author xxm + * @since 2024/1/17 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatPayReconcileService { + private final WeChatPayConfigService weChatPayConfigService; + + /** + * 下载对账单并保存 + * @param date 下载日期 + */ + public void downAndSave(String date, WeChatPayConfig config) { + config = weChatPayConfigService.getConfig(); + this.downBill(date, config); + this.downFundFlow(date, config); + } + + /** + * 下载交易账单 + */ + public void downBill(String date, WeChatPayConfig config){ + // 下载交易账单 + Map bill = DownloadBillModel.builder() + .mch_id(config.getWxMchId()) + .appid(config.getWxAppId()) + .nonce_str(WxPayKit.generateStr()) + .bill_date(date) + .bill_type("ALL") + .build() + .createSign(config.getApiKeyV2(), SignType.HMACSHA256); + String s = WxPayApi.downloadBill(config.isSandbox(), bill); + System.out.println(s); + } + + + /** + * 下载资金账单, 需要双向证书 + */ + public void downFundFlow(String date, WeChatPayConfig config){ + if (StrUtil.isBlank(config.getP12())){ + return; + } + Map fundFlow = DownloadFundFlowModel.builder() + .mch_id(config.getWxMchId()) + .appid(config.getWxAppId()) + .nonce_str(WxPayKit.generateStr()) + .bill_date(date) + .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()); + System.out.println(s); + } + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderService.java index be0ee4182..b20ff6690 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderService.java @@ -2,18 +2,17 @@ package cn.bootx.platform.daxpay.service.core.order.pay.service; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; -import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.entity.RefundableInfo; import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderManager; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.service.core.timeout.service.PayExpiredTimeService; +import cn.bootx.platform.daxpay.util.PayUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; -import java.util.Objects; /** * 支付订单服务 @@ -60,10 +59,7 @@ public class PayOrderService { public void updateRefundSuccess(PayOrder payment, int amount, PayChannelEnum payChannelEnum) { // 删除旧有的退款记录, 替换退款完的新的 List refundableInfos = payment.getRefundableInfos(); - RefundableInfo refundableInfo = refundableInfos.stream() - .filter(o -> Objects.equals(o.getChannel(), payChannelEnum.getCode())) - .findFirst() - .orElseThrow(() -> new PayFailureException("退款数据不存在")); + RefundableInfo refundableInfo = PayUtil.refundableInfoFilter(refundableInfos, payChannelEnum); refundableInfos.remove(refundableInfo); refundableInfo.setAmount(refundableInfo.getAmount() - amount); refundableInfos.add(refundableInfo); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/PayRefundConvert.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/PayRefundOrderConvert.java similarity index 80% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/PayRefundConvert.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/PayRefundOrderConvert.java index 35704511b..f19bdde0b 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/PayRefundConvert.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/PayRefundOrderConvert.java @@ -11,12 +11,13 @@ import org.mapstruct.factory.Mappers; * @since 2022/3/2 */ @Mapper -public interface PayRefundConvert { +public interface PayRefundOrderConvert { - PayRefundConvert CONVERT = Mappers.getMapper(PayRefundConvert.class); + PayRefundOrderConvert CONVERT = Mappers.getMapper(PayRefundOrderConvert.class); PayRefundOrderDto convert(PayRefundOrder in); RefundOrderResult convertResult(PayRefundOrder in); + } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/RefundOrderChannelConvert.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/RefundOrderChannelConvert.java new file mode 100644 index 000000000..c31437759 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/convert/RefundOrderChannelConvert.java @@ -0,0 +1,23 @@ +package cn.bootx.platform.daxpay.service.core.order.refund.convert; + +import cn.bootx.platform.daxpay.result.order.RefundOrderChannelResult; +import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrderChannel; +import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderChannelDto; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author xxm + * @since 2024/1/17 + */ +@Mapper +public interface RefundOrderChannelConvert { + RefundOrderChannelConvert CONVERT = Mappers.getMapper(RefundOrderChannelConvert.class); + + + RefundOrderChannelDto convert(PayRefundOrderChannel in); + + RefundOrderChannelResult convertResult(PayRefundOrderChannel in); + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelManager.java new file mode 100644 index 000000000..10cb8c5b5 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelManager.java @@ -0,0 +1,39 @@ +package cn.bootx.platform.daxpay.service.core.order.refund.dao; + +import cn.bootx.platform.common.mybatisplus.impl.BaseManager; +import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrderChannel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * + * @author xxm + * @since 2024/1/17 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class PayRefundOrderChannelManager extends BaseManager { + + + /** + * 根据退款单ID查找 + */ + public List findAllByRefundId(Long paymentId){ + return findAllByField(PayRefundOrderChannel::getRefundId,paymentId); + } + + /** + * 根据退款单ID和退款通道查询 + */ + public Optional findByPaymentIdAndChannel(Long paymentId, String channel) { + return lambdaQuery() + .eq(PayRefundOrderChannel::getRefundId,paymentId) + .eq(PayRefundOrderChannel::getChannel,channel) + .oneOpt(); + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelMapper.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelMapper.java new file mode 100644 index 000000000..809b8d385 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundOrderChannelMapper.java @@ -0,0 +1,14 @@ +package cn.bootx.platform.daxpay.service.core.order.refund.dao; + +import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrderChannel; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * + * @author xxm + * @since 2024/1/17 + */ +@Mapper +public interface PayRefundOrderChannelMapper extends BaseMapper { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java index d150dcdf4..4359bc476 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java @@ -5,7 +5,7 @@ import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; import cn.bootx.platform.daxpay.code.PayRefundStatusEnum; import cn.bootx.platform.daxpay.entity.RefundableInfo; import cn.bootx.platform.daxpay.service.common.typehandler.RefundableInfoTypeHandler; -import cn.bootx.platform.daxpay.service.core.order.refund.convert.PayRefundConvert; +import cn.bootx.platform.daxpay.service.core.order.refund.convert.PayRefundOrderConvert; import cn.bootx.platform.daxpay.service.dto.order.refund.PayRefundOrderDto; import cn.bootx.table.modify.annotation.DbColumn; import cn.bootx.table.modify.annotation.DbTable; @@ -104,7 +104,7 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction

new DataNotExistException("未查询到支付订单")); } - return PayRefundConvert.CONVERT.convertResult(refundOrder); + // 查询退款明细 + List refundOrderChannels = refundOrderChannelManager.findAllByRefundId(refundOrder.getId()); + List channels = refundOrderChannels.stream() + .map(RefundOrderChannelConvert.CONVERT::convertResult) + .collect(Collectors.toList()); + + RefundOrderResult refundOrderResult = PayRefundOrderConvert.CONVERT.convertResult(refundOrder); + refundOrderResult.setChannels(channels); + return refundOrderResult; } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/service/PayReconcileService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/service/PayReconcileService.java new file mode 100644 index 000000000..42bbc881f --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/service/PayReconcileService.java @@ -0,0 +1,23 @@ +package cn.bootx.platform.daxpay.service.core.payment.reconcile.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 支付对账单下载服务 + * @author xxm + * @since 2024/1/17 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PayReconcileService { + + /** + * 下载对账单 + */ + public void downReconcileBill(){ + + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/AlipayReconcileStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/AlipayReconcileStrategy.java new file mode 100644 index 000000000..e2e2bcbdd --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/AlipayReconcileStrategy.java @@ -0,0 +1,20 @@ +package cn.bootx.platform.daxpay.service.core.payment.reconcile.strategy; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * 支付宝对账策略 + * @author xxm + * @since 2024/1/17 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class AlipayReconcileStrategy { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/WechatPayReconcileStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/WechatPayReconcileStrategy.java new file mode 100644 index 000000000..0a1a9f425 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/reconcile/strategy/WechatPayReconcileStrategy.java @@ -0,0 +1,20 @@ +package cn.bootx.platform.daxpay.service.core.payment.reconcile.strategy; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * 微信支付对账策略 + * @author xxm + * @since 2024/1/17 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class WechatPayReconcileStrategy { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/result/GatewaySyncResult.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/result/GatewaySyncResult.java index fd67a5466..9904e2d1f 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/result/GatewaySyncResult.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/result/GatewaySyncResult.java @@ -22,8 +22,16 @@ public class GatewaySyncResult { */ private PaySyncStatusEnum syncStatus = FAIL; - /** 网关返回的对象, 序列化为json字符串 */ - private String syncInfo; + /** 同步支付时网关返回的对象, 序列化为json字符串 */ + private String syncPayInfo; + + /** 同步退款时网关返回的对象, 序列化为json字符串 */ + private String syncRefundInfo; + + /** 退款信息是否需要修复 */ + private boolean repairRefund; + + /** 网关返回退款信息列表 */ /** 错误提示 */ private String errorMsg; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java index c9169100c..3bfb5b478 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java @@ -227,7 +227,7 @@ public class PaySyncService { /** - * 保存同步记录 + * 保存同步记录 TODO 目前出现一次请求多次与网关同步, 未全部记录 * @param payOrder 支付单 * @param syncResult 同步结果 * @param repair 是否修复 @@ -238,7 +238,7 @@ public class PaySyncService { .setPaymentId(payOrder.getId()) .setBusinessNo(payOrder.getBusinessNo()) .setAsyncChannel(payOrder.getAsyncChannel()) - .setSyncInfo(syncResult.getSyncInfo()) + .setSyncInfo(syncResult.getSyncPayInfo()) .setGatewayStatus(syncResult.getSyncStatus().getCode()) .setRepairOrder(repair) .setRepairOrderId(repairOrderId) diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/config/dao/PayApiConfigManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/config/dao/PayApiConfigManager.java index 51dc5acdd..d0c288a5e 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/config/dao/PayApiConfigManager.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/config/dao/PayApiConfigManager.java @@ -39,6 +39,6 @@ public class PayApiConfigManager extends BaseManager findAll() { - return lambdaQuery().orderByDesc(MpIdEntity::getId).list(); + return lambdaQuery().orderByAsc(MpIdEntity::getId).list(); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/dao/PayChannelInfoManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/dao/PayChannelInfoManager.java index ea0a7af47..43fc56867 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/dao/PayChannelInfoManager.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/dao/PayChannelInfoManager.java @@ -32,6 +32,6 @@ public class PayChannelInfoManager extends BaseManager findAll() { - return lambdaQuery().orderByDesc(MpIdEntity::getId).list(); + return lambdaQuery().orderByAsc(MpIdEntity::getId).list(); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/entity/PayChannelInfo.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/entity/PayChannelInfo.java index c03df448f..d5dfcbc17 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/entity/PayChannelInfo.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/system/payinfo/entity/PayChannelInfo.java @@ -47,7 +47,7 @@ public class PayChannelInfo extends MpBaseEntity implements EntityBaseFunction